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