M4-T05: add TOTP service and setup/enable/disable/status API
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -577,6 +577,157 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/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.
|
||||
|
||||
|
||||
The secret is stored in the DB but TOTP is NOT yet enabled (totp_enabled stays
|
||||
|
||||
False until the user confirms with POST /api/auth/totp/enable).
|
||||
|
||||
|
||||
Recovery codes are returned here as plaintext exactly once; their Argon2 hashes
|
||||
|
||||
are persisted immediately so enable only needs to flip the enabled flag.
|
||||
|
||||
|
||||
Repeating this call replaces any prior pending secret and regenerates codes.
|
||||
|
||||
|
||||
Requires: 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.
|
||||
|
||||
|
||||
Requires a prior call to POST /api/auth/totp/setup (so that a pending secret
|
||||
|
||||
exists). On success, totp_enabled becomes True.
|
||||
|
||||
|
||||
Returns 400 if the code is wrong or there is no pending secret.
|
||||
|
||||
Requires: 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:
|
||||
|
||||
- ``password``: the user''s current login password, OR
|
||||
|
||||
- ``code``: the current 6-digit TOTP code.
|
||||
|
||||
|
||||
On success: totp_enabled=False, totp_secret cleared, all recovery codes deleted.
|
||||
|
||||
Returns 400 if neither credential matches or neither is provided.
|
||||
|
||||
Requires: 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.
|
||||
|
||||
|
||||
Response contains only ``{"enabled": bool}``.
|
||||
|
||||
Secret and recovery codes are NEVER returned here.
|
||||
|
||||
Requires: 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:
|
||||
@@ -1079,6 +1230,71 @@ components:
|
||||
required:
|
||||
- status
|
||||
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.
|
||||
|
||||
|
||||
Exactly 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.
|
||||
|
||||
|
||||
``secret`` and ``recovery_codes`` are **one-time plaintext values**.
|
||||
|
||||
They are never returned again by any subsequent API call.
|
||||
|
||||
The 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:
|
||||
|
||||
Reference in New Issue
Block a user