fix: passkeys behind TLS reverse proxy (#225)
* fix: passkeys behind TLS reverse proxy Add passkeyPublicOrigin and wire it through passkey routes so origin/rpId match the browser when dev runs behind nginx. Expose dev-only /_emdash/api/dev/passkey-url, add admin messaging for insecure WebAuthn contexts, nginx repro under demos/simple, and direct kysely dependency for the simple demo Node adapter bundle. Made-with: Cursor * docs: add passkeyPublicOrigin to configuration reference Adds the new passkeyPublicOrigin option and reverse proxy guidance to the public-facing configuration docs as requested in PR review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update tests and more docs * fix: add missing refresh-server-pat fixture and restore docs heading --------- Co-authored-by: Joseph Eftekhari <jdeftekhari@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
24
e2e/fixtures/refresh-server-pat.ts
Normal file
24
e2e/fixtures/refresh-server-pat.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Re-runs dev-bypass after a dev-reset so the server info file has a valid PAT
|
||||
* and the fixture database is back in "setup complete" state.
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const SERVER_INFO_PATH = join(tmpdir(), "emdash-pw-server.json");
|
||||
|
||||
export async function refreshServerPatAfterDevBypass(baseUrl: string): Promise<void> {
|
||||
const res = await fetch(`${baseUrl}/_emdash/api/setup/dev-bypass?token=1`);
|
||||
if (!res.ok) {
|
||||
throw new Error(`dev-bypass failed (${res.status}): ${await res.text()}`);
|
||||
}
|
||||
const json: { data: { token?: string } } = await res.json();
|
||||
const token = json.data.token;
|
||||
if (!token) throw new Error("dev-bypass did not return a PAT token");
|
||||
|
||||
// Update the server info so subsequent tests use the fresh token
|
||||
const info = JSON.parse(readFileSync(SERVER_INFO_PATH, "utf-8"));
|
||||
info.token = token;
|
||||
writeFileSync(SERVER_INFO_PATH, JSON.stringify(info, null, 2));
|
||||
}
|
||||
33
e2e/fixtures/virtual-authenticator.ts
Normal file
33
e2e/fixtures/virtual-authenticator.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Chrome DevTools virtual WebAuthn authenticator for passkey e2e.
|
||||
* Chromium-only (CDP). See https://developer.chrome.com/docs/devtools/webauthn/
|
||||
*/
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
export async function addVirtualWebAuthnAuthenticator(page: Page): Promise<() => Promise<void>> {
|
||||
const session = await page.context().newCDPSession(page);
|
||||
await session.send("WebAuthn.enable");
|
||||
const { authenticatorId } = await session.send("WebAuthn.addVirtualAuthenticator", {
|
||||
options: {
|
||||
protocol: "ctap2",
|
||||
transport: "internal",
|
||||
hasResidentKey: true,
|
||||
hasUserVerification: true,
|
||||
isUserVerified: true,
|
||||
automaticPresenceSimulation: true,
|
||||
},
|
||||
});
|
||||
|
||||
return async () => {
|
||||
try {
|
||||
await session.send("WebAuthn.removeVirtualAuthenticator", { authenticatorId });
|
||||
} catch {
|
||||
// session may already be closed
|
||||
}
|
||||
try {
|
||||
await session.detach();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user