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>
70 lines
2.8 KiB
Markdown
70 lines
2.8 KiB
Markdown
# 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
|
|
|
|
```text
|
|
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 → door` permission, `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)
|
|
|
|
```http
|
|
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
|
|
|
|
1. Install on the on-prem LNbits (same instance as `boltcards`).
|
|
2. Create a **door**, set its Home Assistant webhook URL, copy its
|
|
**controller token** to the reader config.
|
|
3. Issue Bolt Cards in `boltcards` as usual; add a **grant** (the card's
|
|
`external_id` → the door).
|
|
4. Point the door reader (Pi) at `/access/api/v1/check` with the token.
|
|
|
|
## Notes
|
|
|
|
- Card authentication delegates to `boltcards` over 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.
|