@mikkelscheike/email-provider-links 1.6.0 โ†’ 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Email Provider Links
2
2
 
3
- ๐Ÿ”’ **Enterprise-grade secure email provider detection for login and password reset flows**
3
+ ๐Ÿ”’ **Modern email provider detection library with concurrent DNS resolution and enterprise security**
4
4
 
5
- A TypeScript package that provides direct links to email providers based on email addresses, with comprehensive security features to prevent malicious redirects and supply chain attacks.
5
+ A TypeScript library providing direct links to **93 email providers** (178 domains) with **concurrent DNS resolution**, **optimized performance**, **email alias detection**, and comprehensive security features for login and password reset flows.
6
6
 
7
7
  ## โœจ Features
8
8
 
9
9
  - ๐Ÿš€ **Fast & Lightweight**: Zero dependencies, minimal footprint
10
- - ๐Ÿ“ง **74 Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and more
11
- - ๐ŸŒ **147+ Domains Supported**: Comprehensive international coverage
10
+ - ๐Ÿ“ง **93 Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and many more
11
+ - ๐ŸŒ **178 Domains Supported**: Comprehensive international coverage
12
12
  - ๐Ÿข **Business Domain Detection**: DNS-based detection for custom domains (Google Workspace, Microsoft 365, etc.)
13
13
  - ๐Ÿ”’ **Enterprise Security**: Multi-layer protection against malicious URLs and supply chain attacks
14
14
  - ๐Ÿ›ก๏ธ **URL Validation**: HTTPS-only enforcement with domain allowlisting
@@ -16,7 +16,9 @@ A TypeScript package that provides direct links to email providers based on emai
16
16
  - ๐Ÿ“ **Type Safe**: Full TypeScript support with comprehensive interfaces
17
17
  - โšก **Performance Optimized**: Smart DNS fallback with configurable timeouts
18
18
  - ๐Ÿšฆ **Rate Limiting**: Built-in DNS query rate limiting to prevent abuse
19
- - ๐Ÿงช **Thoroughly Tested**: 142+ tests with 94.69% code coverage
19
+ - ๐Ÿ”„ **Email Alias Detection**: Normalize Gmail dots, plus addressing, and provider-specific aliases
20
+ - ๐Ÿ›ก๏ธ **Fraud Prevention**: Detect duplicate accounts through email alias manipulation
21
+ - ๐Ÿงช **Thoroughly Tested**: 371 tests with 91.75% code coverage
20
22
 
21
23
  ## Installation
22
24
 
@@ -26,21 +28,28 @@ npm install @mikkelscheike/email-provider-links
26
28
 
27
29
  ## Quick Start
28
30
 
31
+ **One function handles everything** - consumer emails, business domains, and unknown providers:
32
+
29
33
  ```typescript
30
- import { getEmailProviderLinkWithDNS } from '@mikkelscheike/email-provider-links';
34
+ import { getEmailProvider } from '@mikkelscheike/email-provider-links';
31
35
 
32
- // Works for any email address
33
- const result = await getEmailProviderLinkWithDNS('user@gmail.com');
34
- console.log(result.loginUrl); // "https://mail.google.com/mail/"
36
+ // Works for ANY email address - the only function you need
37
+ const result = await getEmailProvider('user@gmail.com');
38
+ console.log(result.loginUrl); // "https://mail.google.com/mail/"
39
+ console.log(result.provider?.companyProvider); // "Gmail"
35
40
 
36
- // Business domains too
37
- const business = await getEmailProviderLinkWithDNS('user@mycompany.com');
41
+ // Automatically detects business domains too
42
+ const business = await getEmailProvider('user@mycompany.com');
38
43
  console.log(business.provider?.companyProvider); // "Google Workspace" (if detected)
44
+
45
+ // Gracefully handles unknown providers
46
+ const unknown = await getEmailProvider('user@unknown.com');
47
+ console.log(unknown.loginUrl); // null
39
48
  ```
40
49
 
41
50
  ## Supported Providers
42
51
 
43
- **๐Ÿ“Š Current Coverage: 74 providers supporting 147+ domains**
52
+ **๐Ÿ“Š Current Coverage: 93 providers supporting 178 domains**
44
53
 
45
54
  **Consumer Email Providers:**
46
55
  - **Gmail** (2 domains): gmail.com, googlemail.com
@@ -64,25 +73,26 @@ console.log(business.provider?.companyProvider); // "Google Workspace" (if detec
64
73
  - **Mailfence, SimpleLogin, AnonAddy**
65
74
 
66
75
  **International Providers:**
67
- - **Europe**: GMX, Web.de, Orange, Free.fr, T-Online, Libero
68
- - **Asia**: QQ Mail, NetEase, Sina Mail, Rakuten, Nifty, **Naver** (Korea), **Daum** (Korea), **Biglobe** (Japan)
69
- - **Other Regions**: UOL (Brazil), Telkom (South Africa), Xtra (New Zealand)
76
+ - **Europe**: GMX, Web.de, Orange, Free.fr, T-Online, Libero, Virgilio, Telekom, Tiscali, Skynet, Telenet, Xs4All, Planet.nl, Bluewin, Eircom
77
+ - **Asia**: QQ Mail, NetEase, Sina Mail, Alibaba Mail, Rakuten, Nifty, **Naver** (Korea), **Daum** (Korea), **Biglobe** (Japan), Sify, IndiatTimes (India)
78
+ - **Eastern Europe**: Centrum (Czech/Slovak), Interia, Onet (Poland), Rambler (Russia)
79
+ - **Other Regions**: UOL, Terra (Brazil), Telkom (South Africa), Xtra (New Zealand)
70
80
 
71
81
  ## API
72
82
 
73
- ### `getEmailProviderLinkWithDNS(email, timeout?)`
83
+ ### `getEmailProvider(email, timeout?)`
74
84
  **Recommended** - Detects any email provider including business domains.
75
85
 
76
86
  ```typescript
77
- const result = await getEmailProviderLinkWithDNS('user@gmail.com', 3000);
87
+ const result = await getEmailProvider('user@gmail.com', 3000);
78
88
  // Returns: { provider, loginUrl, detectionMethod, email }
79
89
  ```
80
90
 
81
- ### `getEmailProviderLink(email)`
91
+ ### `getEmailProviderSync(email)`
82
92
  **Synchronous** - Only checks predefined domains (no DNS lookup).
83
93
 
84
94
  ```typescript
85
- const result = getEmailProviderLink('user@gmail.com');
95
+ const result = getEmailProviderSync('user@gmail.com');
86
96
  // Returns: { provider, loginUrl, email }
87
97
  ```
88
98
 
@@ -90,7 +100,7 @@ const result = getEmailProviderLink('user@gmail.com');
90
100
 
91
101
  ```typescript
92
102
  async function handlePasswordReset(email: string) {
93
- const result = await getEmailProviderLinkWithDNS(email);
103
+ const result = await getEmailProvider(email);
94
104
 
95
105
  return {
96
106
  providerUrl: result.loginUrl,
@@ -100,25 +110,112 @@ async function handlePasswordReset(email: string) {
100
110
  }
101
111
  ```
102
112
 
113
+
103
114
  ## Configuration
104
115
 
105
116
  ```typescript
106
117
  // Custom DNS timeout (default: 5000ms)
107
- const result = await getEmailProviderLinkWithDNS(email, 2000);
118
+ const result = await getEmailProvider(email, 2000);
119
+
120
+ // Rate limiting configuration
121
+ import { Config } from '@mikkelscheike/email-provider-links';
122
+ console.log('Max requests:', Config.MAX_DNS_REQUESTS_PER_MINUTE); // 10
123
+ console.log('Default timeout:', Config.DEFAULT_DNS_TIMEOUT); // 5000ms
124
+ ```
125
+
126
+ ## Advanced Usage
127
+
128
+ <details>
129
+ <summary><strong>๐Ÿ“š Secondary Functions & Specialized Use Cases</strong></summary>
130
+
131
+ ### Synchronous Provider Detection (No DNS)
132
+
133
+ If you can't use async or don't want DNS lookups:
134
+
135
+ ```typescript
136
+ import { getEmailProviderSync } from '@mikkelscheike/email-provider-links';
137
+
138
+ // Synchronous - only checks predefined domains
139
+ const result = getEmailProviderSync('user@gmail.com');
140
+ console.log(result.loginUrl); // Works for known domains only
141
+ ```
142
+
143
+ ### Provider Support Checking
144
+
145
+ ```typescript
146
+ import { isEmailProviderSupported, getSupportedProviders } from '@mikkelscheike/email-provider-links';
108
147
 
109
148
  // Check if provider is supported
110
- import { isEmailProviderSupported } from '@mikkelscheike/email-provider-links';
111
149
  const supported = isEmailProviderSupported('user@gmail.com');
112
150
 
113
- // Rate limiting configuration
114
- import { RateLimit } from '@mikkelscheike/email-provider-links';
115
- console.log('Max requests:', RateLimit.MAX_REQUESTS); // 10
116
- console.log('Time window:', RateLimit.WINDOW_MS); // 60000ms
151
+ // Get all supported providers
152
+ const allProviders = getSupportedProviders();
153
+ console.log(`Supports ${allProviders.length} providers`);
154
+ ```
117
155
 
118
- // Custom rate limiter for specific use cases
119
- const customLimiter = new RateLimit.SimpleRateLimiter(20, 120000); // 20 requests per 2 minutes
156
+ ### Advanced Provider Detection
157
+
158
+ ```typescript
159
+ import { getEmailProviderFast, detectProviderConcurrent } from '@mikkelscheike/email-provider-links';
160
+
161
+ // High-performance detection with concurrent DNS
162
+ const fastResult = await getEmailProviderFast('user@mycompany.com', {
163
+ enableParallel: true,
164
+ collectDebugInfo: true
165
+ });
166
+ console.log(fastResult.provider?.companyProvider); // "Google Workspace"
167
+ console.log(fastResult.timing); // Performance metrics
168
+ ```
169
+
170
+ ### Configuration Options
171
+
172
+ ```typescript
173
+ import { Config } from '@mikkelscheike/email-provider-links';
174
+
175
+ // Access configuration constants
176
+ console.log(Config.DEFAULT_DNS_TIMEOUT); // 5000ms
177
+ console.log(Config.MAX_DNS_REQUESTS_PER_MINUTE); // 10
178
+ console.log(Config.SUPPORTED_PROVIDERS_COUNT); // 93
120
179
  ```
121
180
 
181
+ ### ๐Ÿ”„ Email Alias Detection & Normalization
182
+
183
+ **Specialized feature** for preventing duplicate accounts and fraud detection:
184
+
185
+ ```typescript
186
+ import {
187
+ normalizeEmail,
188
+ emailsMatch
189
+ } from '@mikkelscheike/email-provider-links';
190
+
191
+ // Prevent duplicate accounts
192
+ async function registerUser(email: string) {
193
+ const canonical = normalizeEmail(email);
194
+ const existingUser = await findUserByEmail(canonical);
195
+
196
+ if (existingUser) {
197
+ throw new Error('Email already registered');
198
+ }
199
+
200
+ await createUser({ email: canonical });
201
+ }
202
+
203
+ // Check if login email matches registration
204
+ const match = emailsMatch('user@gmail.com', 'u.s.e.r+work@gmail.com');
205
+ console.log(match); // true - same person
206
+
207
+ // Simple normalization
208
+ const canonical = normalizeEmail('u.s.e.r+work@gmail.com');
209
+ console.log(canonical); // 'user@gmail.com'
210
+ ```
211
+
212
+ **Supported alias types:**
213
+ - **Gmail dots**: `u.s.e.r@gmail.com` โ†’ `user@gmail.com`
214
+ - **Plus addressing**: `user+tag@provider.com` โ†’ `user@provider.com`
215
+ - **Provider-specific rules**: Different providers have different capabilities
216
+
217
+ </details>
218
+
122
219
  ## TypeScript Support
123
220
 
124
221
  ```typescript
@@ -130,11 +227,11 @@ interface EmailProviderResult {
130
227
  proxyService?: string;
131
228
  }
132
229
 
133
- interface RateLimitConfig {
134
- MAX_REQUESTS: number; // 10 requests
135
- WINDOW_MS: number; // 60000ms (1 minute)
136
- SimpleRateLimiter: class; // Custom rate limiter class
137
- getCurrentLimiter(): SimpleRateLimiter; // Get current global limiter
230
+ interface ConfigConstants {
231
+ DEFAULT_DNS_TIMEOUT: number; // 5000ms
232
+ MAX_DNS_REQUESTS_PER_MINUTE: number; // 10 requests
233
+ SUPPORTED_PROVIDERS_COUNT: number; // 93 providers
234
+ SUPPORTED_DOMAINS_COUNT: number; // 178 domains
138
235
  }
139
236
  ```
140
237
 
@@ -145,7 +242,7 @@ This package implements **enterprise-grade security** to protect against malicio
145
242
  ### โœ… Multi-Layer Protection
146
243
 
147
244
  - **HTTPS-Only Enforcement**: All provider URLs must use HTTPS protocol
148
- - **Domain Allowlisting**: Only pre-approved domains are allowed (64+ verified providers)
245
+ - **Domain Allowlisting**: Only pre-approved domains are allowed (93+ verified providers)
149
246
  - **Malicious Pattern Detection**: Blocks IP addresses, URL shorteners, suspicious TLDs
150
247
  - **Path Traversal Prevention**: Detects and blocks `../` and encoded variants
151
248
  - **JavaScript Injection Protection**: Prevents `javascript:`, `data:`, and script injections
@@ -165,7 +262,7 @@ Protects against common attack vectors:
165
262
  ### ๐Ÿงช Security Testing
166
263
 
167
264
  - **29 dedicated security tests** covering all attack vectors
168
- - **94% security code coverage** with edge case testing
265
+ - **96% security module coverage** with edge case testing
169
266
  - **Automated security validation** in CI/CD pipeline
170
267
  - **Regular security audits** of provider database
171
268
 
@@ -188,6 +285,7 @@ if (result.securityReport.securityLevel === 'CRITICAL') {
188
285
 
189
286
  We welcome contributions! See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines on adding new email providers.
190
287
 
288
+ **Quality Assurance**: This project maintains high standards with 371 comprehensive tests achieving 91.75% code coverage.
191
289
  **Security Note**: All new providers undergo security validation and must pass our allowlist verification.
192
290
 
193
291
  ## Security
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Email Alias Detection Module
3
+ *
4
+ * Clean, focused implementation with only essential functions.
5
+ * Detects and normalizes email aliases across different providers.
6
+ */
7
+ export interface AliasDetectionResult {
8
+ /** The normalized/canonical email address */
9
+ canonical: string;
10
+ /** The original email address */
11
+ original: string;
12
+ /** Whether an alias was detected */
13
+ isAlias: boolean;
14
+ /** Type of alias detected */
15
+ aliasType: 'plus' | 'dot' | 'none';
16
+ /** The alias part (if any) */
17
+ aliasPart?: string;
18
+ /** The provider that supports this alias type */
19
+ provider?: string;
20
+ }
21
+ /**
22
+ * Detects and analyzes email aliases
23
+ *
24
+ * @param email - Email address to analyze
25
+ * @returns Detailed analysis of the email alias
26
+ */
27
+ export declare function detectEmailAlias(email: string): AliasDetectionResult;
28
+ /**
29
+ * Normalizes an email address to its canonical form.
30
+ *
31
+ * This is the primary function for preventing duplicate accounts.
32
+ *
33
+ * @param email - Email address to normalize
34
+ * @returns Canonical email address
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const canonical = normalizeEmail('U.S.E.R+work@GMAIL.COM');
39
+ * console.log(canonical); // 'user@gmail.com'
40
+ * ```
41
+ */
42
+ export declare function normalizeEmail(email: string): string;
43
+ /**
44
+ * Checks if two email addresses are the same when normalized.
45
+ *
46
+ * This is the primary function for matching aliases during login.
47
+ *
48
+ * @param email1 - First email address
49
+ * @param email2 - Second email address
50
+ * @returns true if the emails represent the same person
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const match = emailsMatch('user@gmail.com', 'u.s.e.r+work@gmail.com');
55
+ * console.log(match); // true
56
+ * ```
57
+ */
58
+ export declare function emailsMatch(email1: string, email2: string): boolean;
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ /**
3
+ * Email Alias Detection Module
4
+ *
5
+ * Clean, focused implementation with only essential functions.
6
+ * Detects and normalizes email aliases across different providers.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.detectEmailAlias = detectEmailAlias;
10
+ exports.normalizeEmail = normalizeEmail;
11
+ exports.emailsMatch = emailsMatch;
12
+ /**
13
+ * Email alias rules for major providers
14
+ */
15
+ const ALIAS_RULES = [
16
+ {
17
+ domains: ['gmail.com', 'googlemail.com'],
18
+ supportsPlusAddressing: true,
19
+ ignoresDots: true,
20
+ normalize: (email) => {
21
+ const [username, domain] = email.toLowerCase().split('@');
22
+ // Remove dots and everything after +
23
+ const cleanUsername = username.replace(/\./g, '').split('+')[0];
24
+ return `${cleanUsername}@${domain}`;
25
+ }
26
+ },
27
+ {
28
+ domains: ['outlook.com', 'hotmail.com', 'live.com', 'msn.com', 'hotmail.co.uk', 'hotmail.fr', 'hotmail.it', 'hotmail.es', 'hotmail.de', 'live.co.uk', 'live.fr', 'live.it', 'live.nl', 'live.com.au', 'live.ca', 'live.jp'],
29
+ supportsPlusAddressing: true,
30
+ ignoresDots: false,
31
+ normalize: (email) => {
32
+ const [username, domain] = email.toLowerCase().split('@');
33
+ // Only remove plus addressing for Outlook
34
+ const cleanUsername = username.split('+')[0];
35
+ return `${cleanUsername}@${domain}`;
36
+ }
37
+ },
38
+ {
39
+ domains: ['yahoo.com', 'yahoo.co.uk', 'yahoo.fr', 'yahoo.co.in', 'yahoo.com.br', 'yahoo.co.jp', 'yahoo.it', 'yahoo.de', 'yahoo.in', 'yahoo.es', 'yahoo.ca', 'yahoo.com.au', 'yahoo.com.ar', 'yahoo.com.mx', 'yahoo.co.id', 'yahoo.com.sg', 'ymail.com', 'rocketmail.com'],
40
+ supportsPlusAddressing: true,
41
+ ignoresDots: false,
42
+ normalize: (email) => {
43
+ const [username, domain] = email.toLowerCase().split('@');
44
+ const cleanUsername = username.split('+')[0];
45
+ return `${cleanUsername}@${domain}`;
46
+ }
47
+ },
48
+ {
49
+ domains: ['fastmail.com', 'fastmail.fm'],
50
+ supportsPlusAddressing: true,
51
+ ignoresDots: false,
52
+ normalize: (email) => {
53
+ const [username, domain] = email.toLowerCase().split('@');
54
+ const cleanUsername = username.split('+')[0];
55
+ return `${cleanUsername}@${domain}`;
56
+ }
57
+ },
58
+ {
59
+ domains: ['proton.me', 'protonmail.com', 'protonmail.ch', 'pm.me'],
60
+ supportsPlusAddressing: true,
61
+ ignoresDots: false,
62
+ normalize: (email) => {
63
+ const [username, domain] = email.toLowerCase().split('@');
64
+ const cleanUsername = username.split('+')[0];
65
+ return `${cleanUsername}@${domain}`;
66
+ }
67
+ },
68
+ {
69
+ domains: ['tutanota.com', 'tutanota.de', 'tutamail.com', 'tuta.io', 'keemail.me', 'tuta.com'],
70
+ supportsPlusAddressing: true,
71
+ ignoresDots: false,
72
+ normalize: (email) => {
73
+ const [username, domain] = email.toLowerCase().split('@');
74
+ const cleanUsername = username.split('+')[0];
75
+ return `${cleanUsername}@${domain}`;
76
+ }
77
+ },
78
+ {
79
+ domains: ['zoho.com', 'zohomail.com', 'zoho.eu'],
80
+ supportsPlusAddressing: true,
81
+ ignoresDots: false,
82
+ normalize: (email) => {
83
+ const [username, domain] = email.toLowerCase().split('@');
84
+ const cleanUsername = username.split('+')[0];
85
+ return `${cleanUsername}@${domain}`;
86
+ }
87
+ },
88
+ {
89
+ domains: ['icloud.com', 'me.com', 'mac.com'],
90
+ supportsPlusAddressing: true,
91
+ ignoresDots: false,
92
+ normalize: (email) => {
93
+ const [username, domain] = email.toLowerCase().split('@');
94
+ const cleanUsername = username.split('+')[0];
95
+ return `${cleanUsername}@${domain}`;
96
+ }
97
+ },
98
+ {
99
+ domains: ['mail.com'],
100
+ supportsPlusAddressing: true,
101
+ ignoresDots: false,
102
+ normalize: (email) => {
103
+ const [username, domain] = email.toLowerCase().split('@');
104
+ const cleanUsername = username.split('+')[0];
105
+ return `${cleanUsername}@${domain}`;
106
+ }
107
+ },
108
+ {
109
+ domains: ['aol.com', 'love.com', 'ygm.com', 'games.com', 'wow.com', 'aim.com'],
110
+ supportsPlusAddressing: false,
111
+ ignoresDots: false,
112
+ normalize: (email) => email.toLowerCase()
113
+ },
114
+ {
115
+ domains: ['mail.ru'],
116
+ supportsPlusAddressing: true,
117
+ ignoresDots: false,
118
+ normalize: (email) => {
119
+ const [username, domain] = email.toLowerCase().split('@');
120
+ const cleanUsername = username.split('+')[0];
121
+ return `${cleanUsername}@${domain}`;
122
+ }
123
+ },
124
+ {
125
+ domains: ['yandex.com', 'yandex.ru'],
126
+ supportsPlusAddressing: true,
127
+ ignoresDots: false,
128
+ normalize: (email) => {
129
+ const [username, domain] = email.toLowerCase().split('@');
130
+ const cleanUsername = username.split('+')[0];
131
+ return `${cleanUsername}@${domain}`;
132
+ }
133
+ }
134
+ ];
135
+ /**
136
+ * Validates email format
137
+ */
138
+ function isValidEmail(email) {
139
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
140
+ return emailRegex.test(email);
141
+ }
142
+ /**
143
+ * Gets the alias rule for a given domain
144
+ */
145
+ function getAliasRule(domain) {
146
+ return ALIAS_RULES.find(rule => rule.domains.includes(domain.toLowerCase()));
147
+ }
148
+ /**
149
+ * Detects and analyzes email aliases
150
+ *
151
+ * @param email - Email address to analyze
152
+ * @returns Detailed analysis of the email alias
153
+ */
154
+ function detectEmailAlias(email) {
155
+ if (!isValidEmail(email)) {
156
+ throw new Error('Invalid email format');
157
+ }
158
+ const originalEmail = email.trim();
159
+ const [username, domain] = originalEmail.toLowerCase().split('@');
160
+ const rule = getAliasRule(domain);
161
+ const result = {
162
+ canonical: originalEmail.toLowerCase(),
163
+ original: originalEmail,
164
+ isAlias: false,
165
+ aliasType: 'none'
166
+ };
167
+ if (!rule) {
168
+ // No specific rule, just normalize case
169
+ result.canonical = originalEmail.toLowerCase();
170
+ return result;
171
+ }
172
+ result.provider = domain;
173
+ // Check for plus addressing
174
+ if (rule.supportsPlusAddressing && username.includes('+')) {
175
+ const plusIndex = username.indexOf('+');
176
+ const baseUsername = username.substring(0, plusIndex);
177
+ const aliasPart = username.substring(plusIndex + 1);
178
+ result.isAlias = true;
179
+ result.aliasType = 'plus';
180
+ result.aliasPart = aliasPart;
181
+ result.canonical = rule.normalize ? rule.normalize(originalEmail) : `${baseUsername}@${domain}`;
182
+ return result;
183
+ }
184
+ // Check for dot variations (Gmail)
185
+ if (rule.ignoresDots && username.includes('.')) {
186
+ const baseUsername = username.replace(/\./g, '');
187
+ if (baseUsername !== username) {
188
+ result.isAlias = true;
189
+ result.aliasType = 'dot';
190
+ result.aliasPart = username;
191
+ result.canonical = rule.normalize ? rule.normalize(originalEmail) : `${baseUsername}@${domain}`;
192
+ return result;
193
+ }
194
+ }
195
+ // Apply provider-specific normalization
196
+ if (rule.normalize) {
197
+ const normalized = rule.normalize(originalEmail);
198
+ if (normalized !== originalEmail.toLowerCase()) {
199
+ result.isAlias = true;
200
+ result.canonical = normalized;
201
+ }
202
+ }
203
+ return result;
204
+ }
205
+ /**
206
+ * Normalizes an email address to its canonical form.
207
+ *
208
+ * This is the primary function for preventing duplicate accounts.
209
+ *
210
+ * @param email - Email address to normalize
211
+ * @returns Canonical email address
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const canonical = normalizeEmail('U.S.E.R+work@GMAIL.COM');
216
+ * console.log(canonical); // 'user@gmail.com'
217
+ * ```
218
+ */
219
+ function normalizeEmail(email) {
220
+ const result = detectEmailAlias(email);
221
+ return result.canonical;
222
+ }
223
+ /**
224
+ * Checks if two email addresses are the same when normalized.
225
+ *
226
+ * This is the primary function for matching aliases during login.
227
+ *
228
+ * @param email1 - First email address
229
+ * @param email2 - Second email address
230
+ * @returns true if the emails represent the same person
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const match = emailsMatch('user@gmail.com', 'u.s.e.r+work@gmail.com');
235
+ * console.log(match); // true
236
+ * ```
237
+ */
238
+ function emailsMatch(email1, email2) {
239
+ try {
240
+ return normalizeEmail(email1) === normalizeEmail(email2);
241
+ }
242
+ catch {
243
+ return false;
244
+ }
245
+ }