diff --git a/.claude-context/sessions/2026-09-23-visitor-kiosk-build.md b/.claude-context/sessions/2026-09-23-visitor-kiosk-build.md index c945bc6..20f73e1 100644 --- a/.claude-context/sessions/2026-09-23-visitor-kiosk-build.md +++ b/.claude-context/sessions/2026-09-23-visitor-kiosk-build.md @@ -179,3 +179,24 @@ Whole project created this session. Principal files: - `brother_ql` 0.9.4 (PyPI) — `print --help` documents the `--red` requirement. - Brother QL-820NWB specification: 12–62 mm media, 60.96 mm max print width, 300×300 dpi. - RFC 6238 (TOTP) — test vectors used to verify `src/auth.js`. + +--- + +## Addendum — 2026-09-23, PIN reset bug + +**Symptom:** after changing a recurring visitor's PIN, phone + PIN was rejected at the kiosk. + +**Cause:** the Edit form carried a "PIN" field whose value `PATCH /admin/api/frequent/:id` +never read. The record saved, the field was silently discarded, and the admin handed out a PIN +the system had never stored. (`POST /admin/api/frequent/:id/pin`, the "New PIN" button, was +always correct — verified by reproduction.) + +**Fix:** `PATCH` now honours a supplied `pin`: 4 digits, checked for collisions via +`allocatePin(pin, row.id)`, written to `pin_enc` and `pin_lookup`. Blank keeps the current PIN. +A malformed or already-used PIN is rejected with a message instead of ignored. The response +carries `pinChanged`, and the console shows the printable card when it is true. Saving a record +also clears `pin_attempts` for both the old and new phone number, so a changed number cannot +leave a stale lockout. + +**Files:** `src/routes/admin.js` (PATCH handler), `public/js/admin.js` (edit modal shows the +current PIN, relabelled field, shows the card after a change), `public/css/admin.css`, `README.md`. diff --git a/README.md b/README.md index 78366b5..c577e27 100644 --- a/README.md +++ b/README.md @@ -503,6 +503,16 @@ digits is only 10,000 combinations, so hashing would add nothing against anyone of the database — the real protection is the lockout: five wrong PINs on a number locks it for 15 minutes. +Two ways to change someone's PIN, and they now behave the same: + +- **New PIN** on the recurring visitors list issues a random one. +- **Edit** shows the current PIN and has a box to type a replacement. Leave it blank to keep the + current one. A typed PIN must be 4 digits and not already in use; either problem is reported + rather than ignored. + +Saving a record also clears any PIN lockout on that visitor, under both the old and the new +mobile number. + Four digits gives 10,000 combinations and each must be unique, so that is the ceiling on simultaneous recurring visitors. Deactivating someone frees theirs. diff --git a/public/css/admin.css b/public/css/admin.css index 2eadb42..e2fed1a 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -445,3 +445,9 @@ pre.raw { border-radius: 3px; background: #fff; } + +.pin-inline { + font-family: ui-monospace, Menlo, Consolas, monospace; + letter-spacing: 0.12em; + color: var(--deep); +} diff --git a/public/js/admin.js b/public/js/admin.js index f4d2573..629c28c 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -502,7 +502,13 @@ function openRecurringModal(person = null) { - ${field('PIN (leave blank to generate one)', 'pin', '')} + ${ + editing + ? `` + : field('PIN (leave blank to generate one)', 'pin', '') + } ${field('Notes', 'notes', person?.notes)}