5.2 KiB
Design Spec: Benutzerverwaltung Part 1 — Core Auth
Datum: 2026-04-27 Status: Genehmigt
Ziel
Lokale Benutzerverwaltung mit JWT-basierter Session (httpOnly-Cookie) und optionalem LDAP-Fallback. Part 1 implementiert das User-Datenmodell, lokale Passwort-Auth und die FastAPI-Dependencies. Part 2 (separater Spec) ergänzt LDAP-Auth und Celery-Hintergrund-Sync.
Datenmodell
Einzige Tabelle users:
| Feld | SQLAlchemy-Typ | Details |
|---|---|---|
id |
Integer, PK |
Auto-increment |
username |
String(64), unique, not null |
sAMAccountName / lokaler Login |
full_name |
String(128), not null |
Vor- + Nachname |
email |
String(128), nullable |
optional |
department |
String(64), nullable |
aus AD (Part 2) |
role |
String(64), nullable |
aus AD description-Feld (Part 2) |
pw_hash |
String(256), nullable |
None = kein lokaler Login möglich |
is_active |
Boolean, default True |
gesperrte Accounts |
is_admin |
Boolean, default False |
Admin-Rechte |
account_expires |
DateTime, nullable |
aus AD; None = kein Ablauf |
last_login |
DateTime, nullable |
bei jedem Login aktualisiert |
created_at |
DateTime, server_default |
automatisch |
updated_at |
DateTime, onupdate |
automatisch |
pw_hash is not None → lokale Auth verfügbar. Kein separates auth_source-Flag.
Auth-Flow
POST /auth/login (username + password als Form-Daten)
│
├─► Nutzer in DB vorhanden und is_active?
│ └─► Nein → 401
│
├─► pw_hash gesetzt?
│ ├─► Ja → passlib bcrypt.verify(password, pw_hash)
│ │ ├─► OK → Login erfolgreich ✓
│ │ └─► Falsch → weiter zu LDAP (falls aktiviert)
│ └─► Nein → weiter zu LDAP (falls aktiviert)
│
├─► LDAP aktiviert? (settings.LDAP_ENABLED == True, gesetzt wenn APP_ENV != "development")
│ ├─► Ja → ldap3 NTLM-Bind gegen gso1.ads1.fh-nuernberg.de
│ │ ├─► OK → AD-Felder in User-Eintrag schreiben,
│ │ │ last_login setzen → Login erfolgreich ✓
│ │ └─► Fehler → 401
│ └─► Nein → 401
│
└─► Bei Erfolg: JWT erstellen → httpOnly-Cookie "access_token" setzen → Redirect /
Timing-Attack-Schutz: Wenn pw_hash gesetzt aber falsch, trotzdem LDAP probieren — kein frühes Abbrechen das Timing-Unterschiede verrät.
JWT
- Library:
python-jose[cryptography] - Algorithmus:
HS256 - Claims:
sub(username),exp(jetzt + 8h),is_admin(bool) - Secret:
settings.SECRET_KEY(Zufallsstring, in.envgesetzt) - Cookie:
access_token,httpOnly=True,samesite="lax",secure=TruewennAPP_ENV == "production"
Konfiguration (Ergänzungen in app/core/config.py)
SECRET_KEY: str = "changeme"
LDAP_ENABLED: bool = False # True wenn APP_ENV != "development"
LDAP_SERVER: str = "gso1.ads1.fh-nuernberg.de"
LDAP_DOMAIN: str = "ADS1"
LDAP_SEARCH_BASE: str = "OU=users,OU=EFI,OU=Faculties,DC=ADS1,DC=fh-nuernberg,DC=de"
LDAP_ENABLED kann in .env explizit überschrieben werden.
Dateistruktur
app/
├── core/
│ ├── config.py # Ergänzung: SECRET_KEY, LDAP_*
│ ├── database.py # neu: Engine, SessionLocal, Base, get_db()
│ └── auth.py # neu: JWT erstellen/lesen, Cookie setzen/löschen
├── modules/
│ └── auth/
│ ├── __init__.py
│ ├── models.py # SQLAlchemy User-Model
│ ├── schemas.py # Pydantic: LoginForm, UserOut
│ ├── service.py # verify_password, authenticate_user
│ ├── router.py # GET/POST /auth/login, GET /auth/logout
│ └── dependencies.py # get_current_user, require_admin
├── templates/
│ └── auth/
│ └── login.html # Login-Formular (extends base.html)
alembic/
├── alembic.ini
├── env.py
└── versions/
└── 0001_create_users.py
Endpunkte
| Methode | Pfad | Beschreibung |
|---|---|---|
GET |
/auth/login |
Login-Formular |
POST |
/auth/login |
Auth-Logik, Cookie setzen, Redirect / |
GET |
/auth/logout |
Cookie löschen, Redirect /auth/login |
GET |
/auth/me |
Aktueller User als JSON (für API-Clients) |
FastAPI-Dependencies
-
get_current_user(request: Request, db: Session = Depends(get_db)) -> UserLiest JWT aus Cookieaccess_token, dekodiert, lädt User aus DB. WirftRedirectResponse("/auth/login")wenn kein/ungültiges Token. -
require_admin(user: User = Depends(get_current_user)) -> UserPrüftuser.is_admin, wirftHTTPException(403)wenn False.
Login-Template
Minimales Formular, extends base.html. Felder: username, password. Fehlermeldung wenn Auth fehlschlägt (kein Detail welche Methode fehlschlug — Sicherheit).
Neue Dependencies
alembic
python-jose[cryptography]
passlib[bcrypt]
python-multipart
ldap3
Out of Scope (Part 1)
- LDAP-Hintergrund-Sync (→ Part 2)
- Passwort-Reset / Registrierungs-UI
- Admin-CRUD-UI für User-Verwaltung (→ sqladmin, separater Spec)
- Refresh-Tokens