feat(web): Remote-Web-UI mit Mikrofon + Forward-Auth (YunoHost-SSO)
- Minimale Web-UI (app/web/, vanilla, same-origin -> kein CORS): Text-Prompt + Mikrofon-Button (Aufnahme im Browser -> /ws/voice -> Antwort wird vorgelesen), Token-Streaming, PCM-Wiedergabe, Identitaet/Logout/Admin im Menue - Forward-/Trusted-Header-Auth (app/auth.py): Identitaet aus SSO-Header, nur von TRUSTED_PROXY_IPS akzeptiert; sonst Token/Anonymous-Fallback. Auto-Provisioning via store.get_or_create_user_by_external_id (+ external_id-Spalte/Migration) - /api/me um is_admin + sso_logout_url erweitert; GET /api/admin/users (Liste) und GET /api/admin/request-headers (SSO-Header-Discovery), Admin-gated - StaticFiles-Mount; Config: TRUSTED_AUTH_HEADER/_PROXY_IPS, ADMIN_USERS, SSO_LOGOUT_URL - WS-Auth liest Identitaet aus dem Handshake-Header - Deploy: nginx-Vorlage (WS-Upgrade!) + deploy/README.md (HTTPS/SSO/Firewall/Discovery) - Tests: Forward-Auth (Provisioning, Admin-Flag, Proxy-IP-Trust, 401/403, Static) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
855fc71a1e
commit
76df695111
16 changed files with 703 additions and 29 deletions
|
|
@ -1,14 +1,41 @@
|
|||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
|
||||
from app.auth import require_admin
|
||||
from app.auth import require_admin, require_admin_or_user
|
||||
from app.dependencies import get_store
|
||||
from app.schemas import UserCreate, UserCreated
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/admin/request-headers", dependencies=[Depends(require_admin_or_user)])
|
||||
async def request_headers(request: Request):
|
||||
"""Discovery: zeigt die eingehenden HTTP-Header + Quell-IP.
|
||||
|
||||
Hilft, hinter dem Reverse-Proxy/SSO den richtigen Identitaets-Header
|
||||
(TRUSTED_AUTH_HEADER) festzustellen. Nur fuer Admins.
|
||||
"""
|
||||
return {
|
||||
"client": request.client.host if request.client else None,
|
||||
"headers": dict(request.headers),
|
||||
}
|
||||
|
||||
|
||||
@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."""
|
||||
user, token = get_store().create_user(payload.display_name)
|
||||
return UserCreated(user_id=user.id, display_name=user.display_name, token=token)
|
||||
|
||||
|
||||
@router.get("/admin/users", dependencies=[Depends(require_admin_or_user)])
|
||||
async def list_users():
|
||||
"""Listet die Nutzer (ohne Secrets). Fuer Admins (SSO/ADMIN_USERS) oder ADMIN_API_KEY."""
|
||||
return [
|
||||
{
|
||||
"user_id": u.id,
|
||||
"display_name": u.display_name,
|
||||
"external_id": u.external_id,
|
||||
"created_at": u.created_at,
|
||||
}
|
||||
for u in get_store().list_users()
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue