149 lines
3.8 KiB
JavaScript
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('/');
|
|
}
|
|
});
|
|
}
|