Skip to content

Gate PIN (D3): decouple claim PIN from override authority (AdminEnrolled) — stacked on #1071#1074

Merged
peterdrier merged 1 commit into
peterdrier:mainfrom
veryaaron:fix/gate-supervisor-pin
Jul 1, 2026
Merged

Gate PIN (D3): decouple claim PIN from override authority (AdminEnrolled) — stacked on #1071#1074
peterdrier merged 1 commit into
peterdrier:mainfrom
veryaaron:fix/gate-supervisor-pin

Conversation

@veryaaron

Copy link
Copy Markdown

Phase D3 of the gate PIN rework — stacked on #1071 (contains its commit until that merges). Fixes the "everyone should be able to set a PIN" block (Peter Drier, as an Admin, couldn't self-enrol) — nobodies-collective#909-adjacent.

The decouple

One GateStaffPin row now has an AdminEnrolled bool that splits the two jobs a PIN used to do:

  • Claim PINeveryone, supervisors included, may self-enrol one at the kiosk (AdminEnrolled=false). It attributes scans. The old supervisor self-enrol block is gone.
  • Override authority — conferred only by an admin enrolment (/Gate/AdminAdminEnrolled=true). AuthorizeOverrideAsync now requires AdminEnrolled=true and the correct PIN and a live supervisor role.

So an attacker cold-setting a supervisor's PIN at the anonymous kiosk gains attribution spoofing only (already possible for any staffer) — never override power. This closes the hole the old block existed for, while unblocking self-enrolment for all. (A peer reviewer initially argued the decouple was unnecessary; I disagreed and a security review confirmed the decouple is what actually closes the hole.)

⚠️ DEPLOY RUNBOOK STEP — required before doors open

The migration backfills all existing rows to AdminEnrolled=false (fail-safe — no one silently keeps override). This means every already-enrolled supervisor loses override authority on deploy until an admin re-enrols them. A blanket backfill to true was rejected by both reviews (fail-open — it would re-grant override to any pre-existing self-set PIN, defeating the feature) and a targeted backfill can't run (the supervisor set lives in another section — a cross-section reach forbidden in a migration).

→ After deploying this, an admin must re-set each gate lead's PIN via /Gate/Admin before the gate opens, or overrides will not work. (This is the same action needed to set up gate leads anyway.)

Reviews

  • Security review: sound, no blocker — flag checked before/independently of PIN+role; persists on insert/update/merge; no self-set PIN can reach override; tests prove both directions.
  • EF migration review: fixed a HasDefaultValue(false) bool-sentinel (this repo's known anti-pattern); migration regenerated clean (AddColumn defaultValue:false backfill, clean Down, snapshot consistent).

Verification

Build clean · 122 gate + 4 web tests green · dotnet format clean. Live-verified on the tablet: a supervisor (Dev Admin) now reaches the normal Set flow instead of the old "blocked" screen; the migration applied cleanly to a real Postgres.

Needs your sign-off, Peter (new auth surface + migration). Not for merge without review.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the db PR includes EF Core migration label Jul 1, 2026
Everyone — supervisors included — may now self-enrol a CLAIM PIN at the
kiosk (attribution only); the old supervisor self-enrol block is removed.
Override authority is conferred solely by an admin enrolment: a new
GateStaffPin.AdminEnrolled flag (migration, default false), set true only
by AdminSetPinAsync (/Gate/Admin). AuthorizeOverrideAsync now requires
AdminEnrolled=true AND the correct PIN AND a live supervisor role — so an
attacker cold-setting a supervisor's PIN at the anonymous kiosk gains
attribution spoofing only (already possible for any staffer), never
override power.

Removes the now-dead BlockedSupervisor mode + SupervisorMustBeAdminEnrolled
result + their view/CSS. Persists AdminEnrolled on insert/update/merge.

DEPLOY: the migration backfills existing rows to false (fail-safe), so an
admin must re-enrol each supervisor's PIN via /Gate/Admin before doors open
or overrides won't work. A blanket-true backfill was rejected (fail-open).

Security-reviewed (sound, no blocker); EF-migration-reviewed (fixed a bool
HasDefaultValue sentinel; regenerated clean). 122 gate tests green.
Live-verified: a supervisor now reaches the Set flow, not the block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@peterdrier peterdrier force-pushed the fix/gate-supervisor-pin branch from eb1df0b to 0ce3233 Compare July 1, 2026 07:45
@peterdrier peterdrier merged commit 774f6ab into peterdrier:main Jul 1, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

db PR includes EF Core migration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants