mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
🔒 refactor: Optimize Email Domain Validation in OpenID, SAML, and Social Logins (#9567)
* refactor: Optimize Email Domain Validation in OpenID, SAML, and Social Login Strategies - Implemented email domain validation for user authentication in OpenID and SAML strategies, ensuring only allowed domains are processed. - Adjusted error messages for clarity and consistency across authentication methods. - Refactored social login to validate email domains before checking for existing users, improving registration flow. * refactor: Email Domain Validation in LDAP and Social Login Strategies
This commit is contained in:
parent
5676976564
commit
d91f34dd42
5 changed files with 78 additions and 60 deletions
|
@ -3,12 +3,12 @@ const jwt = require('jsonwebtoken');
|
||||||
const { webcrypto } = require('node:crypto');
|
const { webcrypto } = require('node:crypto');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { isEnabled, checkEmailConfig } = require('@librechat/api');
|
const { isEnabled, checkEmailConfig } = require('@librechat/api');
|
||||||
const { SystemRoles, errorsToString } = require('librechat-data-provider');
|
const { ErrorTypes, SystemRoles, errorsToString } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
findUser,
|
findUser,
|
||||||
|
findToken,
|
||||||
createUser,
|
createUser,
|
||||||
updateUser,
|
updateUser,
|
||||||
findToken,
|
|
||||||
countUsers,
|
countUsers,
|
||||||
getUserById,
|
getUserById,
|
||||||
findSession,
|
findSession,
|
||||||
|
@ -181,6 +181,14 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
|
|
||||||
let newUserId;
|
let newUserId;
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig();
|
||||||
|
if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
|
||||||
|
const errorMessage =
|
||||||
|
'The email address provided cannot be used. Please use a different email address.';
|
||||||
|
logger.error(`[registerUser] [Registration not allowed] [Email: ${user.email}]`);
|
||||||
|
return { status: 403, message: errorMessage };
|
||||||
|
}
|
||||||
|
|
||||||
const existingUser = await findUser({ email }, 'email _id');
|
const existingUser = await findUser({ email }, 'email _id');
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
|
@ -195,14 +203,6 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
return { status: 200, message: genericVerificationMessage };
|
return { status: 200, message: genericVerificationMessage };
|
||||||
}
|
}
|
||||||
|
|
||||||
const appConfig = await getAppConfig({ role: user.role });
|
|
||||||
if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
|
|
||||||
const errorMessage =
|
|
||||||
'The email address provided cannot be used. Please use a different email address.';
|
|
||||||
logger.error(`[registerUser] [Registration not allowed] [Email: ${user.email}]`);
|
|
||||||
return { status: 403, message: errorMessage };
|
|
||||||
}
|
|
||||||
|
|
||||||
//determine if this is the first registered user (not counting anonymous_user)
|
//determine if this is the first registered user (not counting anonymous_user)
|
||||||
const isFirstRegisteredUser = (await countUsers()) === 0;
|
const isFirstRegisteredUser = (await countUsers()) === 0;
|
||||||
|
|
||||||
|
@ -252,6 +252,13 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
*/
|
*/
|
||||||
const requestPasswordReset = async (req) => {
|
const requestPasswordReset = async (req) => {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
|
const appConfig = await getAppConfig();
|
||||||
|
if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
|
||||||
|
const error = new Error(ErrorTypes.AUTH_FAILED);
|
||||||
|
error.code = ErrorTypes.AUTH_FAILED;
|
||||||
|
error.message = 'Email domain not allowed';
|
||||||
|
return error;
|
||||||
|
}
|
||||||
const user = await findUser({ email }, 'email _id');
|
const user = await findUser({ email }, 'email _id');
|
||||||
const emailEnabled = checkEmailConfig();
|
const emailEnabled = checkEmailConfig();
|
||||||
|
|
||||||
|
|
|
@ -122,17 +122,17 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig();
|
||||||
|
if (!isEmailDomainAllowed(mail, appConfig?.registration?.allowedDomains)) {
|
||||||
|
logger.error(
|
||||||
|
`[LDAP Strategy] Authentication blocked - email domain not allowed [Email: ${mail}]`,
|
||||||
|
);
|
||||||
|
return done(null, false, { message: 'Email domain not allowed' });
|
||||||
|
}
|
||||||
|
|
||||||
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',
|
||||||
|
|
|
@ -340,6 +340,19 @@ async function setupOpenId() {
|
||||||
async (tokenset, done) => {
|
async (tokenset, done) => {
|
||||||
try {
|
try {
|
||||||
const claims = tokenset.claims();
|
const claims = tokenset.claims();
|
||||||
|
const userinfo = {
|
||||||
|
...claims,
|
||||||
|
...(await getUserInfo(openidConfig, tokenset.access_token, claims.sub)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig();
|
||||||
|
if (!isEmailDomainAllowed(userinfo.email, appConfig?.registration?.allowedDomains)) {
|
||||||
|
logger.error(
|
||||||
|
`[OpenID Strategy] Authentication blocked - email domain not allowed [Email: ${userinfo.email}]`,
|
||||||
|
);
|
||||||
|
return done(null, false, { message: 'Email domain not allowed' });
|
||||||
|
}
|
||||||
|
|
||||||
const result = await findOpenIDUser({
|
const result = await findOpenIDUser({
|
||||||
openidId: claims.sub,
|
openidId: claims.sub,
|
||||||
email: claims.email,
|
email: claims.email,
|
||||||
|
@ -354,10 +367,7 @@ async function setupOpenId() {
|
||||||
message: ErrorTypes.AUTH_FAILED,
|
message: ErrorTypes.AUTH_FAILED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const userinfo = {
|
|
||||||
...claims,
|
|
||||||
...(await getUserInfo(openidConfig, tokenset.access_token, claims.sub)),
|
|
||||||
};
|
|
||||||
const fullName = getFullName(userinfo);
|
const fullName = getFullName(userinfo);
|
||||||
|
|
||||||
if (requiredRole) {
|
if (requiredRole) {
|
||||||
|
@ -399,15 +409,7 @@ async function setupOpenId() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
|
|
@ -193,16 +193,25 @@ async function setupSaml() {
|
||||||
logger.info(`[samlStrategy] SAML authentication received for NameID: ${profile.nameID}`);
|
logger.info(`[samlStrategy] SAML authentication received for NameID: ${profile.nameID}`);
|
||||||
logger.debug('[samlStrategy] SAML profile:', profile);
|
logger.debug('[samlStrategy] SAML profile:', profile);
|
||||||
|
|
||||||
|
const userEmail = getEmail(profile) || '';
|
||||||
|
const appConfig = await getAppConfig();
|
||||||
|
|
||||||
|
if (!isEmailDomainAllowed(userEmail, appConfig?.registration?.allowedDomains)) {
|
||||||
|
logger.error(
|
||||||
|
`[SAML Strategy] Authentication blocked - email domain not allowed [Email: ${userEmail}]`,
|
||||||
|
);
|
||||||
|
return done(null, false, { message: 'Email domain not allowed' });
|
||||||
|
}
|
||||||
|
|
||||||
let user = await findUser({ samlId: profile.nameID });
|
let user = await findUser({ samlId: profile.nameID });
|
||||||
logger.info(
|
logger.info(
|
||||||
`[samlStrategy] User ${user ? 'found' : 'not found'} with SAML ID: ${profile.nameID}`,
|
`[samlStrategy] User ${user ? 'found' : 'not found'} with SAML ID: ${profile.nameID}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
const email = getEmail(profile) || '';
|
user = await findUser({ email: userEmail });
|
||||||
user = await findUser({ email });
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`[samlStrategy] User ${user ? 'found' : 'not found'} with email: ${profile.email}`,
|
`[samlStrategy] User ${user ? 'found' : 'not found'} with email: ${userEmail}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,16 +230,7 @@ async function setupSaml() {
|
||||||
getUserName(profile) || getGivenName(profile) || getEmail(profile),
|
getUserName(profile) || getGivenName(profile) || getEmail(profile),
|
||||||
);
|
);
|
||||||
|
|
||||||
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,
|
||||||
|
|
|
@ -15,19 +15,19 @@ const socialLogin =
|
||||||
});
|
});
|
||||||
|
|
||||||
const appConfig = await getAppConfig();
|
const appConfig = await getAppConfig();
|
||||||
const existingUser = await findUser({ email: email.trim() });
|
|
||||||
const ALLOW_SOCIAL_REGISTRATION = isEnabled(process.env.ALLOW_SOCIAL_REGISTRATION);
|
|
||||||
|
|
||||||
if (!existingUser && !isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
|
if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`[${provider}Login] Registration blocked - email domain not allowed [Email: ${email}]`,
|
`[${provider}Login] Authentication blocked - email domain not allowed [Email: ${email}]`,
|
||||||
);
|
);
|
||||||
const error = new Error(ErrorTypes.AUTH_FAILED);
|
const error = new Error(ErrorTypes.AUTH_FAILED);
|
||||||
error.code = ErrorTypes.AUTH_FAILED;
|
error.code = ErrorTypes.AUTH_FAILED;
|
||||||
error.message = 'Email domain not allowed for registration';
|
error.message = 'Email domain not allowed';
|
||||||
return cb(error);
|
return cb(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingUser = await findUser({ email: email.trim() });
|
||||||
|
|
||||||
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);
|
||||||
|
@ -41,20 +41,29 @@ const socialLogin =
|
||||||
return cb(error);
|
return cb(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ALLOW_SOCIAL_REGISTRATION) {
|
const ALLOW_SOCIAL_REGISTRATION = isEnabled(process.env.ALLOW_SOCIAL_REGISTRATION);
|
||||||
const newUser = await createSocialUser({
|
if (!ALLOW_SOCIAL_REGISTRATION) {
|
||||||
email,
|
logger.error(
|
||||||
avatarUrl,
|
`[${provider}Login] Registration blocked - social registration is disabled [Email: ${email}]`,
|
||||||
provider,
|
);
|
||||||
providerKey: `${provider}Id`,
|
const error = new Error(ErrorTypes.AUTH_FAILED);
|
||||||
providerId: id,
|
error.code = ErrorTypes.AUTH_FAILED;
|
||||||
username,
|
error.message = 'Social registration is disabled';
|
||||||
name,
|
return cb(error);
|
||||||
emailVerified,
|
|
||||||
appConfig,
|
|
||||||
});
|
|
||||||
return cb(null, newUser);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newUser = await createSocialUser({
|
||||||
|
email,
|
||||||
|
avatarUrl,
|
||||||
|
provider,
|
||||||
|
providerKey: `${provider}Id`,
|
||||||
|
providerId: id,
|
||||||
|
username,
|
||||||
|
name,
|
||||||
|
emailVerified,
|
||||||
|
appConfig,
|
||||||
|
});
|
||||||
|
return cb(null, newUser);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[${provider}Login]`, err);
|
logger.error(`[${provider}Login]`, err);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue