import { Issuer } from 'openid-client'; import config from '../config.js'; let client = null; let oidcEnabled = false; export async function initOIDC() { try { // Check if OIDC is configured if (!config.oidc.issuer || !config.oidc.clientId || !config.oidc.clientSecret) { console.log('✓ Development mode: OIDC not configured - running without authentication'); oidcEnabled = false; return null; } const issuer = await Issuer.discover(config.oidc.issuer); client = new issuer.Client({ client_id: config.oidc.clientId, client_secret: config.oidc.clientSecret, redirect_uris: [config.oidc.redirectUri], response_types: ['code'], }); oidcEnabled = true; console.log('✓ OIDC Client initialized successfully'); return client; } catch (error) { console.log('✓ Development mode: OIDC not available - running without authentication'); oidcEnabled = false; return null; } } export function isOIDCEnabled() { return oidcEnabled; } export function getOIDCClient() { if (!oidcEnabled || !client) { throw new Error('OIDC not enabled or not initialized'); } return client; } export function getAuthorizationUrl(req) { const client = getOIDCClient(); const nonce = Math.random().toString(36).substring(7); const state = Math.random().toString(36).substring(7); req.session.nonce = nonce; req.session.state = state; return client.authorizationUrl({ scope: 'openid profile email', response_mode: 'form_post', nonce, state, }); } export async function handleCallback(req) { const client = getOIDCClient(); const params = { ...req.query, ...req.body, }; // Log for debugging console.log('OAuth callback params:', { code: params.code ? 'present' : 'missing', state: params.state ? 'present' : 'missing', error: params.error || 'none' }); console.log('Session state:', req.session.state ? 'present' : 'missing'); console.log('Session nonce:', req.session.nonce ? 'present' : 'missing'); // Prepare validation options - only include state if it was provided by the provider const validationOpts = { nonce: req.session.nonce, }; // Only validate state if the provider sent it back if (params.state) { validationOpts.state = req.session.state; } console.log('Validation options:', { hasNonce: !!validationOpts.nonce, hasState: !!validationOpts.state }); const tokenSet = await client.callback(config.oidc.redirectUri, params, validationOpts); const userInfo = await client.userinfo(tokenSet); return { tokenSet, userInfo, }; } export function requireAuth(req, res, next) { if (req.session && req.session.user) { return next(); } req.session.redirectUrl = req.originalUrl; res.redirect('/login'); } export function requireAdmin(req, res, next) { // In dev mode without OIDC, allow access if (!isOIDCEnabled()) { return next(); } if (req.session && req.session.user && req.session.user.isAdmin) { return next(); } res.status(403).json({ error: 'Admin access required' }); } export function logout(req, res, next) { // In dev mode, just destroy session if (!isOIDCEnabled()) { req.session.destroy((err) => { if (err) { return next(err); } res.redirect('/'); }); return; } const client = getOIDCClient(); const idToken = req.session.tokenSet?.id_token; req.session.destroy((err) => { if (err) { return next(err); } if (idToken && client.issuer.metadata.end_session_endpoint) { const logoutUrl = client.issuer.metadata.end_session_endpoint; const postLogoutRedirectUri = `${config.proxyUrl}/`; res.redirect( `${logoutUrl}?id_token_hint=${idToken}&post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}` ); } else { res.redirect('/'); } }); }