proxy-oidcv2/src/middleware/oidcMiddleware.js
2025-12-03 22:14:52 +01:00

149 lines
3.8 KiB
JavaScript

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('/');
}
});
}