fix(auth): confirm passkey operations and preserve safe legacy migration
Fence displayed identity and native prompt generations, classify secret-free outcomes, recover atomic addition receipts without restoring old credentials, and expose truthful own-key verification metadata. Preserve required verifier and enrollment authority boundaries. Include real-store, production browser, native CTAP2 and visible responsive regressions. Validation: root check/fmt; API 115; passkey security 25; Web unit 712/component 605; auth/entry/activation/hosting browsers; product auth E2E 8 and two explicit browser suites. Server suite: 1262 lib+12 CLI, 3 ignored and 2 environment-specific exclusions. State-name checker retains 15 byte-identical baseline findings; no new exceptions. Physical keys/mobile and hosted CI remain unverified.
This commit is contained in:
@@ -19,7 +19,7 @@ const mime: Record<string, string> = {
|
||||
};
|
||||
const policies = new Map<string, boolean>();
|
||||
// 各fixture processはscenarioを逐次実行する。setupのBearer-only要求はcookieを
|
||||
// 送らないので、秘密やheadersを読まず直前の合成page条件で応答を選ぶ。
|
||||
// 送らないので、Bearer有無と直前の合成page条件で応答を選ぶ。header値は読まない。
|
||||
let setupMode: string | null = null;
|
||||
Deno.serve({ hostname: "127.0.0.1", port: Number(port) }, async (request) => {
|
||||
const url = new URL(request.url);
|
||||
@@ -28,19 +28,27 @@ Deno.serve({ hostname: "127.0.0.1", port: Number(port) }, async (request) => {
|
||||
if (url.pathname === "/hosting/setup" && selected?.startsWith("setup-")) {
|
||||
setupMode = selected;
|
||||
}
|
||||
const tokenRequest = request.headers.has("authorization");
|
||||
const mode = selected ??
|
||||
(tokenRequest ? setupMode : null) ??
|
||||
request.headers.get("cookie")?.match(/ui-fixture=([\w-]+)/)?.[1] ??
|
||||
(url.pathname.startsWith("/api/auth/passkeys/addition/")
|
||||
? setupMode
|
||||
: null) ??
|
||||
"admin-on";
|
||||
if (selected) policies.delete(mode);
|
||||
const setup = setupProjectionResponse(url.pathname, mode, selected !== null);
|
||||
const setup = setupProjectionResponse(
|
||||
url.pathname,
|
||||
mode,
|
||||
selected !== null,
|
||||
tokenRequest,
|
||||
);
|
||||
if (setup) return setup;
|
||||
const anonymous = mode.startsWith("anonymous") || mode.startsWith("account-");
|
||||
const ordinary = mode.startsWith("ordinary");
|
||||
if (url.pathname === "/api/auth/whoami") {
|
||||
return Response.json({
|
||||
authentication_reference: anonymous ? null : "fixture-authentication",
|
||||
session: !anonymous
|
||||
? { kind: "authenticated", expires_at: "2099-01-01T00:00:00Z" }
|
||||
: { kind: "anonymous" },
|
||||
@@ -53,6 +61,17 @@ Deno.serve({ hostname: "127.0.0.1", port: Number(port) }, async (request) => {
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/api/auth/passkeys") {
|
||||
return Response.json({
|
||||
user: {
|
||||
user_id: "fixture-user",
|
||||
account_id: "fixture-account",
|
||||
handle: "alice",
|
||||
display_name: "Alice",
|
||||
},
|
||||
keys: [],
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/api/workspaces") return Response.json([]);
|
||||
if (url.pathname === "/api/hosting/access") {
|
||||
return Response.json({ is_admin: !ordinary && !anonymous });
|
||||
|
||||
@@ -19,6 +19,7 @@ export function setupProjectionResponse(
|
||||
path: string,
|
||||
mode: string,
|
||||
reset: boolean,
|
||||
tokenRequest = false,
|
||||
): Response | null {
|
||||
if (!mode.startsWith("setup-")) return null;
|
||||
if (reset) stages.delete(mode);
|
||||
@@ -28,7 +29,17 @@ export function setupProjectionResponse(
|
||||
});
|
||||
}
|
||||
if (path === "/api/auth/whoami") {
|
||||
if (tokenRequest) {
|
||||
return Response.json({
|
||||
actor: { ...user, auth_method: "api_token" },
|
||||
authentication_reference: "fixture-token-binding",
|
||||
session: { kind: "authenticated", expires_at: "2099-01-01T00:00:00Z" },
|
||||
});
|
||||
}
|
||||
return Response.json({
|
||||
authentication_reference: stages.get(mode) === "login"
|
||||
? "fixture-authentication"
|
||||
: null,
|
||||
session: stages.get(mode) === "login"
|
||||
? { kind: "authenticated", expires_at: "2099-01-01T00:00:00Z" }
|
||||
: { kind: "anonymous" },
|
||||
@@ -56,7 +67,10 @@ export function setupProjectionResponse(
|
||||
}
|
||||
if (path === "/api/auth/passkeys/addition/complete") {
|
||||
stages.set(mode, "enrolled");
|
||||
return Response.json({ user });
|
||||
return Response.json({
|
||||
user,
|
||||
authentication_reference: "fixture-authentication",
|
||||
});
|
||||
}
|
||||
if (path === "/api/auth/passkeys/login/options") {
|
||||
return Response.json({
|
||||
@@ -66,7 +80,10 @@ export function setupProjectionResponse(
|
||||
}
|
||||
if (path === "/api/auth/passkeys/login/complete") {
|
||||
stages.set(mode, "login");
|
||||
return Response.json({ user });
|
||||
return Response.json({
|
||||
user,
|
||||
authentication_reference: "fixture-authentication",
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user