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:
2026-10-26 07:18:23 +09:00
parent b284934e34
commit f17b5212c7
66 changed files with 4716 additions and 532 deletions
+261 -3
View File
@@ -298,12 +298,17 @@
"AuthUserResponse": {
"additionalProperties": false,
"properties": {
"authentication_reference": {
"description": "Comparison evidence for the exact newly issued session, not a credential.",
"type": "string"
},
"user": {
"$ref": "#/components/schemas/AuthenticatedUser"
}
},
"required": [
"user"
"user",
"authentication_reference"
],
"type": "object"
},
@@ -6709,15 +6714,112 @@
},
"PasskeyAdditionOptionsRequest": {
"additionalProperties": false,
"description": "Adds a credential to the current authenticated actor; target identity is never public input.",
"description": "Adds to the current actor. Expected identity/binding fields only fence stale UI;\nthey never select an owner or authorize an addition.",
"properties": {
"browser_origin": {
"type": [
"string",
"null"
]
},
"expected_account_id": {
"type": "string"
},
"expected_authentication_reference": {
"type": "string"
},
"expected_user_id": {
"type": "string"
}
},
"required": [
"expected_user_id",
"expected_account_id",
"expected_authentication_reference"
],
"type": "object"
},
"PasskeyAdditionResultRequest": {
"additionalProperties": false,
"properties": {
"challenge_id": {
"type": "string"
},
"expected_account_id": {
"type": "string"
},
"expected_authentication_reference": {
"description": "Original options authentication reference; the current same-owner session may differ.",
"type": "string"
},
"expected_user_id": {
"type": "string"
}
},
"required": [
"challenge_id",
"expected_user_id",
"expected_account_id",
"expected_authentication_reference"
],
"type": "object"
},
"PasskeyAdditionResultResponse": {
"description": "Unknown is not proof that registration failed: completion may still be in flight.\nSaved proves the credential commit, not session issuance or current session health.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"status": {
"const": "unknown",
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"key": {
"$ref": "#/components/schemas/PasskeySummary"
},
"status": {
"const": "saved",
"type": "string"
},
"user": {
"$ref": "#/components/schemas/AuthenticatedUser"
}
},
"required": [
"status",
"user",
"key"
],
"type": "object"
}
]
},
"PasskeyListResponse": {
"additionalProperties": false,
"properties": {
"keys": {
"items": {
"$ref": "#/components/schemas/PasskeySummary"
},
"type": "array"
},
"user": {
"$ref": "#/components/schemas/AuthenticatedUser"
}
},
"required": [
"user",
"keys"
],
"type": "object"
},
"PasskeyLoginCompleteRequest": {
@@ -6820,6 +6922,35 @@
],
"type": "object"
},
"PasskeySummary": {
"additionalProperties": false,
"description": "Safe account metadata. Neither the WebAuthn credential ID nor key material is exposed.\nNormal-login verification is observed verifier success, not inferred discoverability.",
"properties": {
"created_at": {
"type": "string"
},
"key_reference": {
"type": "string"
},
"last_used_at": {
"type": [
"string",
"null"
]
},
"normal_login_verified_at": {
"type": [
"string",
"null"
]
}
},
"required": [
"key_reference",
"created_at"
],
"type": "object"
},
"PasteArtifactAvailability": {
"enum": [
"available",
@@ -15143,6 +15274,13 @@
}
]
},
"authentication_reference": {
"description": "Non-secret comparison evidence, never an authentication credential.",
"type": [
"string",
"null"
]
},
"session": {
"$ref": "#/components/schemas/BrowserSessionState"
}
@@ -18155,7 +18293,7 @@
"info": {
"title": "Yoi Server API",
"version": "0.1.0",
"x-yoi-source-digest": "sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7"
"x-yoi-source-digest": "sha256:3e33a924668d7f7c07611d45b2fda7f1115c66431b741c94e1ff4e8e3e559b23"
},
"openapi": "3.1.0",
"paths": {
@@ -18589,6 +18727,61 @@
}
}
},
"/api/auth/passkeys": {
"get": {
"operationId": "auth_passkey_list",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasskeyListResponse"
}
}
},
"description": "Successful response"
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RepositoryApiError"
}
}
},
"description": "Error response"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RepositoryApiError"
}
}
},
"description": "Error response"
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RepositoryApiError"
}
}
},
"description": "Error response"
}
},
"security": [
{
"bearerAuth": []
},
{
"browserSession": []
}
]
}
},
"/api/auth/passkeys/addition/complete": {
"post": {
"operationId": "auth_passkey_addition_complete",
@@ -18736,6 +18929,71 @@
]
}
},
"/api/auth/passkeys/addition/result": {
"post": {
"operationId": "auth_passkey_addition_result",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasskeyAdditionResultRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasskeyAdditionResultResponse"
}
}
},
"description": "Successful response"
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RepositoryApiError"
}
}
},
"description": "Error response"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RepositoryApiError"
}
}
},
"description": "Error response"
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RepositoryApiError"
}
}
},
"description": "Error response"
}
},
"security": [
{
"bearerAuth": []
},
{
"browserSession": []
}
]
}
},
"/api/auth/passkeys/login/complete": {
"post": {
"operationId": "auth_passkey_login_complete",