@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 +133 -35
- package/dist/alias-detection.d.ts +58 -0
- package/dist/alias-detection.js +245 -0
- package/dist/api.d.ts +172 -0
- package/dist/api.js +439 -0
- package/dist/concurrent-dns.d.ts +130 -0
- package/dist/concurrent-dns.js +403 -0
- package/dist/index.d.ts +45 -349
- package/dist/index.js +65 -576
- package/dist/loader.d.ts +44 -0
- package/dist/loader.js +155 -0
- package/dist/schema.d.ts +66 -0
- package/dist/schema.js +73 -0
- package/dist/security/hash-verifier.js +2 -2
- package/package.json +7 -2
- package/providers/emailproviders.json +634 -428
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Email Provider Links
|
|
2
2
|
|
|
3
|
-
๐ **
|
|
3
|
+
๐ **Modern email provider detection library with concurrent DNS resolution and enterprise security**
|
|
4
4
|
|
|
5
|
-
A TypeScript
|
|
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
|
-
- ๐ง **
|
|
11
|
-
- ๐ **
|
|
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
|
-
-
|
|
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 {
|
|
34
|
+
import { getEmailProvider } from '@mikkelscheike/email-provider-links';
|
|
31
35
|
|
|
32
|
-
// Works for
|
|
33
|
-
const result = await
|
|
34
|
-
console.log(result.loginUrl);
|
|
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
|
-
//
|
|
37
|
-
const business = await
|
|
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:
|
|
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
|
-
- **
|
|
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
|
-
### `
|
|
83
|
+
### `getEmailProvider(email, timeout?)`
|
|
74
84
|
**Recommended** - Detects any email provider including business domains.
|
|
75
85
|
|
|
76
86
|
```typescript
|
|
77
|
-
const result = await
|
|
87
|
+
const result = await getEmailProvider('user@gmail.com', 3000);
|
|
78
88
|
// Returns: { provider, loginUrl, detectionMethod, email }
|
|
79
89
|
```
|
|
80
90
|
|
|
81
|
-
### `
|
|
91
|
+
### `getEmailProviderSync(email)`
|
|
82
92
|
**Synchronous** - Only checks predefined domains (no DNS lookup).
|
|
83
93
|
|
|
84
94
|
```typescript
|
|
85
|
-
const result =
|
|
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
|
|
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
|
|
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
|
-
//
|
|
114
|
-
|
|
115
|
-
console.log(
|
|
116
|
-
|
|
151
|
+
// Get all supported providers
|
|
152
|
+
const allProviders = getSupportedProviders();
|
|
153
|
+
console.log(`Supports ${allProviders.length} providers`);
|
|
154
|
+
```
|
|
117
155
|
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 (
|
|
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
|
-
- **
|
|
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
|
+
}
|