feat(auth): Admin-Selbstanmeldung in die App (Login statt verlorenem Zugangslink)
Die App-Oberflaeche (/) ist bewusst nur per persoenlichem Zugangslink erreichbar (kein SSO, damit Senioren keine Login-Maske sehen). Ein Admin, der seinen Link verliert, kam bisher nicht mehr in die App-UI. Neuer Endpunkt GET /api/admin/login: liegt hinter Authelia (nginx-Location /api/admin/ mit auth_request + error_page-Redirect, Remote-User durchgereicht), erkennt den SSO-Admin, mintet ein frisches Capability-Token, setzt es als va_token-Cookie und leitet in die App (/). Damit meldet sich der Admin allein mit seinem Authelia-Passwort an. Jeder Aufruf rotiert den Token (alte Links dieses Admins werden ungueltig) - fuer eine Recovery-Funktion korrekt. Die huebsche URL https://voice.jamulix.de/admin-login wird per nginx-Alias auf diesen Endpunkt gelegt (nginx-Config liegt ausserhalb des Repos). Doku: BEDIENUNGSANLEITUNG.md § 7.6 - Selbstanmeldung, Recovery per ADMIN_API_KEY (SSO-unabhaengig) und Haertung (zweiter Admin in ADMIN_USERS, Key im Passwortmanager). Tests: Cookie wird gesetzt + 303 -> /, gesetzter Token authentifiziert, Token-Rotation, Nicht-Admin/ohne Identitaet -> 403. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1b1e1bb1d5
commit
b4111c887e
3 changed files with 169 additions and 3 deletions
|
|
@ -6,7 +6,7 @@ import httpx
|
|||
|
||||
import yaml
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.admin_llm import (
|
||||
|
|
@ -16,7 +16,7 @@ from app.admin_llm import (
|
|||
switch_backend,
|
||||
)
|
||||
from app.audit import log_admin_action
|
||||
from app.auth import is_admin_user, require_admin, require_admin_or_user
|
||||
from app.auth import CAPABILITY_COOKIE, is_admin_user, require_admin, require_admin_or_user
|
||||
from app.config import settings
|
||||
from app.dependencies import get_store
|
||||
from app.runtime_config import RUNTIME_SETTABLE, invalidate_cache, runtime_settings
|
||||
|
|
@ -66,6 +66,34 @@ def _current_user_or_none(request: Request):
|
|||
return authenticate(request.headers, client_host, token)
|
||||
|
||||
|
||||
@router.get("/admin/login")
|
||||
async def admin_app_login(request: Request):
|
||||
"""Admin-Selbstanmeldung in die App — Ersatz fuer einen verlorenen Zugangslink.
|
||||
|
||||
Liegt hinter Authelia (nginx: Location /api/admin/ bzw. /admin-login mit
|
||||
auth_request + error_page-Redirect). Erkennt den SSO-Admin via Remote-User,
|
||||
mintet ein frisches Capability-Token, setzt es als va_token-Cookie und leitet
|
||||
in die App (/). Damit meldet sich der Admin allein mit seinem Authelia-Passwort
|
||||
an, ohne persoenlichen Link.
|
||||
|
||||
Jeder Aufruf rotiert das Token (alte Links DIESES Admin-Nutzers werden
|
||||
ungueltig) — fuer eine Recovery-Funktion korrekt und ein Sicherheitsplus:
|
||||
jede SSO-Anmeldung erzeugt eine frische Capability.
|
||||
"""
|
||||
user = _current_user_or_none(request)
|
||||
if not is_admin_user(user):
|
||||
raise HTTPException(status_code=403, detail="Admin privileges required")
|
||||
result = get_store().reset_token(user.id)
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Admin-Nutzer nicht gefunden.")
|
||||
_, raw_token = result
|
||||
log_admin_action(request, "admin_app_login", user_id=user.id)
|
||||
resp = RedirectResponse("/", status_code=303)
|
||||
resp.set_cookie(CAPABILITY_COOKIE, raw_token, httponly=True, secure=True,
|
||||
samesite="lax", max_age=31536000)
|
||||
return resp
|
||||
|
||||
|
||||
@router.post("/admin/users", response_model=UserCreated, dependencies=[Depends(require_admin)])
|
||||
async def create_user(payload: UserCreate):
|
||||
"""Legt einen Nutzer an und gibt das Bearer-Token EINMALIG zurueck."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue