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": {
+178
View File
@@ -222,6 +222,129 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/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.
On success, sets an HttpOnly session cookie and returns the session user +
CSRF token.
On failure, returns 401 with no cookie set.
No 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.
Requires authentication and X-CSRF-Token header.
Returns 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.
Requires authentication and X-CSRF-Token header.
On AuthPasswordChangeError returns 400 with a generic message.
On success, force_password_change becomes False (handled by the service).
Returns 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:
@@ -443,6 +566,36 @@ components:
title: Detail
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:
@@ -466,6 +619,31 @@ components:
- checked_at
- changed
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: