075b45f020
Closes #574. Reporter (@blazmaric) identified the root cause cleanly: express-validator's `.normalizeEmail()` applies provider-specific canonicalization by default — Gmail dot-stripping, +tag stripping, googlemail → gmail folding, etc. That's wrong for identity: PicPeak uses email as a login identifier, so `john.doe@gmail.com` getting silently stored as `johndoe@gmail.com` means the user can't log in with the address they were invited with. The bug existed at 17 call sites across the codebase (auth, admin user create/update, customer create/update, event create/update on three different routes, customer login, feedback submission). All of them are identity-bearing — none had a legitimate reason to strip dots for deduplication. Fix: introduce one shared options object in `utils/emailNormalization` disabling every provider-specific normalization (gmail_remove_dots, gmail_remove_subaddress, gmail_convert_googlemaildotcom, outlookdotcom_remove_subaddress, yahoo_remove_subaddress, icloud_remove_subaddress). The only default left enabled is `all_lowercase`, which is safe — local-parts are case-insensitive in practice on every major provider, and lowercasing keeps login lookup consistent. Every call site updated to pass the shared options. 7 unit tests pin the preserved-dots, preserved-subaddress, preserved-googlemail-domain, and still-lowercase behaviours so a future refactor can't silently regress. ## Migration note Existing accounts whose emails were already stripped before this fix remain with the stripped form in the DB. The fix takes effect for new invitations going forward. If an admin re-invites an existing user with the un-stripped address, that would create a duplicate account — out of scope here; if it becomes a real problem we can add a backward-compat login fallback (try lookup with dot-stripped form too) as a separate change.
58 lines
2.3 KiB
JavaScript
58 lines
2.3 KiB
JavaScript
/**
|
|
* Regression coverage for the identity-preserving email normalization
|
|
* options (#574).
|
|
*
|
|
* express-validator's `.normalizeEmail()` applies provider-specific
|
|
* canonicalization by default — Gmail dot-stripping, +tag stripping,
|
|
* googlemail → gmail folding, etc. That breaks identity because login
|
|
* lookups expect the address as the user was invited with, not the
|
|
* canonicalized form.
|
|
*
|
|
* The tests below run validator.js's `normalizeEmail` (the same
|
|
* implementation express-validator delegates to) through the
|
|
* `IDENTITY_PRESERVING_NORMALIZE_EMAIL` options object and pin the
|
|
* behaviour we depend on:
|
|
* - dots preserved on Gmail
|
|
* - +tags preserved on Gmail / Outlook / Yahoo / iCloud
|
|
* - googlemail.com domain preserved (not folded to gmail.com)
|
|
* - local-part lowercased (still the default — safe and consistent)
|
|
*/
|
|
const validator = require('validator');
|
|
const { IDENTITY_PRESERVING_NORMALIZE_EMAIL } = require('../../src/utils/emailNormalization');
|
|
|
|
const norm = (email) => validator.normalizeEmail(email, IDENTITY_PRESERVING_NORMALIZE_EMAIL);
|
|
|
|
describe('IDENTITY_PRESERVING_NORMALIZE_EMAIL', () => {
|
|
it('preserves dots in the Gmail local-part (the #574 root cause)', () => {
|
|
expect(norm('john.doe@gmail.com')).toBe('john.doe@gmail.com');
|
|
expect(norm('j.o.h.n@gmail.com')).toBe('j.o.h.n@gmail.com');
|
|
});
|
|
|
|
it('preserves Gmail +tags (subaddresses)', () => {
|
|
expect(norm('john.doe+invoices@gmail.com')).toBe('john.doe+invoices@gmail.com');
|
|
});
|
|
|
|
it('does not fold googlemail.com to gmail.com', () => {
|
|
expect(norm('john.doe@googlemail.com')).toBe('john.doe@googlemail.com');
|
|
});
|
|
|
|
it('preserves Outlook +tags', () => {
|
|
expect(norm('jane+work@outlook.com')).toBe('jane+work@outlook.com');
|
|
});
|
|
|
|
it('preserves Yahoo -tags', () => {
|
|
expect(norm('jane-work@yahoo.com')).toBe('jane-work@yahoo.com');
|
|
});
|
|
|
|
it('preserves iCloud +tags', () => {
|
|
expect(norm('jane+receipts@icloud.com')).toBe('jane+receipts@icloud.com');
|
|
});
|
|
|
|
it('lowercases the local-part (default behaviour we keep)', () => {
|
|
// all_lowercase defaults true in validator.js. Local-parts are
|
|
// case-insensitive in practice on every major provider, and
|
|
// lowercasing keeps login lookup consistent.
|
|
expect(norm('John.Doe@Gmail.com')).toBe('john.doe@gmail.com');
|
|
});
|
|
});
|