You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
39 lines
1.0 KiB
TypeScript
39 lines
1.0 KiB
TypeScript
/**
|
|
* Brute-force lockout as a pure reducer so POS and back office share the exact
|
|
* policy and it is testable without a clock. Attempts are audit-logged by the
|
|
* caller (I7: every mutation audit-logged in the same transaction).
|
|
*/
|
|
export interface LockoutPolicy {
|
|
maxFailures: number
|
|
lockMs: number
|
|
}
|
|
|
|
export const DEFAULT_LOCKOUT: LockoutPolicy = { maxFailures: 5, lockMs: 60_000 }
|
|
|
|
export interface LockoutState {
|
|
failures: number
|
|
lockedUntilMs?: number
|
|
}
|
|
|
|
export const FRESH_LOCKOUT: LockoutState = { failures: 0 }
|
|
|
|
export function isLocked(state: LockoutState, nowMs: number): boolean {
|
|
return state.lockedUntilMs !== undefined && nowMs < state.lockedUntilMs
|
|
}
|
|
|
|
export function recordFailure(
|
|
state: LockoutState,
|
|
nowMs: number,
|
|
policy: LockoutPolicy = DEFAULT_LOCKOUT,
|
|
): LockoutState {
|
|
const failures = state.failures + 1
|
|
if (failures >= policy.maxFailures) {
|
|
return { failures, lockedUntilMs: nowMs + policy.lockMs }
|
|
}
|
|
return { failures }
|
|
}
|
|
|
|
export function recordSuccess(): LockoutState {
|
|
return FRESH_LOCKOUT
|
|
}
|