fix: separate account registration from authenticated passkey addition
Bind enrollment purpose and allocated/current account to verifier state. Reject existing-handle registration, derive additions from live transport authentication, recheck revocation/expiry in the insert-only transaction, and consume challenges atomically across connections. Derive stable account WebAuthn handles without rewriting legacy credentials; reject implicit-user login and unsafe legacy ceremonies. Align account callers, DTO/OpenAPI/generated artifacts and migration/recovery rationale. Preserve credential-less legacy identities rather than allowing anonymous reclaim. Validation: 14 actual-verifier passkey tests; full workspace-server 1086 library + 11 binary passed (Drive ignored case explicitly selected separately and passed); server-api default 99 and TypeScript feature 108 passed; isolated auth/Runtime E2E 1+2 passed; Web 645 + component 394 passed; root cargo check, docs/doctests, Server/Runtime builds, fmt, naming and generated consistency passed. Software authenticator only; hosted CI/hardware login not claimed.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// production shellを既存static fixtureから配信し、Account表示用の境界だけを置換する。
|
||||
// 実account/session/credentialは作成せず、queryで撮影条件を固定する。
|
||||
const port = Number(Deno.args[0]);
|
||||
const upstream = Deno.args[1];
|
||||
if (!Number.isInteger(port) || !upstream) {
|
||||
throw new Error("usage: server <port> <upstream>");
|
||||
}
|
||||
Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname === "/health") return new Response("ok");
|
||||
if (url.pathname === "/api/auth/whoami") {
|
||||
return Response.json({
|
||||
actor:
|
||||
request.headers.get("cookie")?.includes("fixture-actor=authenticated")
|
||||
? {
|
||||
user_id: "fixture-user",
|
||||
account_id: "fixture-account",
|
||||
handle: "alice",
|
||||
display_name: "Alice",
|
||||
auth_method: "browser_session",
|
||||
}
|
||||
: null,
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/api/workspaces") return Response.json([]);
|
||||
if (url.pathname.startsWith("/api/")) {
|
||||
return new Response("unexpected fixture API", { status: 404 });
|
||||
}
|
||||
const response = await fetch(new URL(url.pathname + url.search, upstream));
|
||||
const headers = new Headers(response.headers);
|
||||
if (url.pathname === "/account") {
|
||||
headers.set(
|
||||
"set-cookie",
|
||||
`fixture-actor=${
|
||||
url.searchParams.get("actor") === "authenticated"
|
||||
? "authenticated"
|
||||
: "anonymous"
|
||||
}; Path=/; SameSite=Strict`,
|
||||
);
|
||||
}
|
||||
return new Response(response.body, { status: response.status, headers });
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "account-passkeys",
|
||||
"title": "Existing Account registration and self-only passkey addition",
|
||||
"baseUrl": "http://127.0.0.1:15371",
|
||||
"colorScheme": "light",
|
||||
"reducedMotion": "reduce",
|
||||
"personas": [
|
||||
{
|
||||
"id": "synthetic",
|
||||
"label": "Synthetic browser session; no real credentials",
|
||||
"auth": { "kind": "anonymous" }
|
||||
}
|
||||
],
|
||||
"viewports": [
|
||||
{ "label": "desktop", "width": 1440, "height": 900 },
|
||||
{ "label": "narrow", "width": 768, "height": 900 },
|
||||
{ "label": "mobile", "width": 390, "height": 844 }
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"id": "anonymous",
|
||||
"label": "New registration or explicit-handle login",
|
||||
"path": "/account",
|
||||
"goal": "Identify registration as a new-account operation, distinct from logging into an existing handle.",
|
||||
"dataState": "No browser session; existing Account page.",
|
||||
"ready": {
|
||||
"kind": "selector",
|
||||
"selector": ".account-panel:has-text('No browser session is active.')"
|
||||
},
|
||||
"capturePoints": [
|
||||
{ "id": "initial", "label": "Anonymous Account", "fullPage": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "authenticated",
|
||||
"label": "Add only to current user",
|
||||
"path": "/account?actor=authenticated",
|
||||
"goal": "Identify the authenticated actor as the passkey addition target; identity fields cannot retarget addition.",
|
||||
"dataState": "Synthetic passkey actor alice; existing Account page.",
|
||||
"ready": { "kind": "selector", "selector": ".account-details" },
|
||||
"capturePoints": [
|
||||
{ "id": "initial", "label": "Authenticated Account", "fullPage": true }
|
||||
]
|
||||
}
|
||||
],
|
||||
"processes": [
|
||||
{
|
||||
"id": "static-fixture",
|
||||
"command": "deno",
|
||||
"cwd": "../../../../tools/web-ux",
|
||||
"args": [
|
||||
"run",
|
||||
"--allow-net",
|
||||
"--allow-read",
|
||||
"browser-tests/workspace_home_fixture_server.ts",
|
||||
"15372",
|
||||
"../../web/workspace/build"
|
||||
],
|
||||
"readyUrl": "http://127.0.0.1:15372/health"
|
||||
},
|
||||
{
|
||||
"id": "account-fixture",
|
||||
"command": "deno",
|
||||
"cwd": "../..",
|
||||
"args": [
|
||||
"run",
|
||||
"--allow-net",
|
||||
"test/visual-review/account-passkeys-fixture-server.ts",
|
||||
"15371",
|
||||
"http://127.0.0.1:15372"
|
||||
],
|
||||
"readyUrl": "http://127.0.0.1:15371/health"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user