111 lines
3.8 KiB
Markdown
111 lines
3.8 KiB
Markdown
# L484 / Sapien Membership App
|
|
|
|
Vue frontend with a small Node backend for private membership signup, Nostr sign-in, admin approvals, BTCPay invoices, encrypted local member-card export/import, and NFC card access scaffolding.
|
|
|
|
## Development
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
The dev script runs the API and Vite with `--host`. It also loads `.env.local` and seeds development members when `DEV_SEED_MEMBERS=true`.
|
|
|
|
## Production / Portainer
|
|
|
|
The app listens on port `2354` in the provided compose files.
|
|
|
|
Required production environment:
|
|
|
|
```bash
|
|
PORT=2354
|
|
HOST=0.0.0.0
|
|
APP_MODE=all
|
|
ADMIN_ALLOWED_HOSTS=admin.local,l484.local
|
|
MEMBERSHIP_ENCRYPTION_KEY=<32+ random bytes>
|
|
ACCESS_HMAC_KEY=<32+ random bytes>
|
|
ACCESS_CONTROLLER_TOKEN=<random controller token>
|
|
HOME_ASSISTANT_UNLOCK_WEBHOOK_URL=<local HA webhook URL, optional>
|
|
HOME_ASSISTANT_UNLOCK_TIMEOUT_MS=2500
|
|
BTCPAY_SERVER_URL=https://your-btcpay-host
|
|
BTCPAY_STORE_ID=<store id>
|
|
BTCPAY_API_KEY=<api key>
|
|
BTCPAY_WEBHOOK_SECRET=<webhook secret>
|
|
DEV_SEED_MEMBERS=false
|
|
```
|
|
|
|
Keep `server/data` on a persistent volume. Do not deploy `.env.local`.
|
|
|
|
The admin UI, admin APIs, and controller card-scan endpoint are available only when the request comes from localhost, a private LAN IP, a `.local` hostname, or a hostname listed in `ADMIN_ALLOWED_HOSTS`. Public members can still use `/api/member/door/unlock` from the external site when their local membership secret verifies an active paid membership.
|
|
|
|
## BTCPay
|
|
|
|
Create a BTCPay webhook pointing at:
|
|
|
|
```text
|
|
https://your-domain/api/btcpay/webhook
|
|
```
|
|
|
|
Use the same webhook secret as `BTCPAY_WEBHOOK_SECRET`.
|
|
|
|
The admin payment modal opens a live server-sent event stream at `/api/admin/events`. When BTCPay calls the webhook, the backend marks the invoice paid and pushes a `payment-paid` event to open admin sessions. The status endpoint remains available for explicit refresh/error recovery.
|
|
|
|
## NFC Door Readiness
|
|
|
|
The planned lock is a Kwikset Home Connect 918. It unlocks over Z-Wave, so the NFC reader does not directly control the lock. The current plan is:
|
|
|
|
```text
|
|
PN532 -> ESP32 -> L484 backend allow/deny -> Home Assistant/Z-Wave JS -> Zooz ZST39 LR -> Kwikset 918
|
|
```
|
|
|
|
Full hardware and wiring plan: [docs/nfc-door.md](docs/nfc-door.md).
|
|
|
|
ESP32 firmware scaffold: [firmware/esp32-pn532-door](firmware/esp32-pn532-door).
|
|
|
|
The controller-facing endpoint is:
|
|
|
|
```http
|
|
POST /api/access/check
|
|
X-Controller-Token: <ACCESS_CONTROLLER_TOKEN>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"doorId": "front-door",
|
|
"cardCredential": "scanned-card-secret-or-uid"
|
|
}
|
|
```
|
|
|
|
The backend stores only HMACs of card credentials using `ACCESS_HMAC_KEY`. Access is allowed only when:
|
|
|
|
- the scanned card hash matches an active card,
|
|
- the member exists,
|
|
- the member access status resolves to `active`.
|
|
|
|
The response:
|
|
|
|
```json
|
|
{
|
|
"allow": true,
|
|
"reason": "active_member_card",
|
|
"member": {
|
|
"membershipId": "L484-2026-XXXXXX",
|
|
"fullName": "Member Name",
|
|
"status": "active"
|
|
}
|
|
}
|
|
```
|
|
|
|
Every access check is logged in `server/data/access-logs.json`.
|
|
|
|
If `HOME_ASSISTANT_UNLOCK_WEBHOOK_URL` is configured, an approved controller request also calls that local webhook so Home Assistant/Z-Wave JS can unlock the Kwikset 918 through the Zooz stick. Admin mock scans do not trigger unlocks.
|
|
|
|
## Security Notes
|
|
|
|
- The generated user `nsec` is shown once in the browser and is not sent to the backend.
|
|
- Member records are encrypted before being saved in `server/data/memberships.json`.
|
|
- NFC card credentials are never stored raw.
|
|
- Admin APIs require an authorized Nostr public key. Set `MASTER_ADMIN_PUBKEY` to the master admin `npub`; only that key can approve or reject other admin keys.
|
|
- Controller APIs require `ACCESS_CONTROLLER_TOKEN`.
|
|
- Mutating API routes have basic rate limiting.
|
|
- Rotate any development BTCPay credentials before production.
|