From 27c13f24eea429df62142aa030f0babd9685b385 Mon Sep 17 00:00:00 2001 From: rock Date: Sun, 13 Sep 2026 05:25:22 +0900 Subject: [PATCH] chore: remove AUTH_INTEGRATION.md Architecture docs belong in issue/wiki, not repo root. --- AUTH_INTEGRATION.md | 206 -------------------------------------------- 1 file changed, 206 deletions(-) delete mode 100644 AUTH_INTEGRATION.md diff --git a/AUTH_INTEGRATION.md b/AUTH_INTEGRATION.md deleted file mode 100644 index 992c4f8..0000000 --- a/AUTH_INTEGRATION.md +++ /dev/null @@ -1,206 +0,0 @@ -# Authentik Auth Integration for NextJS - -## Current State - -### Gateway Auth Status - -| Endpoint | Auth Status | Notes | -|----------|-------------|-------| -| `/v1/chat/completions` | ❌ **OFF** | LLM routes have no auth middleware | -| `/v1/embeddings` | ❌ **OFF** | Same - no auth | -| `/v1/rerank` | ❌ **OFF** | Same - no auth | -| `X-Service: sqs` | ✅ **ON** | JWT validated via `internal/auth/jwt.go` | -| `/workflow` | ❌ **OFF** | Pass-through to Temporal | - -**Auth module exists** at `homelab-frontend/internal/auth/jwt.go` but only wired for SQS. -LLM routes in `internal/proxy/proxy.go` have no auth middleware. - -### Authentik App - -Authentik app `local-llm` exists for LLM API auth: -- **Client ID**: `local-llm` -- **Client Secret**: `kubectl -n llm-serving get secret local-llm-jwt -o jsonpath='{.data.client-secret}' | base64 -d` -- **Token endpoint**: `https://authentik.riotpiao.com/application/o/token/` -- **Userinfo endpoint**: `https://authentik.riotpiao.com/application/o/userinfo/` -- **OIDC discovery**: `https://authentik.riotpiao.com/application/o/local-llm/.well-known/openid-configuration` - -## Sign-in Methods - -### 1. Resource Owner Password Credentials (ROPC) - -Direct username/password login. Server-side only (needs client_secret). - -```typescript -// API Route: app/api/auth/login/route.ts -const response = await fetch('https://authentik.riotpiao.com/application/o/token/', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'password', - client_id: 'local-llm', - client_secret: process.env.AUTHENTIK_CLIENT_SECRET, - username: 'user@example.com', - password: 'userpassword', - scope: 'openid email profile groups', - }), -}); - -const tokens = await response.json(); -// { access_token, refresh_token, expires_in, token_type } -``` - -### 2. Authorization Code Flow (Browser Redirect) - -Requires adding redirect URIs to `local-llm` Authentik app: - -```python -# In k8s/infra/iam/scripts/authentik-provision.py, update: -"local-llm": { - ... - "redirect_uris": [ - "http://localhost:3000/api/auth/callback", # dev - "https://your-nextjs-app.com/api/auth/callback", # prod - ], -} -``` - -Then standard OIDC flow: -1. Redirect to `https://authentik.riotpiao.com/application/o/authorize/?client_id=local-llm&redirect_uri=...&response_type=code&scope=openid email profile groups` -2. User logs in via Authentik UI -3. Callback receives `code`, exchange for tokens - -## JWT Token Persistence - -### Browser (localStorage) - -```typescript -const TOKEN_KEY = 'llm_auth_token'; - -// Save -localStorage.setItem(TOKEN_KEY, JSON.stringify({ - access_token: tokens.access_token, - refresh_token: tokens.refresh_token, - expires_at: Date.now() + tokens.expires_in * 1000, -})); - -// Load -const stored = JSON.parse(localStorage.getItem(TOKEN_KEY) || 'null'); -if (stored && stored.expires_at > Date.now()) { - // Token valid -} - -// Clear (logout) -localStorage.removeItem(TOKEN_KEY); -``` - -### Server-side (HTTP-only cookies) - -```typescript -// app/api/auth/login/route.ts -import { cookies } from 'next/headers'; - -// After successful login -cookies().set('llm_auth_token', JSON.stringify(tokens), { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - maxAge: tokens.expires_in, - path: '/', -}); - -// Read in middleware or API routes -const tokenCookie = cookies().get('llm_auth_token'); -const tokens = JSON.parse(tokenCookie?.value || 'null'); -``` - -## Token Refresh - -```typescript -async function refreshAccessToken(refresh_token: string) { - const response = await fetch('https://authentik.riotpiao.com/application/o/token/', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'refresh_token', - client_id: 'local-llm', - client_secret: process.env.AUTHENTIK_CLIENT_SECRET, - refresh_token, - }), - }); - return response.json(); -} -``` - -## Environment Variables - -```bash -# .env.local -AUTHENTIK_URL=https://authentik.riotpiao.com -AUTHENTIK_CLIENT_ID=local-llm -AUTHENTIK_CLIENT_SECRET= - -# For client-side (public) -NEXT_PUBLIC_AUTHENTIK_URL=https://authentik.riotpiao.com -NEXT_PUBLIC_AUTHENTIK_CLIENT_ID=local-llm -``` - -## Using Token with LLM API - -```typescript -const token = await getValidToken(); // from localStorage or cookie - -const response = await fetch('https://api.riotpiao.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, // JWT from Authentik - }, - body: JSON.stringify({ - model: 'reasoning', - messages: [{ role: 'user', content: 'Hello' }], - }), -}); -``` - -## TODO - -### Gateway-side (homelab-frontend) - -- [ ] Wire `internal/auth/jwt.go` into LLM proxy handler (`internal/proxy/proxy.go`) -- [ ] Add `authRequired: true` to model config or create LLM-specific middleware -- [ ] Example pattern from SQS (in `internal/serviceadapter/router.go`): - -```go -// In proxy.go ServeHTTP, before dispatching to LLM upstream: -if strings.HasPrefix(r.URL.Path, "/v1/") { - authHeader := r.Header.Get("Authorization") - claims, err := llmJWTAuth.ValidateBearerToken(authHeader) - if err != nil { - // Return 401/403 - } - if !llmJWTAuth.CheckPermissions(claims, "llm:inference", "*") { - // Return 403 insufficient permissions - } -} -``` - -### Authentik-side - -- [ ] Enable ROPC grant in Authentik provider settings (if not already) -- [ ] Add redirect URIs to `local-llm` app if browser OAuth flow needed: - -```python -# k8s/infra/iam/scripts/authentik-provision.py -"local-llm": { - ... - "redirect_uris": [ - "http://localhost:3000/api/auth/callback", - "https://your-app.com/api/auth/callback", - ], -} -``` - -### NextJS-side - -- [ ] Until gateway auth is wired, LLM API works without token -- [ ] Once wired, add `Authorization: Bearer ` to all LLM requests