--- name: onyva-login description: Integriert den zentralen Onyva Dev Login (OIDC/SSO) in ein bestehendes Projekt. Verwende diesen Skill, wenn der Nutzer Onyva Login, OIDC, SSO, Auth-Integration oder /onyva-login in einem Projekt umsetzen will. --- Integriere den zentralen Onyva Dev Login (OIDC/SSO unter `https://login.onyva.dev`) in das aktuelle Projekt. ## Workflow 1. Analysiere zuerst das Projekt. Prüfe Framework, Routing, bestehende Auth-Logik, Session-Handling und Env-Konfiguration, bevor du Code änderst. 2. Kläre fehlende Eingaben nur dann, wenn sie nicht aus dem Repo ableitbar sind. Du brauchst: - `OIDC_CLIENT_ID` - `OIDC_CLIENT_SECRET` - `OIDC_REDIRECT_URI` - `OIDC_LOGOUT_REDIRECT_URI` - welche Routen geschützt sein sollen und welche öffentlich bleiben 3. Implementiere die Integration passend zum vorhandenen Stack. 4. Aktualisiere `.env.example` oder die äquivalente Konfiguration. ## Technische Vorgaben ### Auth-Server - Issuer / Basis-URL: `https://login.onyva.dev` - Discovery: `https://login.onyva.dev/.well-known/openid-configuration` | Endpunkt | Methode | Zweck | |---|---|---| | `/oauth/authorize` | GET | Login starten | | `/oauth/token` | POST | Code gegen Tokens tauschen | | `/oauth/userinfo` | GET | Nutzerinfos per Bearer Token | | `/oauth/jwks.json` | GET | Public Keys (RS256) | | `/logout` | GET | Zentrales Logout | ### Login starten Generiere `state` (mindestens 16 Zeichen, zufällig) und `code_verifier` (PKCE), speichere beide serverseitig in der Session. Dann Weiterleitung zu: ```text GET https://login.onyva.dev/oauth/authorize ?response_type=code &client_id={OIDC_CLIENT_ID} &redirect_uri={OIDC_REDIRECT_URI} &scope=openid profile email &state={state} &code_challenge={BASE64URL(SHA256(code_verifier))} &code_challenge_method=S256 ``` `state` ist Pflicht. Ohne `state` kommt im Callback kein `state` zurück. ### Callback verarbeiten ```python def callback(request): if request.params.get("state") != session.pop("oauth_state", None): return error("State mismatch") if "auth_disabled" in request.params: session["user"] = {"auth_disabled": True} return redirect("/") resp = http_post("https://login.onyva.dev/oauth/token", data={ "grant_type": "authorization_code", "code": request.params["code"], "redirect_uri": OIDC_REDIRECT_URI, "client_id": OIDC_CLIENT_ID, "client_secret": OIDC_CLIENT_SECRET, "code_verifier": session.pop("pkce_verifier"), }) tokens = resp.json() claims = decode_jwt(tokens["id_token"]) session["user"] = { "sub": claims["sub"], "email": claims["email"], "name": claims["name"], "user_type": claims["user_type"], "app_roles": claims["app_roles"], } return redirect("/") ``` Nach erfolgreichem Callback nie auf `/auth/*` zurückleiten. `return_to` immer auf `/` oder eine echte App-Seite normalisieren. ### JWT Claims ```json { "sub": "user:42", "email": "nutzer@onyva.de", "name": "Max Mustermann", "user_type": "employee", "app_roles": ["admin"], "iss": "https://login.onyva.dev", "aud": "mein-client-id", "exp": 1700000000 } ``` ### Logout ```python def logout(request): session.clear() return redirect( f"https://login.onyva.dev/logout" f"?post_logout_redirect_uri={urllib.parse.quote(OIDC_LOGOUT_REDIRECT_URI)}" ) ``` `post_logout_redirect_uri` muss absolut sein (`https://...`). ### Auth-Mode-Verhalten | auth_mode | Auth-Server-Verhalten | Client-Verhalten | |---|---|---| | `required` | 403 wenn keine Mitgliedschaft | Fehlermeldung anzeigen | | `optional` | Code für jeden angemeldeten Nutzer; `app_roles` ggf. leer | App entscheidet selbst | | `disabled` | Sofort zurück mit `?auth_disabled=1` | Kein Login-Dialog, alle Funktionen offen | ### Entwicklungsmodus ```python def require_auth(request): if not AUTH_ENABLED: return { "sub": "user:0", "email": "dev@local", "name": "Dev", "user_type": "employee", "app_roles": ["admin"], } ``` ### Env-Variablen ```env OIDC_ISSUER=https://login.onyva.dev OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= OIDC_REDIRECT_URI= OIDC_LOGOUT_REDIRECT_URI= AUTH_ENABLED=true ``` ## Fallstricke - `state` immer mitsenden und validieren - Redirect URI muss exakt mit dem Eintrag im Admin-Panel übereinstimmen - Token-Endpunkt mit `application/x-www-form-urlencoded`, nicht JSON - Session-Middleware korrekt aktivieren - Öffentliche Routen explizit vom Auth Guard ausnehmen - Kein Redirect zurück auf `/auth/*` ## Checkliste - [ ] Env-Variablen ergänzt und `.env.example` aktualisiert - [ ] Redirect URI im Admin-Panel eingetragen - [ ] Login-Route mit `state` und PKCE gebaut - [ ] Callback validiert `state` und tauscht Code gegen Tokens - [ ] Claims landen in der Session - [ ] Logout löscht lokale Session und ruft zentrales Logout auf - [ ] Geschützte und öffentliche Routen sauber getrennt - [ ] `AUTH_ENABLED=false` für lokale Entwicklung berücksichtigt - [ ] `auth_disabled=1` Fall behandelt