mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-17 07:58:08 +01:00
📬 refactor: Normalize Email Handling in User Methods (#10743)
- Updated the `findUser` method to normalize email fields to lowercase and trimmed whitespace for case-insensitive matching. - Enhanced the `normalizeEmailInCriteria` function to handle email normalization in search criteria, including `` conditions. - Added tests to ensure email normalization works correctly across various scenarios, including case differences and whitespace handling.
This commit is contained in:
parent
d7ce19e15a
commit
d5d362e52b
7 changed files with 847 additions and 4 deletions
|
|
@ -617,4 +617,171 @@ describe('Token Methods - Detailed Tests', () => {
|
|||
expect(remainingTokens.find((t) => t.token === 'email-verify-token-2')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Email Normalization', () => {
|
||||
let normUserId: mongoose.Types.ObjectId;
|
||||
|
||||
beforeEach(async () => {
|
||||
normUserId = new mongoose.Types.ObjectId();
|
||||
|
||||
// Create token with lowercase email (as stored in DB)
|
||||
await Token.create({
|
||||
token: 'norm-token-1',
|
||||
userId: normUserId,
|
||||
email: 'john.doe@example.com',
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(Date.now() + 3600000),
|
||||
});
|
||||
});
|
||||
|
||||
describe('findToken email normalization', () => {
|
||||
test('should find token by email with different case (case-insensitive)', async () => {
|
||||
const foundUpper = await methods.findToken({ email: 'JOHN.DOE@EXAMPLE.COM' });
|
||||
const foundMixed = await methods.findToken({ email: 'John.Doe@Example.COM' });
|
||||
const foundLower = await methods.findToken({ email: 'john.doe@example.com' });
|
||||
|
||||
expect(foundUpper).toBeDefined();
|
||||
expect(foundUpper?.token).toBe('norm-token-1');
|
||||
|
||||
expect(foundMixed).toBeDefined();
|
||||
expect(foundMixed?.token).toBe('norm-token-1');
|
||||
|
||||
expect(foundLower).toBeDefined();
|
||||
expect(foundLower?.token).toBe('norm-token-1');
|
||||
});
|
||||
|
||||
test('should find token by email with leading/trailing whitespace', async () => {
|
||||
const foundWithSpaces = await methods.findToken({ email: ' john.doe@example.com ' });
|
||||
const foundWithTabs = await methods.findToken({ email: '\tjohn.doe@example.com\t' });
|
||||
|
||||
expect(foundWithSpaces).toBeDefined();
|
||||
expect(foundWithSpaces?.token).toBe('norm-token-1');
|
||||
|
||||
expect(foundWithTabs).toBeDefined();
|
||||
expect(foundWithTabs?.token).toBe('norm-token-1');
|
||||
});
|
||||
|
||||
test('should find token by email with both case difference and whitespace', async () => {
|
||||
const found = await methods.findToken({ email: ' JOHN.DOE@EXAMPLE.COM ' });
|
||||
|
||||
expect(found).toBeDefined();
|
||||
expect(found?.token).toBe('norm-token-1');
|
||||
});
|
||||
|
||||
test('should find token with combined email and other criteria', async () => {
|
||||
const found = await methods.findToken({
|
||||
userId: normUserId.toString(),
|
||||
email: 'John.Doe@Example.COM',
|
||||
});
|
||||
|
||||
expect(found).toBeDefined();
|
||||
expect(found?.token).toBe('norm-token-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteTokens email normalization', () => {
|
||||
test('should delete token by email with different case', async () => {
|
||||
const result = await methods.deleteTokens({ email: 'JOHN.DOE@EXAMPLE.COM' });
|
||||
|
||||
expect(result.deletedCount).toBe(1);
|
||||
|
||||
const remaining = await Token.find({});
|
||||
expect(remaining).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should delete token by email with whitespace', async () => {
|
||||
const result = await methods.deleteTokens({ email: ' john.doe@example.com ' });
|
||||
|
||||
expect(result.deletedCount).toBe(1);
|
||||
|
||||
const remaining = await Token.find({});
|
||||
expect(remaining).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should delete token by email with case and whitespace combined', async () => {
|
||||
const result = await methods.deleteTokens({ email: ' John.Doe@EXAMPLE.COM ' });
|
||||
|
||||
expect(result.deletedCount).toBe(1);
|
||||
|
||||
const remaining = await Token.find({});
|
||||
expect(remaining).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should only delete matching token when using normalized email', async () => {
|
||||
// Create additional token with different email
|
||||
await Token.create({
|
||||
token: 'norm-token-2',
|
||||
userId: new mongoose.Types.ObjectId(),
|
||||
email: 'jane.doe@example.com',
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(Date.now() + 3600000),
|
||||
});
|
||||
|
||||
const result = await methods.deleteTokens({ email: 'JOHN.DOE@EXAMPLE.COM' });
|
||||
|
||||
expect(result.deletedCount).toBe(1);
|
||||
|
||||
const remaining = await Token.find({});
|
||||
expect(remaining).toHaveLength(1);
|
||||
expect(remaining[0].email).toBe('jane.doe@example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Email verification flow with normalization', () => {
|
||||
test('should handle OpenID provider email case mismatch scenario', async () => {
|
||||
/**
|
||||
* Simulate the exact bug scenario:
|
||||
* 1. User registers with email stored as lowercase
|
||||
* 2. OpenID provider returns email with different casing
|
||||
* 3. System should still find and delete the correct token
|
||||
*/
|
||||
const userId = new mongoose.Types.ObjectId();
|
||||
|
||||
// Token created during registration (email stored lowercase)
|
||||
await Token.create({
|
||||
token: 'verification-token',
|
||||
userId: userId,
|
||||
email: 'user@company.com',
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(Date.now() + 86400000),
|
||||
});
|
||||
|
||||
// OpenID provider returns email with different case
|
||||
const emailFromProvider = 'User@Company.COM';
|
||||
|
||||
// Should find the token despite case mismatch
|
||||
const found = await methods.findToken({ email: emailFromProvider });
|
||||
expect(found).toBeDefined();
|
||||
expect(found?.token).toBe('verification-token');
|
||||
|
||||
// Should delete the token despite case mismatch
|
||||
const deleted = await methods.deleteTokens({ email: emailFromProvider });
|
||||
expect(deleted.deletedCount).toBe(1);
|
||||
});
|
||||
|
||||
test('should handle resend verification email with case mismatch', async () => {
|
||||
const userId = new mongoose.Types.ObjectId();
|
||||
|
||||
// Old verification token
|
||||
await Token.create({
|
||||
token: 'old-verification',
|
||||
userId: userId,
|
||||
email: 'john.smith@enterprise.com',
|
||||
createdAt: new Date(Date.now() - 3600000),
|
||||
expiresAt: new Date(Date.now() + 82800000),
|
||||
});
|
||||
|
||||
// User requests resend with different email casing
|
||||
const userInputEmail = ' John.Smith@ENTERPRISE.COM ';
|
||||
|
||||
// Delete old tokens for this email
|
||||
const deleted = await methods.deleteTokens({ email: userInputEmail });
|
||||
expect(deleted.deletedCount).toBe(1);
|
||||
|
||||
// Verify token was actually deleted
|
||||
const remaining = await Token.find({ userId });
|
||||
expect(remaining).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue