feat(access): add End Session button to re-lock a tap-in session
A Bolt Card tap loads the holder's card for the whole session, so an unattended idle menu is transactable by the next person until the 60s IDLE_LOCK_TIMEOUT fires. Give the holder an explicit re-lock: - END_SESSION event on `idle`, guarded to the active gate, targets `locked` (whose entry already clears the access session + loaded card). No-op on a gate-disabled machine that rests at idle. - endSession() store action; IdleView shows a destructive-styled "End Session" button top-right only while accessControl.enabled. - Tests: END_SESSION re-locks when the gate is active; no-op when off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ivBosaWmv8vwFE7ejrdHW
This commit is contained in:
parent
c83b40fe5e
commit
d35faf1c93
5 changed files with 53 additions and 0 deletions
|
|
@ -90,6 +90,25 @@ describe('ATM access control (ADR-003)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('user-initiated end of session', () => {
|
||||
it('END_SESSION re-locks immediately when the gate is active', () => {
|
||||
const actor = createActor(createATMMachine({}, { accessControlEnabled: true }))
|
||||
actor.start()
|
||||
actor.send({ type: 'ACCESS_GRANTED', role: 'user', credentialIdHash: 'abc' })
|
||||
expect(actor.getSnapshot().value).toBe('idle')
|
||||
actor.send({ type: 'END_SESSION' })
|
||||
expect(actor.getSnapshot().value).toBe('locked')
|
||||
})
|
||||
|
||||
it('END_SESSION is a no-op when the gate is disabled (stays at idle)', () => {
|
||||
const actor = createActor(createATMMachine()) // gate off → rests at idle
|
||||
actor.start()
|
||||
expect(actor.getSnapshot().value).toBe('idle')
|
||||
actor.send({ type: 'END_SESSION' })
|
||||
expect(actor.getSnapshot().value).toBe('idle')
|
||||
})
|
||||
})
|
||||
|
||||
describe('build/dev bypass', () => {
|
||||
it('accessBypassFlag opens the gate even when enabled', () => {
|
||||
const actor = createActor(
|
||||
|
|
|
|||
|
|
@ -549,6 +549,11 @@ export function createATMMachine(
|
|||
target: 'cashOut',
|
||||
actions: ['setStartTime', 'setCashOutFee'],
|
||||
},
|
||||
// User taps "End session": re-lock now rather than waiting out
|
||||
// IDLE_LOCK_TIMEOUT. Guarded to the active gate so a gate-disabled
|
||||
// machine (which rests at idle) never leaves it via this event.
|
||||
// `locked`'s entry clears the access session + loaded card.
|
||||
END_SESSION: { guard: 'accessGateActive', target: 'locked' },
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -179,6 +179,10 @@ export type ATMEvent =
|
|||
| { type: 'ACCESS_GRANTED'; role: AccessRole; credentialIdHash: string }
|
||||
| { type: 'ACCESS_DENIED'; reason: string }
|
||||
| { type: 'DEV_UNLOCK' }
|
||||
// User-initiated end of a tap-in session: re-lock immediately instead of
|
||||
// waiting out IDLE_LOCK_TIMEOUT, so a loaded Bolt Card can't be reused by
|
||||
// the next person the moment its holder steps away.
|
||||
| { type: 'END_SESSION' }
|
||||
| { type: 'SELECT_AMOUNT'; amount: number }
|
||||
| { type: 'FINISH_INSERTING' }
|
||||
| { type: 'USER_SCANNED_NPUB'; npub: string }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue