From 28decb54eea9f7a86fa6d4c46d0c6238de85f67f Mon Sep 17 00:00:00 2001 From: Rashid Narikkodan Date: Mon, 6 Apr 2026 11:39:13 +0530 Subject: [PATCH 1/4] fix(email): make domain validation case-insensitive Normalize email domain to lowercase before validation. Normalize allowed and blocked domain lists at ingestion time. Normalize default blocked domains to ensure consistent comparison. Replace array lookups with Set for O(1) performance. Fixes incorrect rejection of valid emails like test@GMAIL.com when gmail.com is allowed. Fixes #40045 --- .../app/lib/server/lib/validateEmailDomain.js | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/validateEmailDomain.js b/apps/meteor/app/lib/server/lib/validateEmailDomain.js index d00a8808e8aee..4ece344772ca7 100644 --- a/apps/meteor/app/lib/server/lib/validateEmailDomain.js +++ b/apps/meteor/app/lib/server/lib/validateEmailDomain.js @@ -9,33 +9,47 @@ import { settings } from '../../../settings/server'; const dnsResolveMx = util.promisify(dns.resolveMx); -let emailDomainBlackList = []; -let emailDomainWhiteList = []; +yarn devlet emailDomainBlackSet = new Set(); +let emailDomainWhiteSet = new Set(); +let defaultBlackSet = new Set(); +// Normalization helper (single source of truth) +const normalize = (domain) => domain.trim().toLowerCase(); + +// Normalize default blacklist once at startup +defaultBlackSet = new Set(emailDomainDefaultBlackList.map(normalize)); + +// Watch for admin config changes → normalize at ingestion settings.watch('Accounts_BlockedDomainsList', (value) => { if (!value) { - emailDomainBlackList = []; + emailDomainBlackSet = new Set(); return; } - emailDomainBlackList = value - .split(',') - .filter(Boolean) - .map((domain) => domain.trim()); + emailDomainBlackSet = new Set( + value + .split(',') + .filter(Boolean) + .map(normalize) + ); }); + settings.watch('Accounts_AllowedDomainsList', (value) => { if (!value) { - emailDomainWhiteList = []; + emailDomainWhiteSet = new Set(); return; } - emailDomainWhiteList = value - .split(',') - .filter(Boolean) - .map((domain) => domain.trim()); + emailDomainWhiteSet = new Set( + value + .split(',') + .filter(Boolean) + .map(normalize) + ); }); export const validateEmailDomain = async function (email) { + // Step 1: Validate basic email format if (!validateEmail(email)) { throw new Meteor.Error('error-invalid-email', `Invalid email ${email}`, { function: 'RocketChat.validateEmailDomain', @@ -43,23 +57,40 @@ export const validateEmailDomain = async function (email) { }); } - const emailDomain = email.substr(email.lastIndexOf('@') + 1); + // Step 2: Extract + normalize domain + const atIndex = email.lastIndexOf('@'); + if (atIndex === -1) { + throw new Meteor.Error('error-invalid-email', `Invalid email ${email}`, { + function: 'RocketChat.validateEmailDomain', + }); + } - if (emailDomainWhiteList.length && !emailDomainWhiteList.includes(emailDomain)) { + const emailDomain = normalize(email.slice(atIndex + 1)); + + // Step 3: Whitelist enforcement + if (emailDomainWhiteSet.size > 0 && !emailDomainWhiteSet.has(emailDomain)) { throw new Meteor.Error('error-invalid-domain', 'The email domain is not in whitelist', { function: 'RocketChat.validateEmailDomain', }); } + + // Step 4: Blacklist enforcement (takes priority) if ( - emailDomainBlackList.length && - (emailDomainBlackList.indexOf(emailDomain) !== -1 || - (settings.get('Accounts_UseDefaultBlockedDomainsList') && emailDomainDefaultBlackList.indexOf(emailDomain) !== -1)) + emailDomainBlackSet.size > 0 && + ( + emailDomainBlackSet.has(emailDomain) || + ( + settings.get('Accounts_UseDefaultBlockedDomainsList') && + defaultBlackSet.has(emailDomain) + ) + ) ) { throw new Meteor.Error('error-email-domain-blacklisted', 'The email domain is blacklisted', { function: 'RocketChat.validateEmailDomain', }); } + // Step 5: DNS validation (optional) if (settings.get('Accounts_UseDNSDomainCheck')) { try { await dnsResolveMx(emailDomain); @@ -69,4 +100,4 @@ export const validateEmailDomain = async function (email) { }); } } -}; +}; \ No newline at end of file From 5b92e256ec1c63afc430a67843712394e96e658b Mon Sep 17 00:00:00 2001 From: Rashid Narikkodan Date: Mon, 6 Apr 2026 12:25:33 +0530 Subject: [PATCH 2/4] fix(email): remove malformed declaration causing syntax error --- apps/meteor/app/lib/server/lib/validateEmailDomain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/lib/server/lib/validateEmailDomain.js b/apps/meteor/app/lib/server/lib/validateEmailDomain.js index 4ece344772ca7..ac7bf02397937 100644 --- a/apps/meteor/app/lib/server/lib/validateEmailDomain.js +++ b/apps/meteor/app/lib/server/lib/validateEmailDomain.js @@ -9,7 +9,7 @@ import { settings } from '../../../settings/server'; const dnsResolveMx = util.promisify(dns.resolveMx); -yarn devlet emailDomainBlackSet = new Set(); +let emailDomainBlackSet = new Set(); let emailDomainWhiteSet = new Set(); let defaultBlackSet = new Set(); From df2bdd9015867d0ea72be19529c6a1eb4bee2845 Mon Sep 17 00:00:00 2001 From: Rashid Narikkodan Date: Mon, 6 Apr 2026 22:43:46 +0530 Subject: [PATCH 3/4] fix: format fixes and coderabbitai proposed fix(Do not disable the default blacklist when the custom list is empty.) --- .../app/lib/server/lib/validateEmailDomain.js | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/validateEmailDomain.js b/apps/meteor/app/lib/server/lib/validateEmailDomain.js index ac7bf02397937..c0cb369098912 100644 --- a/apps/meteor/app/lib/server/lib/validateEmailDomain.js +++ b/apps/meteor/app/lib/server/lib/validateEmailDomain.js @@ -26,12 +26,7 @@ settings.watch('Accounts_BlockedDomainsList', (value) => { return; } - emailDomainBlackSet = new Set( - value - .split(',') - .filter(Boolean) - .map(normalize) - ); + emailDomainBlackSet = new Set(value.split(',').filter(Boolean).map(normalize)); }); settings.watch('Accounts_AllowedDomainsList', (value) => { @@ -40,12 +35,7 @@ settings.watch('Accounts_AllowedDomainsList', (value) => { return; } - emailDomainWhiteSet = new Set( - value - .split(',') - .filter(Boolean) - .map(normalize) - ); + emailDomainWhiteSet = new Set(value.split(',').filter(Boolean).map(normalize)); }); export const validateEmailDomain = async function (email) { @@ -75,16 +65,7 @@ export const validateEmailDomain = async function (email) { } // Step 4: Blacklist enforcement (takes priority) - if ( - emailDomainBlackSet.size > 0 && - ( - emailDomainBlackSet.has(emailDomain) || - ( - settings.get('Accounts_UseDefaultBlockedDomainsList') && - defaultBlackSet.has(emailDomain) - ) - ) - ) { + if (emailDomainBlackSet.has(emailDomain) || (settings.get('Accounts_UseDefaultBlockedDomainsList') && defaultBlackSet.has(emailDomain))) { throw new Meteor.Error('error-email-domain-blacklisted', 'The email domain is blacklisted', { function: 'RocketChat.validateEmailDomain', }); @@ -100,4 +81,4 @@ export const validateEmailDomain = async function (email) { }); } } -}; \ No newline at end of file +}; From cfd1f13d99ab05e3268c123078694dc0cfb637a5 Mon Sep 17 00:00:00 2001 From: Rashid Narikkodan Date: Mon, 6 Apr 2026 22:47:28 +0530 Subject: [PATCH 4/4] fix:Filter after normalization so blank entries do not survive. --- apps/meteor/app/lib/server/lib/validateEmailDomain.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/validateEmailDomain.js b/apps/meteor/app/lib/server/lib/validateEmailDomain.js index c0cb369098912..8d707f9fa7e2b 100644 --- a/apps/meteor/app/lib/server/lib/validateEmailDomain.js +++ b/apps/meteor/app/lib/server/lib/validateEmailDomain.js @@ -26,7 +26,7 @@ settings.watch('Accounts_BlockedDomainsList', (value) => { return; } - emailDomainBlackSet = new Set(value.split(',').filter(Boolean).map(normalize)); + emailDomainBlackSet = new Set(value.split(',').map(normalize).filter(Boolean)); }); settings.watch('Accounts_AllowedDomainsList', (value) => { @@ -35,7 +35,7 @@ settings.watch('Accounts_AllowedDomainsList', (value) => { return; } - emailDomainWhiteSet = new Set(value.split(',').filter(Boolean).map(normalize)); + emailDomainWhiteSet = new Set(value.split(',').map(normalize).filter(Boolean)); }); export const validateEmailDomain = async function (email) {