🔒 fix: Email Domain Validation Order and Coverage (#9566)

This commit is contained in:
Danny Avila 2025-09-10 23:13:39 -04:00 committed by GitHub
parent 85aa3e7d9c
commit 5676976564
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 69 additions and 15 deletions

View file

@ -11,18 +11,25 @@ const { getAppConfig } = require('~/server/services/Config');
* @param {Object} res - Express response object. * @param {Object} res - Express response object.
* @param {Function} next - Next middleware function. * @param {Function} next - Next middleware function.
* *
* @returns {Promise<function|Object>} - Returns a Promise which when resolved calls next middleware if the domain's email is allowed * @returns {Promise<void>} - Calls next middleware if the domain's email is allowed, otherwise redirects to login
*/ */
const checkDomainAllowed = async (req, res, next = () => {}) => { const checkDomainAllowed = async (req, res, next) => {
const email = req?.user?.email; try {
const appConfig = await getAppConfig({ const email = req?.user?.email;
role: req?.user?.role, const appConfig = await getAppConfig({
}); role: req?.user?.role,
if (email && !isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { });
logger.error(`[Social Login] [Social Login not allowed] [Email: ${email}]`);
return res.redirect('/login'); if (email && !isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
} else { logger.error(`[Social Login] [Social Login not allowed] [Email: ${email}]`);
return next(); res.redirect('/login');
return;
}
next();
} catch (error) {
logger.error('[checkDomainAllowed] Error checking domain:', error);
res.redirect('/login');
} }
}; };

View file

@ -26,9 +26,12 @@ const domains = {
router.use(logHeaders); router.use(logHeaders);
router.use(loginLimiter); router.use(loginLimiter);
const oauthHandler = async (req, res) => { const oauthHandler = async (req, res, next) => {
try { try {
await checkDomainAllowed(req, res); if (res.headersSent) {
return;
}
await checkBan(req, res); await checkBan(req, res);
if (req.banned) { if (req.banned) {
return; return;
@ -46,6 +49,7 @@ const oauthHandler = async (req, res) => {
res.redirect(domains.client); res.redirect(domains.client);
} catch (err) { } catch (err) {
logger.error('Error in setting authentication tokens:', err); logger.error('Error in setting authentication tokens:', err);
next(err);
} }
}; };
@ -79,6 +83,7 @@ router.get(
scope: ['openid', 'profile', 'email'], scope: ['openid', 'profile', 'email'],
}), }),
setBalanceConfig, setBalanceConfig,
checkDomainAllowed,
oauthHandler, oauthHandler,
); );
@ -104,6 +109,7 @@ router.get(
profileFields: ['id', 'email', 'name'], profileFields: ['id', 'email', 'name'],
}), }),
setBalanceConfig, setBalanceConfig,
checkDomainAllowed,
oauthHandler, oauthHandler,
); );
@ -125,6 +131,7 @@ router.get(
session: false, session: false,
}), }),
setBalanceConfig, setBalanceConfig,
checkDomainAllowed,
oauthHandler, oauthHandler,
); );
@ -148,6 +155,7 @@ router.get(
scope: ['user:email', 'read:user'], scope: ['user:email', 'read:user'],
}), }),
setBalanceConfig, setBalanceConfig,
checkDomainAllowed,
oauthHandler, oauthHandler,
); );
@ -171,6 +179,7 @@ router.get(
scope: ['identify', 'email'], scope: ['identify', 'email'],
}), }),
setBalanceConfig, setBalanceConfig,
checkDomainAllowed,
oauthHandler, oauthHandler,
); );
@ -192,6 +201,7 @@ router.post(
session: false, session: false,
}), }),
setBalanceConfig, setBalanceConfig,
checkDomainAllowed,
oauthHandler, oauthHandler,
); );

View file

@ -4,6 +4,7 @@ const { logger } = require('@librechat/data-schemas');
const { isEnabled, getBalanceConfig } = require('@librechat/api'); const { isEnabled, getBalanceConfig } = require('@librechat/api');
const { SystemRoles, ErrorTypes } = require('librechat-data-provider'); const { SystemRoles, ErrorTypes } = require('librechat-data-provider');
const { createUser, findUser, updateUser, countUsers } = require('~/models'); const { createUser, findUser, updateUser, countUsers } = require('~/models');
const { isEmailDomainAllowed } = require('~/server/services/domains');
const { getAppConfig } = require('~/server/services/Config'); const { getAppConfig } = require('~/server/services/Config');
const { const {
@ -124,6 +125,15 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
if (!user) { if (!user) {
const isFirstRegisteredUser = (await countUsers()) === 0; const isFirstRegisteredUser = (await countUsers()) === 0;
const role = isFirstRegisteredUser ? SystemRoles.ADMIN : SystemRoles.USER; const role = isFirstRegisteredUser ? SystemRoles.ADMIN : SystemRoles.USER;
const appConfig = await getAppConfig({ role });
if (!isEmailDomainAllowed(mail, appConfig?.registration?.allowedDomains)) {
logger.error(
`[LDAP Strategy] Registration blocked - email domain not allowed [Email: ${mail}]`,
);
return done(null, false, { message: 'Email domain not allowed for registration' });
}
user = { user = {
provider: 'ldap', provider: 'ldap',
ldapId, ldapId,
@ -133,7 +143,6 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
name: fullName, name: fullName,
role, role,
}; };
const appConfig = await getAppConfig({ role: user?.role });
const balanceConfig = getBalanceConfig(appConfig); const balanceConfig = getBalanceConfig(appConfig);
const userId = await createUser(user, balanceConfig); const userId = await createUser(user, balanceConfig);
user._id = userId; user._id = userId;

View file

@ -15,6 +15,7 @@ const {
getBalanceConfig, getBalanceConfig,
} = require('@librechat/api'); } = require('@librechat/api');
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { isEmailDomainAllowed } = require('~/server/services/domains');
const { findUser, createUser, updateUser } = require('~/models'); const { findUser, createUser, updateUser } = require('~/models');
const { getAppConfig } = require('~/server/services/Config'); const { getAppConfig } = require('~/server/services/Config');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
@ -400,6 +401,13 @@ async function setupOpenId() {
const appConfig = await getAppConfig(); const appConfig = await getAppConfig();
if (!user) { if (!user) {
if (!isEmailDomainAllowed(userinfo.email, appConfig?.registration?.allowedDomains)) {
logger.error(
`[OpenID Strategy] Registration blocked - email domain not allowed [Email: ${userinfo.email}]`,
);
return done(null, false, { message: 'Email domain not allowed for registration' });
}
user = { user = {
provider: 'openid', provider: 'openid',
openidId: userinfo.sub, openidId: userinfo.sub,

View file

@ -7,6 +7,7 @@ const { ErrorTypes } = require('librechat-data-provider');
const { hashToken, logger } = require('@librechat/data-schemas'); const { hashToken, logger } = require('@librechat/data-schemas');
const { Strategy: SamlStrategy } = require('@node-saml/passport-saml'); const { Strategy: SamlStrategy } = require('@node-saml/passport-saml');
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { isEmailDomainAllowed } = require('~/server/services/domains');
const { findUser, createUser, updateUser } = require('~/models'); const { findUser, createUser, updateUser } = require('~/models');
const { getAppConfig } = require('~/server/services/Config'); const { getAppConfig } = require('~/server/services/Config');
const paths = require('~/config/paths'); const paths = require('~/config/paths');
@ -222,11 +223,19 @@ async function setupSaml() {
const appConfig = await getAppConfig(); const appConfig = await getAppConfig();
if (!user) { if (!user) {
const userEmail = getEmail(profile) || '';
if (!isEmailDomainAllowed(userEmail, appConfig?.registration?.allowedDomains)) {
logger.error(
`[SAML Strategy] Registration blocked - email domain not allowed [Email: ${userEmail}]`,
);
return done(null, false, { message: 'Email domain not allowed for registration' });
}
user = { user = {
provider: 'saml', provider: 'saml',
samlId: profile.nameID, samlId: profile.nameID,
username, username,
email: getEmail(profile) || '', email: userEmail,
emailVerified: true, emailVerified: true,
name: fullName, name: fullName,
}; };

View file

@ -2,6 +2,7 @@ const { isEnabled } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { ErrorTypes } = require('librechat-data-provider'); const { ErrorTypes } = require('librechat-data-provider');
const { createSocialUser, handleExistingUser } = require('./process'); const { createSocialUser, handleExistingUser } = require('./process');
const { isEmailDomainAllowed } = require('~/server/services/domains');
const { getAppConfig } = require('~/server/services/Config'); const { getAppConfig } = require('~/server/services/Config');
const { findUser } = require('~/models'); const { findUser } = require('~/models');
@ -17,6 +18,16 @@ const socialLogin =
const existingUser = await findUser({ email: email.trim() }); const existingUser = await findUser({ email: email.trim() });
const ALLOW_SOCIAL_REGISTRATION = isEnabled(process.env.ALLOW_SOCIAL_REGISTRATION); const ALLOW_SOCIAL_REGISTRATION = isEnabled(process.env.ALLOW_SOCIAL_REGISTRATION);
if (!existingUser && !isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
logger.error(
`[${provider}Login] Registration blocked - email domain not allowed [Email: ${email}]`,
);
const error = new Error(ErrorTypes.AUTH_FAILED);
error.code = ErrorTypes.AUTH_FAILED;
error.message = 'Email domain not allowed for registration';
return cb(error);
}
if (existingUser?.provider === provider) { if (existingUser?.provider === provider) {
await handleExistingUser(existingUser, avatarUrl, appConfig); await handleExistingUser(existingUser, avatarUrl, appConfig);
return cb(null, existingUser); return cb(null, existingUser);