M2-T02: add session/auth JSON API for the SPA

- GET /api/session (user + csrf_token, 401 when unauthenticated)
- POST /api/auth/login (sets HttpOnly session cookie; 401 on bad creds; no CSRF)
- POST /api/auth/logout (session+CSRF; revokes session, clears cookie; 204)
- POST /api/auth/password (session+CSRF; reuses change_password; 400 on failure; 204)
- reuses app/services/auth.py and shared require_session/require_csrf deps
- register router in app/main.py; regenerate openapi/
- tests/test_api_session.py
This commit is contained in:
2026-06-12 23:15:56 +02:00
parent de77019ce3
commit 8da1f13e60
6 changed files with 943 additions and 0 deletions
+246
View File
@@ -350,6 +350,176 @@
}
}
},
"/api/session": {
"get": {
"tags": [
"api-session"
],
"summary": "Get Session",
"description": "Return the current session user and CSRF token. Returns 401 if not authenticated.",
"operationId": "get_session_api_session_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionResponse"
}
}
}
}
}
}
},
"/api/auth/login": {
"post": {
"tags": [
"api-session"
],
"summary": "Post Login",
"description": "Authenticate with username and password.\n\nOn success, sets an HttpOnly session cookie and returns the session user + CSRF token.\nOn failure, returns 401 with no cookie set.\nNo X-CSRF-Token required (unauthenticated endpoint).",
"operationId": "post_login_api_auth_login_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/auth/logout": {
"post": {
"tags": [
"api-session"
],
"summary": "Post Logout",
"description": "Revoke the current session and clear the session cookie.\nRequires authentication and X-CSRF-Token header.\nReturns 204 No Content.",
"operationId": "post_logout_api_auth_logout_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": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/auth/password": {
"post": {
"tags": [
"api-session"
],
"summary": "Post Change Password",
"description": "Change the current user's password.\nRequires authentication and X-CSRF-Token header.\nOn AuthPasswordChangeError returns 400 with a generic message.\nOn success, force_password_change becomes False (handled by the service).\nReturns 204 No Content.",
"operationId": "post_change_password_api_auth_password_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/PasswordChangeRequest"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/homeassistant/publish": {
"post": {
"tags": [
@@ -673,6 +843,47 @@
"type": "object",
"title": "HTTPValidationError"
},
"LoginRequest": {
"properties": {
"username": {
"type": "string",
"title": "Username"
},
"password": {
"type": "string",
"title": "Password"
}
},
"type": "object",
"required": [
"username",
"password"
],
"title": "LoginRequest"
},
"PasswordChangeRequest": {
"properties": {
"current_password": {
"type": "string",
"title": "Current Password"
},
"new_password": {
"type": "string",
"title": "New Password"
},
"confirm_password": {
"type": "string",
"title": "Confirm Password"
}
},
"type": "object",
"required": [
"current_password",
"new_password",
"confirm_password"
],
"title": "PasswordChangeRequest"
},
"PublicIPCheckResponse": {
"properties": {
"status": {
@@ -703,6 +914,41 @@
],
"title": "PublicIPCheckResponse"
},
"SessionResponse": {
"properties": {
"user": {
"$ref": "#/components/schemas/SessionUser"
},
"csrf_token": {
"type": "string",
"title": "Csrf Token"
}
},
"type": "object",
"required": [
"user",
"csrf_token"
],
"title": "SessionResponse"
},
"SessionUser": {
"properties": {
"username": {
"type": "string",
"title": "Username"
},
"force_password_change": {
"type": "boolean",
"title": "Force Password Change"
}
},
"type": "object",
"required": [
"username",
"force_password_change"
],
"title": "SessionUser"
},
"StatusResponse": {
"properties": {
"status": {