oidc: authenticate SPA users via Authentik
- Add OIDC proxy endpoints (GET config, POST token) to API server - Implement PKCE Authorization Code flow in SPA - Enable Authentik login tab in Config page - Handle callback + auto-refresh + session restore - Add restart: unless-stopped to all persistent services - Configure OIDC issuer + client_id in docker-compose
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
// or cross-origin (Wails webview, remote access). See
|
||||
// plans/2026-07-12-wails-desktop-app.md 0.2.
|
||||
|
||||
import { getToken, isOIDCConfigured } from './oidc'
|
||||
|
||||
export interface OikosConfig {
|
||||
apiUrl: string // e.g. "https://oikos.hubris.network", or "" for same-origin
|
||||
token?: string // bearer token for auth
|
||||
@@ -51,7 +53,7 @@ export function clearConfig() {
|
||||
}
|
||||
|
||||
export function isConfigured(): boolean {
|
||||
return !!getConfig().token
|
||||
return !!getConfig().token || isOIDCConfigured()
|
||||
}
|
||||
|
||||
// Relative paths are used in dev (Vite proxy) and when the SPA shares an
|
||||
@@ -63,6 +65,15 @@ export function apiBase(path: string): string {
|
||||
return `${c.apiUrl}${path}`
|
||||
}
|
||||
|
||||
// Resolves the auth token for a request: OIDC takes precedence, then static.
|
||||
function resolveAuthHeader(): string | null {
|
||||
const oidcToken = getToken()
|
||||
if (oidcToken) return `Bearer ${oidcToken}`
|
||||
const c = getConfig()
|
||||
if (c.token) return `Bearer ${c.token}`
|
||||
return null
|
||||
}
|
||||
|
||||
// ---- Auth fetch wrapper ----
|
||||
// Prepends the API base URL (absolute when configured, relative when unset
|
||||
// for the Vite dev proxy / same-origin prod) and adds the Authorization
|
||||
@@ -72,9 +83,9 @@ export async function fetchWithAuth(path: string, opts?: RequestInit): Promise<R
|
||||
'Content-Type': 'application/json',
|
||||
...(opts?.headers as Record<string, string> ?? {})
|
||||
}
|
||||
const c = getConfig()
|
||||
if (c.token) {
|
||||
headers['Authorization'] = `Bearer ${c.token}`
|
||||
const authH = resolveAuthHeader()
|
||||
if (authH) {
|
||||
headers['Authorization'] = authH
|
||||
}
|
||||
|
||||
return fetch(apiBase(path), { ...opts, headers })
|
||||
@@ -84,9 +95,11 @@ export async function fetchWithAuth(path: string, opts?: RequestInit): Promise<R
|
||||
// a query parameter (the SSE handler's combinedAuth checks it alongside the
|
||||
// Authorization header, only for this route).
|
||||
export function sseUrl(path: string): string {
|
||||
const c = getConfig()
|
||||
const url = apiBase(path)
|
||||
if (!c.token) return url
|
||||
const c = getConfig()
|
||||
const oidcToken = getToken()
|
||||
const token = oidcToken ?? c.token
|
||||
if (!token) return url
|
||||
const sep = url.includes('?') ? '&' : '?'
|
||||
return `${url}${sep}token=${encodeURIComponent(c.token)}`
|
||||
return `${url}${sep}token=${encodeURIComponent(token)}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user