🔡 fix: Normalize Email Case When Issuing Verification Tokens (#14172)

The user schema lowercases emails on save, but sendVerificationEmail
stored the verification token with the raw registration email. For
mixed-case registrations, verifyEmail's lookup by the lowercased DB
email never matched the token, failing with 'No email verification
data found'. Normalize once and use it for the link, recipient, and
token. resendVerificationEmail already used the DB email and was
unaffected.

Co-authored-by: VictorEPlus <victor.ortega@eplusadvisor.com>
This commit is contained in:
VictorEPlus 2026-07-09 07:42:39 -05:00 • committed by GitHub
parent cf9a426d29
commit 9446f3278c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 5 deletions

View file

@ -218,16 +218,17 @@ const createTokenHash = () => {
*/
const sendVerificationEmail = async (user) => {
const [verifyToken, hash] = createTokenHash();
const email = user.email.toLowerCase();
const verificationLink = `${
domains.client
}/verify?token=${verifyToken}&email=${encodeURIComponent(user.email)}`;
}/verify?token=${verifyToken}&email=${encodeURIComponent(email)}`;
await sendEmail({
email: user.email,
email,
subject: 'Verify your email',
payload: {
appName: process.env.APP_TITLE || 'LibreChat',
name: user.name || user.username || user.email,
name: user.name || user.username || email,
verificationLink: verificationLink,
year: new Date().getFullYear(),
},
@ -236,14 +237,14 @@ const sendVerificationEmail = async (user) => {
await createToken({
userId: user._id,
email: user.email,
email,
type: AuthTokenTypes.EMAIL_VERIFICATION,
token: hash,
createdAt: Date.now(),
expiresIn: 900,
});
logger.info(`[sendVerificationEmail] Verification link issued. [Email: ${user.email}]`);
logger.info(`[sendVerificationEmail] Verification link issued. [Email: ${email}]`);
};
/**

View file

@ -471,6 +471,31 @@ describe('registerUser', () => {
}),
);
});
it('normalizes mixed-case emails when issuing the verification token and link', async () => {
checkEmailConfig.mockReturnValue(true);
const result = await registerUser({
...registrationPayload,
email: 'Mixed.Case@Example.com',
});
expect(result.status).toBe(200);
expect(createToken).toHaveBeenCalledWith(
expect.objectContaining({
email: 'mixed.case@example.com',
type: 'email_verification',
}),
);
expect(sendEmail).toHaveBeenCalledWith(
expect.objectContaining({
email: 'mixed.case@example.com',
payload: expect.objectContaining({
verificationLink: expect.stringContaining(encodeURIComponent('mixed.case@example.com')),
}),
}),
);
});
});
describe('verifyEmail public response handling', () => {