forked from Onyva/onyva-login-skill
restructure: plugin files in /plugin subdir for correct sparse-checkout
This commit is contained in:
170
plugin/skills/onyva-login/SKILL.md
Normal file
170
plugin/skills/onyva-login/SKILL.md
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
name: onyva-login
|
||||
description: Integriert den zentralen Onyva Dev Login (OIDC/SSO) in ein bestehendes Projekt. Nutze diesen Skill wenn jemand "onyva login integrieren", "auth integrieren" oder "/onyva-login" schreibt.
|
||||
---
|
||||
|
||||
Deine Aufgabe ist es, den zentralen Onyva Dev Login (OIDC/SSO-Server unter `https://login.onyva.dev`) in das aktuelle Projekt zu integrieren.
|
||||
|
||||
## Vorgehen
|
||||
|
||||
1. **Analysiere das Projekt zuerst** — Lies relevante Dateien (Framework, Routing, vorhandene Auth-Logik, Env-Konfiguration), bevor du Code schreibst.
|
||||
|
||||
2. **Frage nach fehlenden Infos** — Du brauchst:
|
||||
- `OIDC_CLIENT_ID` und `OIDC_CLIENT_SECRET` (aus dem Admin-Panel von login.onyva.dev)
|
||||
- `OIDC_REDIRECT_URI` (wohin der Auth-Server nach dem Login zurückleitet — muss im Admin-Panel eingetragen sein)
|
||||
- `OIDC_LOGOUT_REDIRECT_URI` (wohin nach dem zentralen Logout weitergeleitet wird)
|
||||
- Falls noch nicht bekannt: welche Routen geschützt sein sollen und welche öffentlich bleiben
|
||||
|
||||
3. **Implementiere die Integration** nach den technischen Vorgaben unten.
|
||||
|
||||
4. **Passe `.env.example`** (oder äquivalente Konfiguration) mit allen nötigen Variablen an.
|
||||
|
||||
---
|
||||
|
||||
## 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` (≥16 Zeichen, zufällig) und `code_verifier` (PKCE), speichere beide serverseitig in der Session. Dann Weiterleitung zu:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
**Wichtig:** `state` muss nachweislich in der URL stehen — ohne `state` kein `state` im Callback.
|
||||
|
||||
### Callback verarbeiten
|
||||
|
||||
```python
|
||||
# Pseudocode — an das Framework anpassen
|
||||
def callback(request):
|
||||
# 1. State validieren
|
||||
if request.params.get("state") != session.pop("oauth_state", None):
|
||||
return error("State mismatch")
|
||||
|
||||
# 2. Sonderfall: Auth deaktiviert
|
||||
if "auth_disabled" in request.params:
|
||||
session["user"] = {"auth_disabled": True}
|
||||
return redirect("/")
|
||||
|
||||
# 3. Token-Tausch (application/x-www-form-urlencoded, kein JSON)
|
||||
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()
|
||||
|
||||
# 4. Claims aus id_token lesen
|
||||
claims = decode_jwt(tokens["id_token"]) # Verifikation optional für interne Nutzung
|
||||
session["user"] = {
|
||||
"sub": claims["sub"], # "user:42"
|
||||
"email": claims["email"],
|
||||
"name": claims["name"],
|
||||
"user_type": claims["user_type"], # "employee" | "customer"
|
||||
"app_roles": claims["app_roles"], # ["admin"] | ["editor"] | ["viewer"] | []
|
||||
}
|
||||
return redirect("/")
|
||||
```
|
||||
|
||||
Nach erfolgreichem Callback niemals 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://...`). Der Auth-Server löscht die zentrale Session — der Nutzer ist danach auf allen Onyva-Apps ausgeloggt.
|
||||
|
||||
### 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 |
|
||||
|
||||
### AUTH_ENABLED=false (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"]}
|
||||
# ... normaler Flow
|
||||
```
|
||||
|
||||
### Env-Variablen
|
||||
|
||||
```env
|
||||
OIDC_ISSUER=https://login.onyva.dev
|
||||
OIDC_CLIENT_ID= # aus Admin-Panel
|
||||
OIDC_CLIENT_SECRET= # aus Admin-Panel
|
||||
OIDC_REDIRECT_URI= # muss exakt im Admin-Panel stehen; bei /api-Prefix: https://app.example.com/api/auth/callback
|
||||
OIDC_LOGOUT_REDIRECT_URI= # z.B. https://app.example.com/
|
||||
AUTH_ENABLED=true
|
||||
```
|
||||
|
||||
## Wichtige Fallstricke
|
||||
|
||||
- **State ist Pflicht** — wird er nicht mitgeschickt, kommt er nicht zurück und der Callback schlägt fehl
|
||||
- **Redirect URI exakt** — `/api/auth/callback` ≠ `/auth/callback`; im Admin-Panel muss genau dieselbe URL stehen
|
||||
- **Token-Endpunkt: Form, kein JSON** — `Content-Type: application/x-www-form-urlencoded`
|
||||
- **Session-Middleware** — bei Starlette/FastAPI: `itsdangerous` als Dependency erforderlich
|
||||
- **Öffentliche Routen ausdrücklich ausnehmen** — Upload-Links, Webhooks, Health-Checks müssen vom Auth Guard ausgeschlossen bleiben
|
||||
- **Kein Redirect auf Auth-Routen** — `return_to` niemals auf `/auth/*` setzen
|
||||
|
||||
## Schnellcheckliste
|
||||
|
||||
- [ ] Env-Variablen konfiguriert und `.env.example` aktualisiert
|
||||
- [ ] Redirect URI im Admin-Panel von login.onyva.dev eingetragen
|
||||
- [ ] Login-Route: `state` + PKCE generiert, in Session gespeichert, in Authorize-URL enthalten
|
||||
- [ ] Callback-Route: `state` validiert, Token-Tausch, Claims in Session
|
||||
- [ ] Logout-Route: lokale Session gelöscht, Weiterleitung zu zentralem Logout
|
||||
- [ ] Auth Guard für geschützte Routen
|
||||
- [ ] Öffentliche Routen explizit ausgenommen
|
||||
- [ ] `AUTH_ENABLED=false` Bypass für Entwicklung
|
||||
- [ ] `auth_disabled=1` Callback-Fall abgedeckt
|
||||
Reference in New Issue
Block a user