fix(access): only accept END_SESSION from idle so the session cap can't strand funds
The root-level END_SESSION let the 10-minute hard cap (and the End Session button) jump to `locked` from any state, bypassing the money-path guards the machine already has: confirmAbandon with bills stacked, an in-flight dispense, an outbound cash-in payment. Cap fires at minute 10 while a customer's bills sit in the stacker → locked → next unlock resetContext wipes them unpaid; during dispensingCash the done-event is dropped and no transaction record is written. Nothing is lost by scoping it: every transaction terminal state already targets #atm.locked on this branch, so the machine re-locks on its own when the transaction ends. END_SESSION now lives on idle.on only, and useSessionSecurity defers both deadlines until currentState is idle — an expired session re-locks on the first tick back at the menu. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
82fbf12950
commit
5114619fce
3 changed files with 34 additions and 19 deletions
|
|
@ -17,7 +17,11 @@ import { useAtmStore } from '@/stores/atm'
|
|||
* (those carry their own, longer machine timeouts).
|
||||
* - HARD cap (HARD_CAP_MS): re-lock this long after the session began,
|
||||
* regardless of activity. Anchored to unlock time and never reset — an
|
||||
* absolute ceiling a forgotten or relayed card can't hold open.
|
||||
* absolute ceiling a forgotten or relayed card can't hold open. Like the
|
||||
* soft limit it only fires while on the idle menu: locking mid-transaction
|
||||
* would strand stacked bills or an in-flight dispense, and every
|
||||
* transaction already returns to `locked` on its own, so the cap simply
|
||||
* takes effect the moment the machine is back at idle.
|
||||
*
|
||||
* Security properties:
|
||||
* - Only `event.isTrusted` input resets the soft timer, so synthetic/scripted
|
||||
|
|
@ -77,6 +81,10 @@ export function useSessionSecurity() {
|
|||
// can only ever make it fire late-then-immediately, never early.
|
||||
useIntervalFn(() => {
|
||||
if (sessionStartedAt === null || atm.isLocked || !atm.accessControl.enabled) return
|
||||
// Never lock out from under a transaction (the machine only accepts
|
||||
// END_SESSION from idle anyway); the deadlines keep counting meanwhile, so
|
||||
// an expired session re-locks on the first tick back at the menu.
|
||||
if (atm.currentState !== 'idle') return
|
||||
const now = Date.now()
|
||||
|
||||
// Hard cap first — absolute, activity-independent.
|
||||
|
|
@ -86,8 +94,8 @@ export function useSessionSecurity() {
|
|||
return
|
||||
}
|
||||
|
||||
// Soft inactivity — idle menu only, resets on trusted input.
|
||||
if (atm.currentState === 'idle' && now - lastActivityAt >= SOFT_IDLE_MS) {
|
||||
// Soft inactivity — resets on trusted input.
|
||||
if (now - lastActivityAt >= SOFT_IDLE_MS) {
|
||||
disarm()
|
||||
atm.endSession('inactivity')
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue