diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 4089cc2e3..c7fe2852e 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -443,52 +443,48 @@ authRoutes.get('/oidc-login', async (req, res, next) => { authRoutes.get('/oidc-callback', async (req, res, next) => { - try { - const settings = getSettings(); - const { oidcDomain, oidcClientId, oidcClientSecret } = settings.main; + 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.' }); - } + // Log the initial OIDC callback request + logger.info('Received OIDC callback', { ip: req.ip }); - 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 (!settings.main.oidcLogin) { + logger.warn('OIDC sign-in is disabled', { ip: req.ip }); + return res.status(500).json({ error: 'OIDC sign-in is disabled.' }); + } - logger.debug('OIDC callback received', { - path: '/oidc-callback', - state: state, - code: code, - cookieState: cookieState, - }); + const cookieState = req.cookies['oidc-state']; + const url = new URL(req.url, `${req.protocol}://${req.hostname}`); + const state = url.searchParams.get('state'); + const scope = url.searchParams.get('scope'); // Handling additional 'scope' parameter - if (!state || state !== cookieState || !code) { - logger.warn('OIDC callback state or code mismatch or missing', { - path: '/oidc-callback', - state: state, - code: code, - cookieState: cookieState, - }); + try { + // State validation + if (!state || cookieState !== state) { + logger.warn('OIDC state mismatch', { ip: req.ip, state, cookieState }); return res.redirect('/login'); } - res.clearCookie('oidc-state'); - const wellKnownInfo = await getOIDCWellknownConfiguration(oidcDomain); + // Code validation + const code = url.searchParams.get('code'); + if (!code) { + logger.warn('OIDC code missing', { ip: req.ip }); + return res.redirect('/login'); + } - const callbackUrl = new URL( - '/api/v1/auth/oidc-callback', - `${req.protocol}://${req.headers.host}` - ).toString(); + const wellKnownInfo = await getOIDCWellknownConfiguration(oidcDomain); + // Token request + const callbackUrl = new URL('/api/v1/auth/oidc-callback', `${req.protocol}://${req.headers.host}`); const formData = new URLSearchParams(); formData.append('client_secret', oidcClientSecret); formData.append('grant_type', 'authorization_code'); - formData.append('redirect_uri', callbackUrl); + formData.append('redirect_uri', callbackUrl.toString()); formData.append('client_id', oidcClientId); formData.append('code', code); + formData.append('scope', scope); // Include the 'scope' in the token request const response = await fetch(wellKnownInfo.token_endpoint, { method: 'POST', @@ -498,78 +494,53 @@ authRoutes.get('/oidc-callback', async (req, res, next) => { body: formData, }); + // Validate response const body = await response.json(); - if (body.error) { - logger.warn('Failed OIDC token exchange', { - path: '/oidc-callback', - error: body.error, - state: state, - code: code, - }); + logger.warn('Invalid token response', { ip: req.ip, error: body.error }); return res.redirect('/login'); } + // Token validation const { id_token: idToken } = body; - const decoded = decodeJwt(idToken); - const jwtSchema = createJwtSchema({ - oidcClientId: oidcClientId, - oidcDomain: oidcDomain, - }); - + let decoded; try { + decoded = decodeJwt(idToken); + const jwtSchema = createJwtSchema({ oidcClientId, oidcDomain }); await jwtSchema.validate(decoded); } catch (error) { - logger.warn('Invalid JWT in OIDC callback', { - path: '/oidc-callback', - error: error.message, - idToken: idToken, - }); + logger.warn('JWT validation failed', { ip: req.ip, error: error.message }); return res.redirect('/login'); } + // Email verification if (!decoded.email_verified) { - logger.warn('Email not verified in OIDC callback', { - path: '/oidc-callback', - email: decoded.email, - }); + logger.warn('Email not verified', { ip: req.ip, email: decoded.email }); return res.redirect('/login'); } + // User handling const userRepository = getRepository(User); - let user = await userRepository.findOne({ - where: { email: decoded.email }, - }); + let user = await userRepository.findOne({ where: { email: decoded.email } }); if (!user) { - logger.info(`Creating new user from OIDC callback for ${decoded.email}`, { - path: '/oidc-callback', - email: decoded.email, - }); + logger.info('Creating new user', { ip: req.ip, email: decoded.email }); const avatar = gravatarUrl(decoded.email, { default: 'mm', size: 200 }); - user = new User({ - avatar: avatar, - username: decoded.email, - email: decoded.email, - permissions: settings.main.defaultPermissions, - plexToken: '', - userType: UserType.LOCAL, - }); + user = new User({ avatar, username: decoded.email, email: decoded.email, permissions: settings.main.defaultPermissions, plexToken: '', userType: UserType.LOCAL }); await userRepository.save(user); } + // Session handling if (req.session) { req.session.userId = user.id; } - + logger.info('User logged in successfully', { ip: req.ip, userId: user.id }); return res.redirect('/'); } catch (error) { - logger.error('Error in OIDC callback processing', { - path: '/oidc-callback', - error: error.message, - }); - next(error); + logger.error('OIDC login error', { ip: req.ip, errorMessage: error.message }); + return res.redirect('/login'); } }); + export default authRoutes;