Promoted from the door-portal scratch repo to its own repo for install via the aiolabs catalog. Authenticates a tapped Bolt Card via boltcards /verify, authorizes against per-door grants, and fires the door's local Home Assistant webhook to unlock a Z-Wave lock. Fails closed; logs every attempt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2.8 KiB
2.8 KiB
Access — LNbits extension
NFC door access for LNbits. A tapped Bolt Card (NTAG424) is authenticated
via the boltcards SUN check, authorized against per-door grants, and — if
allowed — the door's local Home Assistant webhook is fired to unlock a
Z-Wave lock. Every tap is logged.
Runs on the on-prem LNbits on the door's LAN, so the unlock path stays local and the door does not depend on the internet.
Flow
Pi + PN532 reader
→ POST /access/api/v1/check (X-Controller-Token: <door token>)
{ "doorId": "...", "external_id": "...", "p": "...", "c": "..." }
1. authenticate → boltcards /verify (valid, non-replayed SUN?)
2. authorize → active grant for this external_id on this door?
3. actuate → POST door.ha_webhook_url (→ lock.unlock → Z-Wave)
4. log → access.logs (allow/deny + reason)
→ { "allow": true|false, "reason": "..." }
The reader never unlocks directly; this extension owns the allow/deny decision and the Home Assistant call. Fails closed at every step.
Data model
- doors — a resource:
name,controller_token(reader secret),ha_webhook_url(local HA webhook),boltcards_base_url(blank = this instance),unlock_timeout_ms,enabled. - grants —
external_id → doorpermission,enabled/expires_at. - logs — every allow/deny with a reason.
Admin API (wallet admin key)
GET/POST /access/api/v1/doors,PUT/DELETE /access/api/v1/doors/{id}GET/POST /access/api/v1/grants,DELETE /access/api/v1/grants/{id}GET /access/api/v1/logs
Reader API (controller token)
POST /access/api/v1/check
X-Controller-Token: <door.controller_token>
Content-Type: application/json
{ "doorId": "front-door", "external_id": "abc123", "p": "<32-hex>", "c": "<16-hex>" }
Setup
- Install on the on-prem LNbits (same instance as
boltcards). - Create a door, set its Home Assistant webhook URL, copy its controller token to the reader config.
- Issue Bolt Cards in
boltcardsas usual; add a grant (the card'sexternal_id→ the door). - Point the door reader (Pi) at
/access/api/v1/checkwith the token.
Notes
- Card authentication delegates to
boltcardsover loopback HTTP via its/api/v1/verify/{external_id}endpoint (aiolabs fork ≥ 1.1.1-aio.2) — a side-effect-light SUN check that returns{"authenticated": true, …}without/scan's spend semantics (no withdrawRequest, no daily-limit, no "hit"), while still advancing the SUN counter for replay protection. - Requires boltcards 1.1.1-aio.2+ installed on the same LNbits.
- Empty
ha_webhook_url= authorize + log only (bring-up mode; no unlock). - Keep this on the LAN; use a strong per-door controller token; never expose Home Assistant publicly.