| | const cookies = require('cookie'); |
| | const jwt = require('jsonwebtoken'); |
| | const openIdClient = require('openid-client'); |
| | const { logger } = require('@librechat/data-schemas'); |
| | const { isEnabled, findOpenIDUser } = require('@librechat/api'); |
| | const { |
| | requestPasswordReset, |
| | setOpenIDAuthTokens, |
| | resetPassword, |
| | setAuthTokens, |
| | registerUser, |
| | } = require('~/server/services/AuthService'); |
| | const { findUser, getUserById, deleteAllUserSessions, findSession } = require('~/models'); |
| | const { getGraphApiToken } = require('~/server/services/GraphTokenService'); |
| | const { getOAuthReconnectionManager } = require('~/config'); |
| | const { getOpenIdConfig } = require('~/strategies'); |
| |
|
| | const registrationController = async (req, res) => { |
| | try { |
| | const response = await registerUser(req.body); |
| | const { status, message } = response; |
| | res.status(status).send({ message }); |
| | } catch (err) { |
| | logger.error('[registrationController]', err); |
| | return res.status(500).json({ message: err.message }); |
| | } |
| | }; |
| |
|
| | const resetPasswordRequestController = async (req, res) => { |
| | try { |
| | const resetService = await requestPasswordReset(req); |
| | if (resetService instanceof Error) { |
| | return res.status(400).json(resetService); |
| | } else { |
| | return res.status(200).json(resetService); |
| | } |
| | } catch (e) { |
| | logger.error('[resetPasswordRequestController]', e); |
| | return res.status(400).json({ message: e.message }); |
| | } |
| | }; |
| |
|
| | const resetPasswordController = async (req, res) => { |
| | try { |
| | const resetPasswordService = await resetPassword( |
| | req.body.userId, |
| | req.body.token, |
| | req.body.password, |
| | ); |
| | if (resetPasswordService instanceof Error) { |
| | return res.status(400).json(resetPasswordService); |
| | } else { |
| | await deleteAllUserSessions({ userId: req.body.userId }); |
| | return res.status(200).json(resetPasswordService); |
| | } |
| | } catch (e) { |
| | logger.error('[resetPasswordController]', e); |
| | return res.status(400).json({ message: e.message }); |
| | } |
| | }; |
| |
|
| | const refreshController = async (req, res) => { |
| | const refreshToken = req.headers.cookie ? cookies.parse(req.headers.cookie).refreshToken : null; |
| | const token_provider = req.headers.cookie |
| | ? cookies.parse(req.headers.cookie).token_provider |
| | : null; |
| | if (!refreshToken) { |
| | return res.status(200).send('Refresh token not provided'); |
| | } |
| | if (token_provider === 'openid' && isEnabled(process.env.OPENID_REUSE_TOKENS) === true) { |
| | try { |
| | const openIdConfig = getOpenIdConfig(); |
| | const tokenset = await openIdClient.refreshTokenGrant(openIdConfig, refreshToken); |
| | const claims = tokenset.claims(); |
| | const { user, error } = await findOpenIDUser({ |
| | findUser, |
| | email: claims.email, |
| | openidId: claims.sub, |
| | idOnTheSource: claims.oid, |
| | strategyName: 'refreshController', |
| | }); |
| | if (error || !user) { |
| | return res.status(401).redirect('/login'); |
| | } |
| | const token = setOpenIDAuthTokens(tokenset, res, user._id.toString(), refreshToken); |
| |
|
| | user.federatedTokens = { |
| | access_token: tokenset.access_token, |
| | id_token: tokenset.id_token, |
| | refresh_token: refreshToken, |
| | expires_at: claims.exp, |
| | }; |
| |
|
| | return res.status(200).send({ token, user }); |
| | } catch (error) { |
| | logger.error('[refreshController] OpenID token refresh error', error); |
| | return res.status(403).send('Invalid OpenID refresh token'); |
| | } |
| | } |
| | try { |
| | const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET); |
| | const user = await getUserById(payload.id, '-password -__v -totpSecret -backupCodes'); |
| | if (!user) { |
| | return res.status(401).redirect('/login'); |
| | } |
| |
|
| | const userId = payload.id; |
| |
|
| | if (process.env.NODE_ENV === 'CI') { |
| | const token = await setAuthTokens(userId, res); |
| | return res.status(200).send({ token, user }); |
| | } |
| |
|
| | |
| | const session = await findSession( |
| | { |
| | userId: userId, |
| | refreshToken: refreshToken, |
| | }, |
| | { lean: false }, |
| | ); |
| |
|
| | if (session && session.expiration > new Date()) { |
| | const token = await setAuthTokens(userId, res, session); |
| |
|
| | |
| | try { |
| | void getOAuthReconnectionManager() |
| | .reconnectServers(userId) |
| | .catch((err) => { |
| | logger.error('[refreshController] Error reconnecting OAuth MCP servers:', err); |
| | }); |
| | } catch (err) { |
| | logger.warn(`[refreshController] Cannot attempt OAuth MCP servers reconnection:`, err); |
| | } |
| |
|
| | res.status(200).send({ token, user }); |
| | } else if (req?.query?.retry) { |
| | |
| | res.status(403).send('No session found'); |
| | } else if (payload.exp < Date.now() / 1000) { |
| | res.status(403).redirect('/login'); |
| | } else { |
| | res.status(401).send('Refresh token expired or not found for this user'); |
| | } |
| | } catch (err) { |
| | logger.error(`[refreshController] Invalid refresh token:`, err); |
| | res.status(403).send('Invalid refresh token'); |
| | } |
| | }; |
| |
|
| | const graphTokenController = async (req, res) => { |
| | try { |
| | |
| | if (!req.user.openidId || req.user.provider !== 'openid') { |
| | return res.status(403).json({ |
| | message: 'Microsoft Graph access requires Entra ID authentication', |
| | }); |
| | } |
| |
|
| | |
| | if (!isEnabled(process.env.OPENID_REUSE_TOKENS)) { |
| | return res.status(403).json({ |
| | message: 'SharePoint integration requires OpenID token reuse to be enabled', |
| | }); |
| | } |
| |
|
| | |
| | const authHeader = req.headers.authorization; |
| | if (!authHeader || !authHeader.startsWith('Bearer ')) { |
| | return res.status(401).json({ |
| | message: 'Valid authorization token required', |
| | }); |
| | } |
| |
|
| | |
| | const scopes = req.query.scopes; |
| | if (!scopes) { |
| | return res.status(400).json({ |
| | message: 'Graph API scopes are required as query parameter', |
| | }); |
| | } |
| |
|
| | const accessToken = authHeader.substring(7); |
| | const tokenResponse = await getGraphApiToken(req.user, accessToken, scopes); |
| |
|
| | res.json(tokenResponse); |
| | } catch (error) { |
| | logger.error('[graphTokenController] Failed to obtain Graph API token:', error); |
| | res.status(500).json({ |
| | message: 'Failed to obtain Microsoft Graph token', |
| | }); |
| | } |
| | }; |
| |
|
| | module.exports = { |
| | refreshController, |
| | registrationController, |
| | resetPasswordController, |
| | resetPasswordRequestController, |
| | graphTokenController, |
| | }; |
| |
|