Skip to main content

Staff session lifecycle

A staff session is what's between sign-in and sign-out. It governs which staff is attributed to actions, when the sign-in overlay re-appears, and when the session is revoked.

Drafted from planning · v0.1

States and transitions

State definitions

StateWhat it means
activeSession is in use; the operator's actions are attributed to this staff
idle_lockedSession exists; the sign-in overlay is shown; work state preserved
expiredSession has expired (timeout or sign-out); cookie cleared
revokedAn admin explicitly killed the session; cookie cleared

Behaviors

Sign-in

POST /api/auth/login:

  • Inserts staff_sessions row: {staff_id, session_token, expires_at = now + 12h, ip, user_agent}
  • Response: Set-Cookie: helm_session=<token>; HttpOnly; SameSite=Lax; Secure; Max-Age=43200
  • Transitions session to active

Idle detection

The operator app listens for pointerdown, keydown, wheel events. Each event resets a 60-second timer. On timeout:

  • The sign-in overlay covers the screen
  • The session row is NOT deleted (so work state can resume on PIN re-entry)
  • The cookie remains set

Idle unlock

On PIN re-entry through the overlay:

  • The Worker verifies the PIN against the same staff row
  • If match: removes the overlay, the page state is intact
  • If no match: increments failed_attempts; lockout applies after 5 in 60s

Sign-out

POST /api/auth/logout:

  • Deletes the staff_sessions row
  • Clears the cookie (Set-Cookie: helm_session=; Max-Age=0)

Hard expiry

12 hours after sign-in (configurable in shop_config.session_max_seconds), the session expires regardless of activity. The operator re-signs-in on the next request.

This bounds the blast radius of a leaked cookie.

Admin revocation

DELETE /api/auth/sessions/{id}:

  • Sys Admin or Owner can revoke any session
  • Useful when staff leaves the shop and their device walks away with them
  • Audit-logged

Idle-lockout vs hard expiry

The two timeouts serve different purposes:

  • Idle (60s) — physical security. The operator walks away from the till; the next person needs a PIN.
  • Hard expiry (12h) — session theft mitigation. Even if a cookie leaks, it's useful only until the hard expiry.

The values are configurable per shop; defaults err on the conservative side.

  • HttpOnly — JS cannot read it (prevents XSS-based session theft)
  • SameSite=Lax — not sent on cross-site POSTs (CSRF protection for most operations)
  • Secure — HTTPS only in production
  • Max-Age=43200 — 12 hours; matches hard expiry

For state-changing endpoints, the Worker also requires a CSRF token (planned, slice-1 follow-on) — a hidden form field that the client-side enhancer or template populates from staff_sessions.csrf_token.

Audit

Sign-in, sign-out, and revoke events are written to audit_events. Idle-lock and unlock are NOT audited (they don't change anything substantive). Failed sign-in attempts are logged separately to auth_attempts for rate-limit + forensic use.

See also