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>
75 lines
1.9 KiB
Python
75 lines
1.9 KiB
Python
from datetime import datetime
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class Door(BaseModel):
|
|
"""A physical door/resource that a tap can open."""
|
|
|
|
id: str
|
|
wallet: str
|
|
name: str
|
|
# Secret the reader (Pi) must present in X-Controller-Token to hit /check.
|
|
controller_token: str
|
|
# Local Home Assistant webhook that fires lock.unlock (LAN-only). When empty,
|
|
# /check still authenticates + authorizes and logs, but performs no unlock
|
|
# (useful for bring-up / bench testing before the lock is wired).
|
|
ha_webhook_url: str
|
|
# boltcards API base used to SUN-verify the tapped card, e.g.
|
|
# http://localhost:5000/boltcards/api/v1 (same on-prem LNbits). Falls back to
|
|
# the instance base URL when empty (see services.verify_card).
|
|
boltcards_base_url: str
|
|
unlock_timeout_ms: int
|
|
enabled: bool
|
|
time: datetime
|
|
|
|
|
|
class CreateDoor(BaseModel):
|
|
name: str
|
|
ha_webhook_url: str = ""
|
|
boltcards_base_url: str = ""
|
|
# Generated server-side when left blank.
|
|
controller_token: str = ""
|
|
unlock_timeout_ms: int = 2500
|
|
enabled: bool = True
|
|
|
|
|
|
class Grant(BaseModel):
|
|
"""A card's permission to open a specific door."""
|
|
|
|
id: str
|
|
door_id: str
|
|
# The Bolt Card's boltcards `external_id` (the authenticated identity).
|
|
external_id: str
|
|
label: str
|
|
enabled: bool
|
|
# Optional hard expiry; null = no expiry.
|
|
expires_at: datetime | None
|
|
time: datetime
|
|
|
|
|
|
class CreateGrant(BaseModel):
|
|
door_id: str
|
|
external_id: str
|
|
label: str = ""
|
|
expires_at: datetime | None = None
|
|
enabled: bool = True
|
|
|
|
|
|
class AccessLog(BaseModel):
|
|
id: str
|
|
door_id: str
|
|
external_id: str
|
|
decision: str # "allow" | "deny"
|
|
reason: str
|
|
ip: str
|
|
time: datetime
|
|
|
|
|
|
class CheckRequest(BaseModel):
|
|
"""What the door reader (Pi) POSTs to /access/api/v1/check."""
|
|
|
|
doorId: str
|
|
external_id: str
|
|
p: str
|
|
c: str
|