docs: add auth part 1 design spec (core auth + JWT)
This commit is contained in:
parent
d33864c119
commit
a5855600f7
163
docs/superpowers/specs/2026-04-27-auth-part1-design.md
Normal file
163
docs/superpowers/specs/2026-04-27-auth-part1-design.md
Normal file
@ -0,0 +1,163 @@
|
||||
# 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 `.env` gesetzt)
|
||||
- Cookie: `access_token`, `httpOnly=True`, `samesite="lax"`, `secure=True` wenn `APP_ENV == "production"`
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration (Ergänzungen in `app/core/config.py`)
|
||||
|
||||
```python
|
||||
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)) -> User`
|
||||
Liest JWT aus Cookie `access_token`, dekodiert, lädt User aus DB.
|
||||
Wirft `RedirectResponse("/auth/login")` wenn kein/ungültiges Token.
|
||||
|
||||
- `require_admin(user: User = Depends(get_current_user)) -> User`
|
||||
Prüft `user.is_admin`, wirft `HTTPException(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
|
||||
Loading…
x
Reference in New Issue
Block a user