added additional logging and error handling to try to debug a scope parameter failure

pull/3743/head
Mike Kao 11 months ago
parent 4367ee3ca6
commit 11aa3f218f

@ -415,67 +415,81 @@ authRoutes.post('/reset-password/:guid', async (req, res, next) => {
}); });
authRoutes.get('/oidc-login', async (req, res, next) => { authRoutes.get('/oidc-login', async (req, res, next) => {
const state = randomBytes(32).toString('hex'); try {
const redirectUrl = await getOIDCRedirectUrl(req, state); const state = randomBytes(32).toString('hex');
const redirectUrl = await getOIDCRedirectUrl(req, state);
res.cookie('oidc-state', state, { res.cookie('oidc-state', state, {
maxAge: 60000, maxAge: 60000,
httpOnly: true, httpOnly: true,
secure: req.protocol === 'https', secure: req.protocol === 'https',
}); });
return res.redirect(redirectUrl);
});
authRoutes.get('/oidc-callback', async (req, res, next) => { logger.debug('OIDC login initiated', {
const settings = getSettings(); path: '/oidc-login',
const { oidcDomain, oidcClientId, oidcClientSecret } = settings.main; redirectUrl: redirectUrl,
state: state,
});
if (!settings.main.oidcLogin) { return res.redirect(redirectUrl);
return res.status(500).json({ error: 'OIDC sign-in is disabled.' }); } catch (error) {
logger.error('Failed to initiate OIDC login', {
path: '/oidc-login',
error: error.message,
});
next(error); // Or handle the error as appropriate for your application
} }
const cookieState = req.cookies['oidc-state']; });
const url = new URL(req.url, `${req.protocol}://${req.hostname}`);
const state = url.searchParams.get('state');
authRoutes.get('/oidc-callback', async (req, res, next) => {
try { try {
// Check that the request belongs to the correct state const settings = getSettings();
if (state && cookieState === state) { const { oidcDomain, oidcClientId, oidcClientSecret } = settings.main;
res.clearCookie('oidc-state');
} else { if (!settings.main.oidcLogin) {
logger.info('Failed OIDC login attempt', { logger.warn('OIDC sign-in is disabled', { path: '/oidc-callback' });
cause: 'Invalid state', return res.status(500).json({ error: 'OIDC sign-in is disabled.' });
ip: req.ip,
state: state,
cookieState: cookieState,
});
return res.redirect('/login');
} }
// Check that a code as been issued const cookieState = req.cookies['oidc-state'];
const url = new URL(req.url, `${req.protocol}://${req.hostname}`);
const state = url.searchParams.get('state');
const code = url.searchParams.get('code'); const code = url.searchParams.get('code');
if (!code) {
logger.info('Failed OIDC login attempt', { logger.debug('OIDC callback received', {
cause: 'Invalid code', path: '/oidc-callback',
ip: req.ip, state: state,
code: code,
cookieState: cookieState,
});
if (!state || state !== cookieState || !code) {
logger.warn('OIDC callback state or code mismatch or missing', {
path: '/oidc-callback',
state: state,
code: code, code: code,
cookieState: cookieState,
}); });
return res.redirect('/login'); return res.redirect('/login');
} }
res.clearCookie('oidc-state');
const wellKnownInfo = await getOIDCWellknownConfiguration(oidcDomain); const wellKnownInfo = await getOIDCWellknownConfiguration(oidcDomain);
// Fetch the token data
const callbackUrl = new URL( const callbackUrl = new URL(
'/api/v1/auth/oidc-callback', '/api/v1/auth/oidc-callback',
`${req.protocol}://${req.headers.host}` `${req.protocol}://${req.headers.host}`
); ).toString();
const formData = new URLSearchParams(); const formData = new URLSearchParams();
formData.append('client_secret', oidcClientSecret); formData.append('client_secret', oidcClientSecret);
formData.append('grant_type', 'authorization_code'); formData.append('grant_type', 'authorization_code');
formData.append('redirect_uri', callbackUrl.toString()); formData.append('redirect_uri', callbackUrl);
formData.append('client_id', oidcClientId); formData.append('client_id', oidcClientId);
formData.append('code', code); formData.append('code', code);
const response = await fetch(wellKnownInfo.token_endpoint, { const response = await fetch(wellKnownInfo.token_endpoint, {
method: 'POST', method: 'POST',
headers: new Headers([ headers: new Headers([
@ -484,49 +498,39 @@ authRoutes.get('/oidc-callback', async (req, res, next) => {
body: formData, body: formData,
}); });
// Check that the response is valid const body = await response.json();
const body = (await response.json()) as
| { id_token: string; error: never }
| { error: string };
if (body.error) { if (body.error) {
logger.info('Failed OIDC login attempt', { logger.warn('Failed OIDC token exchange', {
cause: 'Invalid token response', path: '/oidc-callback',
ip: req.ip, error: body.error,
body: body, state: state,
code: code,
}); });
return res.redirect('/login'); return res.redirect('/login');
} }
// Validate that the token response is valid and not manipulated const { id_token: idToken } = body;
const { id_token: idToken } = body as Extract< const decoded = decodeJwt(idToken);
typeof body, const jwtSchema = createJwtSchema({
{ id_token: string } oidcClientId: oidcClientId,
>; oidcDomain: oidcDomain,
try { });
const decoded = decodeJwt(idToken);
const jwtSchema = createJwtSchema({
oidcClientId: oidcClientId,
oidcDomain: oidcDomain,
});
try {
await jwtSchema.validate(decoded); await jwtSchema.validate(decoded);
} catch { } catch (error) {
logger.info('Failed OIDC login attempt', { logger.warn('Invalid JWT in OIDC callback', {
cause: 'Invalid jwt', path: '/oidc-callback',
ip: req.ip, error: error.message,
idToken: idToken, idToken: idToken,
}); });
return res.redirect('/login'); return res.redirect('/login');
} }
// Check that email is verified and map email to user
const decoded: InferType<ReturnType<typeof createJwtSchema>> =
decodeJwt(idToken);
if (!decoded.email_verified) { if (!decoded.email_verified) {
logger.info('Failed OIDC login attempt', { logger.warn('Email not verified in OIDC callback', {
cause: 'Email not verified', path: '/oidc-callback',
ip: req.ip,
email: decoded.email, email: decoded.email,
}); });
return res.redirect('/login'); return res.redirect('/login');
@ -537,10 +541,9 @@ authRoutes.get('/oidc-callback', async (req, res, next) => {
where: { email: decoded.email }, where: { email: decoded.email },
}); });
// Create user if it doesn't exist
if (!user) { if (!user) {
logger.info(`Creating user for ${decoded.email}`, { logger.info(`Creating new user from OIDC callback for ${decoded.email}`, {
ip: req.ip, path: '/oidc-callback',
email: decoded.email, email: decoded.email,
}); });
const avatar = gravatarUrl(decoded.email, { default: 'mm', size: 200 }); const avatar = gravatarUrl(decoded.email, { default: 'mm', size: 200 });
@ -555,18 +558,17 @@ authRoutes.get('/oidc-callback', async (req, res, next) => {
await userRepository.save(user); await userRepository.save(user);
} }
// Set logged in session and return
if (req.session) { if (req.session) {
req.session.userId = user.id; req.session.userId = user.id;
} }
return res.redirect('/'); return res.redirect('/');
} catch (error) { } catch (error) {
logger.error('Failed OIDC login attempt', { logger.error('Error in OIDC callback processing', {
cause: 'Unknown error', path: '/oidc-callback',
ip: req.ip, error: error.message,
errorMessage: error.message,
}); });
return res.redirect('/login'); next(error);
} }
}); });

Loading…
Cancel
Save