diff --git a/lib/actions/door.ts b/lib/actions/door.ts index 8fd43a9..d558392 100644 --- a/lib/actions/door.ts +++ b/lib/actions/door.ts @@ -282,7 +282,7 @@ export async function testDoorConnection(input: unknown) { revalidatePath('/door/manage') return result.ok ? { data: { ok: true, status: result.status } } - : { error: result.reason } + : { error: 'The door controller call failed. Check the door audit log for the redacted detail.' } } export async function listDoorAccessLog() { @@ -511,7 +511,7 @@ export async function grantCard(input: unknown) { targetMemberId, action: 'grant', success: false, detail: `failed: ${result.reason}`, password, }) - return { error: result.reason } + return { error: 'The door controller call failed. Check the door audit log for the redacted detail.' } } await auditDoor(admin, { @@ -590,7 +590,7 @@ export async function revokeCard(input: unknown) { targetMemberId, action: 'revoke', success: false, detail: `failed (slot ${slot} kept): ${result.reason}`, password, }) - return { error: result.reason } + return { error: 'The door controller call failed. Check the door audit log for the redacted detail.' } } await admin.from('door_card_slots').delete().eq('connection_id', connectionId).eq('card_id', cardId) @@ -688,7 +688,7 @@ export async function doorControl(input: unknown) { }) revalidatePath('/door/manage') - return result.ok ? { data: { ok: true, status: result.status } } : { error: result.reason } + return result.ok ? { data: { ok: true, status: result.status } } : { error: 'The door controller call failed. Check the door audit log for the redacted detail.' } } // ─── Member self-entry ("buzz me in") ──────────────────────────────────────── @@ -799,7 +799,7 @@ export async function selfEntry(input: unknown) { }) revalidatePath('/dashboard') - return result.ok ? { data: { ok: true } } : { error: result.reason } + return result.ok ? { data: { ok: true } } : { error: 'The door controller call failed. Check the door audit log for the redacted detail.' } } // The signed-in member's OWN door activity (rows where they were the actor or diff --git a/lib/door-logic.ts b/lib/door-logic.ts index ee19900..7f75e7b 100644 --- a/lib/door-logic.ts +++ b/lib/door-logic.ts @@ -211,11 +211,25 @@ export function applyTemplate( // the shared door password: any literal occurrence, and any e=/&e= query // value (the firmware auth param), become "". The password is a // credential and must never land in door_access_log. -export function redactDoorSecrets(text: string, password?: string | null): string { +export function redactDoorSecrets( + text: string, + password?: string | null, + authParam?: string | null, +): string { let out = text if (password && password.length > 0) { out = out.split(password).join('') } + // The connection's actual auth query param (generic adapters may name it + // anything, not just e=/pw=). Scrub its value regardless of the name so a + // verbose upstream error echoing the URL cannot leak the secret even when + // `password` is empty/unknown. + if (authParam && /^\w+$/.test(authParam)) { + out = out.replace( + new RegExp(`([?&]${authParam}=)[^&\\s"']*`, 'gi'), + '$1', + ) + } out = out.replace(/([?&](?:e|pw|password|api[_-]?key|token)=)[^&\s"']*/gi, '$1') return out } diff --git a/lib/door/executor.ts b/lib/door/executor.ts index 756d82a..e63f5cf 100644 --- a/lib/door/executor.ts +++ b/lib/door/executor.ts @@ -18,6 +18,7 @@ export async function callDoor(opts: { url: string pinnedHost: string password?: string | null + authParam?: string | null timeoutMs?: number }): Promise { const guard = validateDoorTarget(opts.url, opts.pinnedHost) @@ -57,11 +58,11 @@ export async function callDoor(opts: { /* ignore */ } } - const snippet = redactDoorSecrets(received.slice(0, SNIPPET), opts.password) + const snippet = redactDoorSecrets(received.slice(0, SNIPPET), opts.password, opts.authParam) return { ok: res.ok, status: res.status, snippet } } catch (e) { const msg = e instanceof Error ? e.message : 'request failed' - return { ok: false, reason: redactDoorSecrets(msg, opts.password) } + return { ok: false, reason: redactDoorSecrets(msg, opts.password, opts.authParam) } } finally { clearTimeout(timer) }