mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-09 03:58:51 +01:00
246 lines
5.7 KiB
JavaScript
246 lines
5.7 KiB
JavaScript
// file deepcode ignore NoRateLimitingForLogin: Rate limiting is handled by the `loginLimiter` middleware
|
|
const express = require('express');
|
|
const jwt = require('jsonwebtoken');
|
|
const passport = require('passport');
|
|
const client = require('openid-client');
|
|
const {
|
|
checkBan,
|
|
logHeaders,
|
|
loginLimiter,
|
|
setBalanceConfig,
|
|
checkDomainAllowed,
|
|
} = require('~/server/middleware');
|
|
const { setAuthTokens, setOpenIDAuthTokens } = require('~/server/services/AuthService');
|
|
const { logger } = require('~/config');
|
|
const { isEnabled } = require('~/server/utils');
|
|
|
|
const router = express.Router();
|
|
|
|
const domains = {
|
|
client: process.env.DOMAIN_CLIENT,
|
|
server: process.env.DOMAIN_SERVER,
|
|
};
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET || process.env.OPENID_SESSION_SECRET;
|
|
|
|
router.use(logHeaders);
|
|
router.use(loginLimiter);
|
|
|
|
const oauthHandler = async (req, res) => {
|
|
try {
|
|
await checkDomainAllowed(req, res);
|
|
await checkBan(req, res);
|
|
if (req.banned) {
|
|
return;
|
|
}
|
|
if (
|
|
req.user &&
|
|
req.user.provider == 'openid' &&
|
|
isEnabled(process.env.OPENID_REUSE_TOKENS) === true
|
|
) {
|
|
setOpenIDAuthTokens(req.user.tokenset, res);
|
|
} else {
|
|
await setAuthTokens(req.user._id, res);
|
|
}
|
|
res.redirect(domains.client);
|
|
} catch (err) {
|
|
logger.error('Error in setting authentication tokens:', err);
|
|
}
|
|
};
|
|
|
|
router.get('/error', (req, res) => {
|
|
// A single error message is pushed by passport when authentication fails.
|
|
logger.error('Error in OAuth authentication:', { message: req.session.messages.pop() });
|
|
|
|
// Redirect to login page with auth_failed parameter to prevent infinite redirect loops
|
|
res.redirect(`${domains.client}/login?redirect=false`);
|
|
});
|
|
|
|
/**
|
|
* Google Routes
|
|
*/
|
|
router.get(
|
|
'/google',
|
|
passport.authenticate('google', {
|
|
scope: ['openid', 'profile', 'email'],
|
|
session: false,
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/google/callback',
|
|
passport.authenticate('google', {
|
|
failureRedirect: `${domains.client}/oauth/error`,
|
|
failureMessage: true,
|
|
session: false,
|
|
scope: ['openid', 'profile', 'email'],
|
|
}),
|
|
setBalanceConfig,
|
|
oauthHandler,
|
|
);
|
|
|
|
/**
|
|
* Facebook Routes
|
|
*/
|
|
router.get(
|
|
'/facebook',
|
|
passport.authenticate('facebook', {
|
|
scope: ['public_profile'],
|
|
profileFields: ['id', 'email', 'name'],
|
|
session: false,
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/facebook/callback',
|
|
passport.authenticate('facebook', {
|
|
failureRedirect: `${domains.client}/oauth/error`,
|
|
failureMessage: true,
|
|
session: false,
|
|
scope: ['public_profile'],
|
|
profileFields: ['id', 'email', 'name'],
|
|
}),
|
|
setBalanceConfig,
|
|
oauthHandler,
|
|
);
|
|
|
|
/**
|
|
* OpenID Routes
|
|
*/
|
|
router.get('/openid', (req, res, next) => {
|
|
const state = client.randomState();
|
|
|
|
try {
|
|
const stateToken = jwt.sign(
|
|
{
|
|
state: state,
|
|
timestamp: Date.now(),
|
|
},
|
|
JWT_SECRET,
|
|
{ expiresIn: '10m' },
|
|
);
|
|
|
|
res.cookie('oauth_state', stateToken, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
signed: false,
|
|
maxAge: 10 * 60 * 1000,
|
|
sameSite: 'lax',
|
|
});
|
|
passport.authenticate('openid', {
|
|
session: false,
|
|
state: state,
|
|
})(req, res, next);
|
|
} catch (error) {
|
|
logger.error('Error creating state token for OpenID authentication', error);
|
|
return res.redirect(`${domains.client}/oauth/error`);
|
|
}
|
|
});
|
|
|
|
router.get(
|
|
'/openid/callback',
|
|
(req, res, next) => {
|
|
if (!req.query.state) {
|
|
logger.error('Missing state parameter in OpenID callback');
|
|
return res.redirect(`${domains.client}/oauth/error`);
|
|
}
|
|
|
|
const stateToken = req.cookies.oauth_state;
|
|
if (!stateToken) {
|
|
logger.error('No state cookie found for OpenID callback');
|
|
return res.redirect(`${domains.client}/oauth/error`);
|
|
}
|
|
|
|
try {
|
|
const decodedState = jwt.verify(stateToken, JWT_SECRET);
|
|
if (req.query.state !== decodedState.state) {
|
|
logger.error('Invalid state parameter in OpenID callback', {
|
|
received: req.query.state,
|
|
expected: decodedState.state,
|
|
});
|
|
return res.redirect(`${domains.client}/oauth/error`);
|
|
}
|
|
res.clearCookie('oauth_state');
|
|
passport.authenticate('openid', {
|
|
failureRedirect: `${domains.client}/oauth/error`,
|
|
failureMessage: true,
|
|
session: false,
|
|
})(req, res, next);
|
|
} catch (error) {
|
|
logger.error('Invalid or expired state token in OpenID callback', error);
|
|
res.clearCookie('oauth_state');
|
|
return res.redirect(`${domains.client}/oauth/error`);
|
|
}
|
|
},
|
|
setBalanceConfig,
|
|
oauthHandler,
|
|
);
|
|
|
|
/**
|
|
* GitHub Routes
|
|
*/
|
|
router.get(
|
|
'/github',
|
|
passport.authenticate('github', {
|
|
scope: ['user:email', 'read:user'],
|
|
session: false,
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/github/callback',
|
|
passport.authenticate('github', {
|
|
failureRedirect: `${domains.client}/oauth/error`,
|
|
failureMessage: true,
|
|
session: false,
|
|
scope: ['user:email', 'read:user'],
|
|
}),
|
|
setBalanceConfig,
|
|
oauthHandler,
|
|
);
|
|
|
|
/**
|
|
* Discord Routes
|
|
*/
|
|
router.get(
|
|
'/discord',
|
|
passport.authenticate('discord', {
|
|
scope: ['identify', 'email'],
|
|
session: false,
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/discord/callback',
|
|
passport.authenticate('discord', {
|
|
failureRedirect: `${domains.client}/oauth/error`,
|
|
failureMessage: true,
|
|
session: false,
|
|
scope: ['identify', 'email'],
|
|
}),
|
|
setBalanceConfig,
|
|
oauthHandler,
|
|
);
|
|
|
|
/**
|
|
* Apple Routes
|
|
*/
|
|
router.get(
|
|
'/apple',
|
|
passport.authenticate('apple', {
|
|
session: false,
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
'/apple/callback',
|
|
passport.authenticate('apple', {
|
|
failureRedirect: `${domains.client}/oauth/error`,
|
|
failureMessage: true,
|
|
session: false,
|
|
}),
|
|
setBalanceConfig,
|
|
oauthHandler,
|
|
);
|
|
|
|
module.exports = router;
|