Files
hackerspace.sh/content/docs/reference/access-control.md
T
Moheeb Zara 16229ae523 feat(docs): add a Diátaxis documentation section to the public site
A new public /docs area covering the whole platform, organized by Diátaxis:
tutorials (learning), how-to guides (tasks), reference (facts), and explanation
(the why). 29 pages, each grounded in the actual routes, actions, schema, and
UI — verified against the code and corrected for accuracy (e.g. admin-only vs
admin+board gating, where the permissions matrix actually lives, how Stripe
reconciliation matches).

Implementation:
- lib/docs/nav.ts is the single source of truth (categories + pages), driving
  the sidebar, static params, and prev/next.
- Content authored as content/docs/<category>/<slug>.md, read at build time
  (the routes are statically generated), rendered by <DocsMarkdown> — reuses the
  existing react-markdown/GFM stack, no new dependencies.
- (docs) route group with its own theme (docs.css) matching the public site's
  dark / monospace / lime aesthetic; sidebar with active-state, breadcrumb,
  prev/next, and a Diátaxis home. Public — signed-in users can read it too.
- "Docs" link added to the landing nav.

typecheck 0 · lint 0 errors · build ok (all /docs pages prerendered)
2026-08-05 00:42:42 -07:00

5.4 KiB
Raw Blame History

Physical access lives in the door module: member cards, per-door connections to a controller, the slot map that keys cards into a controller, inbound event ingest, and an append-only access log. This page is the exhaustive reference for those tables, their fields, and the permissions that gate them. To set a door up step by step, see Connect a door.

Permissions

Two access permission codes gate the whole module, plus one for API buttons. All are additive and assignable to any role; the seed grants them to board, and admin holds them implicitly. See Roles and permissions.

Code Group Grants
door.manage Access Configure connections and API buttons; manage member cards; view the access log
door.operate Access Live actions: open/unlock/lock, grant/revoke a card; view the access log
apicall.invoke Access Default required permission to press an API button

Managers configure at /door/manage and /door/buttons. Members see their own cards, self-entry doors, buttons they may press, and their recent activity at /doors; the dashboard also surfaces self-entry.

Member cards

member_cards maps a physical card UID to a member. The raw UID is treated as a credential: only door.manage holders can read the table, and a member's own view returns a per-card list (card_type, label, is_active, and the last four characters of the UID, rendered ••••ABCD) — never more than the last four of the UID.

Field Type Notes
card_uid text The credential. Unique per (space_id, card_uid)
card_type text rfid or nfc (default rfid)
label text Optional operator label
is_active boolean Inactive cards cannot be granted

Door connections

A door_connections row is one configured integration to a physical controller.

Field Values Notes
adapter native_heatsync, generic_http HeatSync/23b uses built-in query-string verbs; generic uses per-verb templates
base_url must start with http:///https:// Controller or reachable proxy
pinned_host hostname/IP SSRF pin: the executor calls only this host, blocks cloud-metadata/link-local, and forbids redirects
auth_mode none, query, header, bearer With auth_param
secret_ref secrets vault id Outbound door password; decrypted server-side only, never returned
verbs jsonb Generic per-verb templates with {slot}, {tag}, {perm}, {door}, {pw}
allow_member_self_entry boolean Opt-in per connection; off by default
is_enabled boolean Disabled connections reject all calls
inbound_enabled boolean Gates the inbound webhook
inbound_secret_ref secrets vault id Inbound webhook bearer, distinct from the door password

Native HeatSync encodes fixed-width query strings: grant ?m<slot>&p<perm>&t<tag>&e=<pw>, revoke ?r<slot>&e=<pw>, and control verbs ?o1 (open), ?u (unlock), ?l (lock), ?9 (status), ?z (log).

Card slots

door_card_slots is the platform's allocation map. Controllers key cards by an integer slot (HeatSync range 0–200), scoped per connection.

  • UNIQUE (connection_id, slot) arbitrates concurrent grants racing for a slot.
  • UNIQUE (connection_id, card_id) makes re-granting idempotent.
  • The lowest-free-slot policy lives in unit-tested logic, not SQL. Only the service-client executor writes rows, in lockstep with the controller call.

Inbound ingest

Two transports pull entry/denied events into the log and share one core. Ingested rows carry door_access_log.dedupe_key (partial-unique per connection), so retries and re-polls insert once via ON CONFLICT DO NOTHING.

  • Poll: a CRON_SECRET-guarded route reads each enabled native_heatsync connection's ?z log through the hardened executor.
  • Webhook: a per-connection public endpoint accepts normalized event JSON, authenticated by the inbound bearer secret. Used for generic controllers and relays.

Access log

door_access_log is an append-only, immutable audit of every attempt. Secrets are redacted before write, and only the validated service-client executor inserts rows (no client write policy). door.manage or door.operate may read it.

Field Notes
action grant, revoke, open, unlock, lock, test, self_entry, plus ingested events
success Attempt outcome
actor_member_id / target_member_id Who acted / whose card
detail Redacted status snippet
dedupe_key Set only on ingested rows

API buttons

api_buttons are named admin-defined HTTP calls fired through the same hardened egress as the door executor.

Field Values Notes
label / button_group text Grouped and ordered by sort_order
method GET, POST, PUT, PATCH, DELETE Default POST
base_url / pinned_host — Per-button SSRF pin
url_template / body_template / headers — With secret_ref injected server-side
required_permission catalog code Default apicall.invoke; a member sees only buttons whose permission they hold
confirm boolean Prompt before firing (default true)

Managing buttons requires door.manage. Every press writes one redacted row to api_call_log, which mirrors the access log's immutable, door.manage-read posture.