feat(auth): Passwort-Login (1FA) für die App; Admin ebenfalls 1FA

Variante 1: Senioren behalten ihren persönlichen Link; zusätzlich ein
Passwort-Einstieg ohne 2FA über Authelia.

- Neuer Endpunkt GET /api/login (app_login): hinter Authelia (nginx-Location
  /login, one_factor), erkennt den per Remote-User weitergereichten Nutzer
  (Admin ODER normaler Authelia-Nutzer), mintet das va_token-Cookie und leitet
  in die App (/). Cookie-Mint in _issue_login_cookie() gemeinsam mit
  /api/admin/login refaktoriert.
- "Persönlicher Zugang nötig"-Seite (main.py) bekommt einen großen Knopf
  „Mit Benutzername & Passwort anmelden" -> /login. Bare / leitet bewusst NICHT
  automatisch um (Senioren ohne Authelia-Konto würden sonst feststecken).

Begleitende Infra (außerhalb des Repos):
- nginx: location = /login (auth_request one_factor -> /api/login).
- Authelia: access_control-Regel voice.jamulix.de -> one_factor (über der
  *.jamulix.de-Wildcard); damit sind App-Login UND Admin nur noch 1FA, andere
  Subdomains bleiben 2FA.

Tests: /api/login für Nicht-Admin/Admin/ohne-Identität. 257 Offline-Tests grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-26 19:04:37 +02:00
commit 6792e1be86
3 changed files with 63 additions and 21 deletions

View file

@ -75,11 +75,14 @@ _NO_ACCESS_PAGE = """<!doctype html><html lang="de"><head><meta charset="utf-8">
<style>body{font-family:system-ui,-apple-system,sans-serif;background:#f8fafc;color:#1e293b;
display:grid;place-items:center;min-height:100vh;margin:0;padding:1.5rem}
.box{max-width:30rem;text-align:center}h1{font-size:1.3rem;margin:0 0 .75rem}
p{color:#475569;line-height:1.6;margin:.5rem 0}.emoji{font-size:2.5rem}</style></head>
p{color:#475569;line-height:1.6;margin:.5rem 0}.emoji{font-size:2.5rem}
.btn{display:inline-block;margin-top:1.25rem;background:#2563eb;color:#fff;text-decoration:none;
font-weight:600;font-size:1.05rem;padding:.8rem 1.4rem;border-radius:.75rem}</style></head>
<body><div class="box"><div class="emoji">🔒</div>
<h1>Persönlicher Zugang nötig</h1>
<p>Bitte öffne die App über deinen persönlichen Zugangslink.</p>
<p>Wenn du keinen Link hast, wende dich an deine Betreuungsperson.</p>
<p>Senioren: bitte die App über den persönlichen Zugangslink öffnen.</p>
<p>Betreuer und Admins können sich mit Benutzername und Passwort anmelden:</p>
<a class="btn" href="/login">Mit Benutzername &amp; Passwort anmelden</a>
</div></body></html>"""