From 1db93167db22c3e800bd843cfb8fdc7ab60ad1db Mon Sep 17 00:00:00 2001 From: Moheeb Zara Date: Sat, 16 May 2026 20:38:52 -0700 Subject: [PATCH] feat(door): flexible transport + clearer controller/generic API docs The app is cloud-hosted, so a connection's target is not assumed to be LAN-only: any pinned host (public controller/proxy or VPN-reachable LAN device) is allowed except cloud-metadata/link-local. KNOWN_DOOR_ CONTROLLERS surfaces the controller type with source links (HeatSync/23b native -> the heatsynclabs + zyphlar repos; otherwise generic HTTP). The generic adapter form now explains the request model and every {slot}/{tag}/{perm}/{door}/{pw} placeholder. Copy and the migration comment corrected away from "LAN-only". --- app/(app)/door/manage/door-manage-client.tsx | 73 ++++++++++++++++---- lib/door-logic.ts | 38 ++++++++++ scripts/035_door_connections.sql | 6 +- 3 files changed, 101 insertions(+), 16 deletions(-) diff --git a/app/(app)/door/manage/door-manage-client.tsx b/app/(app)/door/manage/door-manage-client.tsx index 36eaf9b..e061447 100644 --- a/app/(app)/door/manage/door-manage-client.tsx +++ b/app/(app)/door/manage/door-manage-client.tsx @@ -14,6 +14,7 @@ import { deleteDoorConnection, testDoorConnection, } from '@/lib/actions' +import { KNOWN_DOOR_CONTROLLERS, controllerForAdapter } from '@/lib/door-logic' type Conn = { id: string @@ -155,9 +156,12 @@ export function DoorManageClient({

- A connection targets a door controller on your LAN. The shared password is read - from your encrypted Secrets vault, never stored here. The server only ever calls - the exact pinned host, never follows redirects, and records every action in the + A connection targets a door controller. Because this app is cloud-hosted, the + target can be a publicly reachable controller or proxy, or a VPN-reachable + device on your LAN. The shared password is read from your encrypted Secrets + vault and never stored here. The server only ever calls the exact pinned host + (any public or private host you set, except cloud-metadata/link-local), never + follows redirects, caps time and response size, and records every action in the access log with secrets redacted.

@@ -167,8 +171,9 @@ export function DoorManageClient({ value={d.name} onChange={e => setD({ ...d, name: e.target.value })} />
setD({ ...d, pinned_host: e.target.value })} />
+ {(() => { + const k = controllerForAdapter(d.adapter) + if (!k) return null + return ( +
+

{k.note}

+ {k.repos.length > 0 && ( +

+ Source:{' '} + {k.repos.map((r, i) => ( + + {i > 0 && ' · '} + {r.label} + + ))} +

+ )} +
+ ) + })()} {d.adapter === 'generic_http' && ( -
- setD({ ...d, grant: e.target.value })} /> - setD({ ...d, revoke: e.target.value })} /> - setD({ ...d, open: e.target.value })} /> - setD({ ...d, status: e.target.value })} /> +
+
+

+ Per-verb request templates. The template is appended to the base URL and + sent as an HTTP GET to the pinned host + (no redirects followed). Placeholders are URL-encoded and substituted: +

+
    +
  • {'{slot}'} — the member’s integer card slot
  • +
  • {'{tag}'} — the card UID / tag value
  • +
  • {'{perm}'} — permission level (default 1)
  • +
  • {'{door}'} — door identifier (for open/lock verbs)
  • +
  • {'{pw}'} — the shared password from your Secrets vault (server-side only; never shown or logged)
  • +
+

+ Leave a verb blank if your controller does not support it. Example + (HeatSync-style): ?m{'{slot}'}&p001&t{'{tag}'}&e={'{pw}'} +

+
+
+ setD({ ...d, grant: e.target.value })} /> + setD({ ...d, revoke: e.target.value })} /> + setD({ ...d, open: e.target.value })} /> + setD({ ...d, status: e.target.value })} /> +
)}