| 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, |
| }; |
|
|