import * as oidc from 'openid-client' const CACHE_TTL_MS = 60 * 60 * 1000 const configCache = new Map() // Auth configuration export function getAuthConfig() { const hasOidc = !!(process.env.OIDC_ISSUER && process.env.OIDC_CLIENT_ID && process.env.OIDC_CLIENT_SECRET) const label = process.env.OIDC_LABEL?.trim() || (hasOidc ? 'Sign in with OIDC' : '') return Object.freeze({ oidc: { enabled: hasOidc, label } }) } function getRedirectUri() { const explicit = process.env.OIDC_REDIRECT_URI ?? process.env.OPENID_REDIRECT_URI ?? '' if (explicit.trim()) return explicit.trim() const base = process.env.NUXT_APP_URL || process.env.APP_URL || '' if (base) return `${base.replace(/\/$/, '')}/api/auth/oidc/callback` const host = process.env.HOST || 'localhost' const port = process.env.PORT || '3000' const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http' return `${protocol}://${host}:${port}/api/auth/oidc/callback` } export async function getOidcConfig() { const issuer = process.env.OIDC_ISSUER const clientId = process.env.OIDC_CLIENT_ID const clientSecret = process.env.OIDC_CLIENT_SECRET if (!issuer || !clientId || !clientSecret) return null const key = issuer const cached = configCache.get(key) if (cached && Date.now() < cached.expires) return cached.config const server = new URL(issuer) const config = await oidc.discovery( server, clientId, undefined, oidc.ClientSecretPost(clientSecret), { timeout: 5000 }, ) configCache.set(key, { config, expires: Date.now() + CACHE_TTL_MS }) return config } export function getOidcRedirectUri() { return getRedirectUri() } export function constantTimeCompare(a, b) { if (typeof a !== 'string' || typeof b !== 'string' || a.length !== b.length) return false let out = 0 for (let i = 0; i < a.length; i++) out |= a.charCodeAt(i) ^ b.charCodeAt(i) return out === 0 } export function createOidcParams() { return { state: oidc.randomState(), nonce: oidc.randomNonce(), codeVerifier: oidc.randomPKCECodeVerifier(), } } export async function getCodeChallenge(verifier) { return oidc.calculatePKCECodeChallenge(verifier) } export function validateRedirectPath(redirect) { if (typeof redirect !== 'string' || !redirect.startsWith('/') || redirect.startsWith('//')) return '/' const path = redirect.split('?')[0] if (path.includes('//')) return '/' return redirect } export function buildAuthorizeUrl(config, params) { return oidc.buildAuthorizationUrl(config, params) } export async function exchangeCode(config, currentUrl, checks) { return oidc.authorizationCodeGrant(config, currentUrl, checks) }