feat(access): access-control gate — npub-QR badge + PIN + dev bypass (ADR-003)

Squashed skeleton (was 11 commits on feat/access-control-skeleton) for a
clean rebase onto dev. Adds a `locked` gate the terminal boots into until a
credential is presented; opt-in and non-breaking (defaults off → boots
straight to idle as before).

- state-machine: `locked` state + ACCESS_GRANTED/ACCESS_DENIED/DEV_UNLOCK
  events + accessBypass/devUnlockAllowed guards (packages/state-machine).
- services/access: reader abstraction, npub+PIN authorize() (nostr-tools
  nip19; accepts nostr:/nprofile), camera npub-QR reader, mock reader.
- LockedView.vue + ColorModeToggle: branded viewfinder, PIN pad, denied
  reason, dev-unlock; camera off-by-default + idle return.
- store/main/electron.d.ts: seed gate config, grant/deny/devUnlock wiring,
  access.json provisioning (no rebuild), get-config surface.
- deploy: access.example.json + provision-access.sh; ADR-003.

Credential union is npub today; UID (NFC tap) is the next step.
This commit is contained in:
Patrick Mulligan 2026-08-06 22:16:56 +02:00 • committed by Padreug
commit a7b409b109
20 changed files with 1534 additions and 28 deletions

View file

@ -93,3 +93,29 @@ VITE_SPIRE_SEED=
# Set to 'true' for development/demo environments only
# When false (production default), initialization failures show a maintenance screen
# VITE_ALLOW_MOCK_FALLBACK=true
# =============================================================================
# Access Control (ADR-003)
# =============================================================================
# Badge-to-enter gate. When disabled (default), the machine boots straight to
# idle exactly as before. When enabled, it boots into a locked screen and
# requires a credential (prototype: an npub QR scanned by the camera, with an
# optional PIN) before transactions are reachable.
# ACCESS_CONTROL_ENABLED=true
# Prototype posture: admit ANY valid npub when the allow-list has no match.
# Turn OFF once a real allow-list (/var/lib/bitspire/access.json) is provisioned.
# ACCESS_OPEN_ENROLLMENT=true
# Allow the on-screen runtime dev/operator unlock button (default: allowed when
# the gate is on). Set to 'false' to hide it on a locked-down deployment.
# ACCESS_DEV_UNLOCK=false
# Per-machine salt for hashing credentials/PINs. Provision a real value in
# production (or in access.json); a fixed default is used if unset.
# ACCESS_SALT=change-me-per-machine
# Build/dev bypass — forces the gate OPEN even when enabled (browser dev / CI).
# Renderer-side (Vite) flag, never set in a production image.
# VITE_SKIP_ACCESS_GATE=true