M4-T05: add TOTP service and setup/enable/disable/status API

This commit is contained in:
2026-06-21 21:55:17 +02:00
parent 5026967cee
commit ee3031013e
10 changed files with 1301 additions and 1 deletions
+262
View File
@@ -795,6 +795,184 @@
}
}
},
"/api/auth/totp/setup": {
"post": {
"tags": [
"api-session"
],
"summary": "Post Totp Setup",
"description": "Generate a new pending TOTP secret, otpauth URI, and one-time recovery codes.\n\nThe secret is stored in the DB but TOTP is NOT yet enabled (totp_enabled stays\nFalse until the user confirms with POST /api/auth/totp/enable).\n\nRecovery codes are returned here as plaintext exactly once; their Argon2 hashes\nare persisted immediately so enable only needs to flip the enabled flag.\n\nRepeating this call replaces any prior pending secret and regenerates codes.\n\nRequires: session cookie + X-CSRF-Token.",
"operationId": "post_totp_setup_api_auth_totp_setup_post",
"parameters": [
{
"name": "X-CSRF-Token",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "X-Csrf-Token"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpSetupResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/auth/totp/enable": {
"post": {
"tags": [
"api-session"
],
"summary": "Post Totp Enable",
"description": "Enable TOTP by confirming with the current 6-digit code from the authenticator app.\n\nRequires a prior call to POST /api/auth/totp/setup (so that a pending secret\nexists). On success, totp_enabled becomes True.\n\nReturns 400 if the code is wrong or there is no pending secret.\nRequires: session cookie + X-CSRF-Token.",
"operationId": "post_totp_enable_api_auth_totp_enable_post",
"parameters": [
{
"name": "X-CSRF-Token",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "X-Csrf-Token"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpEnableRequest"
}
}
}
},
"responses": {
"204": {
"description": "Successful Response"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/auth/totp/disable": {
"post": {
"tags": [
"api-session"
],
"summary": "Post Totp Disable",
"description": "Disable TOTP. The caller must provide exactly one of:\n- ``password``: the user's current login password, OR\n- ``code``: the current 6-digit TOTP code.\n\nOn success: totp_enabled=False, totp_secret cleared, all recovery codes deleted.\nReturns 400 if neither credential matches or neither is provided.\nRequires: session cookie + X-CSRF-Token.",
"operationId": "post_totp_disable_api_auth_totp_disable_post",
"parameters": [
{
"name": "X-CSRF-Token",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "X-Csrf-Token"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpDisableRequest"
}
}
}
},
"responses": {
"204": {
"description": "Successful Response"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/auth/totp": {
"get": {
"tags": [
"api-session"
],
"summary": "Get Totp Status",
"description": "Return the current TOTP status for the authenticated user.\n\nResponse contains only ``{\"enabled\": bool}``.\nSecret and recovery codes are NEVER returned here.\nRequires: session cookie only (no CSRF — read-only).",
"operationId": "get_totp_status_api_auth_totp_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpStatusResponse"
}
}
}
}
}
}
},
"/homeassistant/publish": {
"post": {
"tags": [
@@ -1549,6 +1727,90 @@
],
"title": "StatusResponse"
},
"TotpDisableRequest": {
"properties": {
"password": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Password"
},
"code": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Code"
}
},
"type": "object",
"title": "TotpDisableRequest",
"description": "Disable TOTP by proving identity.\n\nExactly one of ``password`` or ``code`` must be provided."
},
"TotpEnableRequest": {
"properties": {
"code": {
"type": "string",
"title": "Code"
}
},
"type": "object",
"required": [
"code"
],
"title": "TotpEnableRequest",
"description": "The user confirms setup by providing the 6-digit TOTP code."
},
"TotpSetupResponse": {
"properties": {
"secret": {
"type": "string",
"title": "Secret"
},
"otpauth_uri": {
"type": "string",
"title": "Otpauth Uri"
},
"recovery_codes": {
"items": {
"type": "string"
},
"type": "array",
"title": "Recovery Codes"
}
},
"type": "object",
"required": [
"secret",
"otpauth_uri",
"recovery_codes"
],
"title": "TotpSetupResponse",
"description": "Returned once after a setup call.\n\n``secret`` and ``recovery_codes`` are **one-time plaintext values**.\nThey are never returned again by any subsequent API call.\nThe frontend must display and instruct the user to save them before confirming."
},
"TotpStatusResponse": {
"properties": {
"enabled": {
"type": "boolean",
"title": "Enabled"
}
},
"type": "object",
"required": [
"enabled"
],
"title": "TotpStatusResponse",
"description": "Minimal status response — never exposes secret or recovery codes."
},
"ValidationError": {
"properties": {
"loc": {