feat: POS PIN unlock with employee number + PIN auth

- Add employeeNumber and pinHash fields to users table
- POST /auth/pin-login: takes combined code (4-digit employee# + 4-digit PIN)
- POST /auth/set-pin: employee sets their own PIN (requires full auth)
- DELETE /auth/pin: remove PIN
- Lock screen with numpad, auto-submits on 8 digits, visual dot separator
- POS uses its own auth token separate from admin session
- Admin "POS" link clears admin session before navigating
- /pos route has no auth guard — lock screen is the auth
- API client uses POS token when available, admin token otherwise
- Auto-lock timer reads pos_lock_timeout from app_config (default 15 min)
- Lock button in POS top bar, shows current cashier name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryan
2026-04-04 20:59:09 +00:00
parent 978c6da37a
commit 24b2b8c292
13 changed files with 396 additions and 39 deletions

View File

@@ -17,3 +17,13 @@ export const LoginSchema = z.object({
password: z.string().min(1),
})
export type LoginInput = z.infer<typeof LoginSchema>
export const PinLoginSchema = z.object({
code: z.string().min(8).max(10).regex(/^\d+$/, 'Code must be digits only'),
})
export type PinLoginInput = z.infer<typeof PinLoginSchema>
export const SetPinSchema = z.object({
pin: z.string().min(4).max(6).regex(/^\d+$/, 'PIN must be digits only'),
})
export type SetPinInput = z.infer<typeof SetPinSchema>

View File

@@ -1,8 +1,8 @@
export { PaginationSchema } from './pagination.schema.js'
export type { PaginationInput, PaginatedResponse } from './pagination.schema.js'
export { UserRole, RegisterSchema, LoginSchema } from './auth.schema.js'
export type { RegisterInput, LoginInput } from './auth.schema.js'
export { UserRole, RegisterSchema, LoginSchema, PinLoginSchema, SetPinSchema } from './auth.schema.js'
export type { RegisterInput, LoginInput, PinLoginInput, SetPinInput } from './auth.schema.js'
export {
BillingMode,