@mikkelscheike/email-provider-links 1.7.0 โ 1.9.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 +106 -99
- package/dist/alias-detection.d.ts +30 -47
- package/dist/alias-detection.js +29 -104
- 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 -353
- package/dist/index.js +65 -596
- 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 +2 -2
- package/providers/emailproviders.json +530 -479
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
10
|
- ๐ง **93 Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and many more
|
|
11
|
-
- ๐ **
|
|
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
|
|
@@ -18,7 +18,7 @@ A TypeScript package providing direct links to **93 email providers** (180+ doma
|
|
|
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
20
|
- ๐ก๏ธ **Fraud Prevention**: Detect duplicate accounts through email alias manipulation
|
|
21
|
-
- ๐งช **Thoroughly Tested**:
|
|
21
|
+
- ๐งช **Thoroughly Tested**: 371 tests with 91.75% code coverage
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
@@ -28,21 +28,28 @@ npm install @mikkelscheike/email-provider-links
|
|
|
28
28
|
|
|
29
29
|
## Quick Start
|
|
30
30
|
|
|
31
|
+
**One function handles everything** - consumer emails, business domains, and unknown providers:
|
|
32
|
+
|
|
31
33
|
```typescript
|
|
32
|
-
import {
|
|
34
|
+
import { getEmailProvider } from '@mikkelscheike/email-provider-links';
|
|
33
35
|
|
|
34
|
-
// Works for
|
|
35
|
-
const result = await
|
|
36
|
-
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"
|
|
37
40
|
|
|
38
|
-
//
|
|
39
|
-
const business = await
|
|
41
|
+
// Automatically detects business domains too
|
|
42
|
+
const business = await getEmailProvider('user@mycompany.com');
|
|
40
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
|
|
41
48
|
```
|
|
42
49
|
|
|
43
50
|
## Supported Providers
|
|
44
51
|
|
|
45
|
-
**๐ Current Coverage: 93 providers supporting
|
|
52
|
+
**๐ Current Coverage: 93 providers supporting 178 domains**
|
|
46
53
|
|
|
47
54
|
**Consumer Email Providers:**
|
|
48
55
|
- **Gmail** (2 domains): gmail.com, googlemail.com
|
|
@@ -73,19 +80,19 @@ console.log(business.provider?.companyProvider); // "Google Workspace" (if detec
|
|
|
73
80
|
|
|
74
81
|
## API
|
|
75
82
|
|
|
76
|
-
### `
|
|
83
|
+
### `getEmailProvider(email, timeout?)`
|
|
77
84
|
**Recommended** - Detects any email provider including business domains.
|
|
78
85
|
|
|
79
86
|
```typescript
|
|
80
|
-
const result = await
|
|
87
|
+
const result = await getEmailProvider('user@gmail.com', 3000);
|
|
81
88
|
// Returns: { provider, loginUrl, detectionMethod, email }
|
|
82
89
|
```
|
|
83
90
|
|
|
84
|
-
### `
|
|
91
|
+
### `getEmailProviderSync(email)`
|
|
85
92
|
**Synchronous** - Only checks predefined domains (no DNS lookup).
|
|
86
93
|
|
|
87
94
|
```typescript
|
|
88
|
-
const result =
|
|
95
|
+
const result = getEmailProviderSync('user@gmail.com');
|
|
89
96
|
// Returns: { provider, loginUrl, email }
|
|
90
97
|
```
|
|
91
98
|
|
|
@@ -93,7 +100,7 @@ const result = getEmailProviderLink('user@gmail.com');
|
|
|
93
100
|
|
|
94
101
|
```typescript
|
|
95
102
|
async function handlePasswordReset(email: string) {
|
|
96
|
-
const result = await
|
|
103
|
+
const result = await getEmailProvider(email);
|
|
97
104
|
|
|
98
105
|
return {
|
|
99
106
|
providerUrl: result.loginUrl,
|
|
@@ -103,57 +110,85 @@ async function handlePasswordReset(email: string) {
|
|
|
103
110
|
}
|
|
104
111
|
```
|
|
105
112
|
|
|
106
|
-
## ๐ Email Alias Detection
|
|
107
113
|
|
|
108
|
-
|
|
114
|
+
## Configuration
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
```typescript
|
|
117
|
+
// Custom DNS timeout (default: 5000ms)
|
|
118
|
+
const result = await getEmailProvider(email, 2000);
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
```
|
|
117
125
|
|
|
118
|
-
|
|
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:
|
|
119
134
|
|
|
120
135
|
```typescript
|
|
121
|
-
import {
|
|
122
|
-
detectEmailAlias,
|
|
123
|
-
normalizeEmail,
|
|
124
|
-
emailsMatch
|
|
125
|
-
} from '@mikkelscheike/email-provider-links';
|
|
136
|
+
import { getEmailProviderSync } from '@mikkelscheike/email-provider-links';
|
|
126
137
|
|
|
127
|
-
//
|
|
128
|
-
const result =
|
|
129
|
-
console.log(result.
|
|
130
|
-
|
|
131
|
-
console.log(result.aliasType); // 'plus'
|
|
132
|
-
console.log(result.aliasPart); // 'work'
|
|
138
|
+
// Synchronous - only checks predefined domains
|
|
139
|
+
const result = getEmailProviderSync('user@gmail.com');
|
|
140
|
+
console.log(result.loginUrl); // Works for known domains only
|
|
141
|
+
```
|
|
133
142
|
|
|
134
|
-
|
|
135
|
-
const canonical = normalizeEmail('U.S.E.R+Newsletter@GMAIL.COM');
|
|
136
|
-
console.log(canonical); // 'user@gmail.com'
|
|
143
|
+
### Provider Support Checking
|
|
137
144
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
```typescript
|
|
146
|
+
import { isEmailProviderSupported, getSupportedProviders } from '@mikkelscheike/email-provider-links';
|
|
147
|
+
|
|
148
|
+
// Check if provider is supported
|
|
149
|
+
const supported = isEmailProviderSupported('user@gmail.com');
|
|
150
|
+
|
|
151
|
+
// Get all supported providers
|
|
152
|
+
const allProviders = getSupportedProviders();
|
|
153
|
+
console.log(`Supports ${allProviders.length} providers`);
|
|
141
154
|
```
|
|
142
155
|
|
|
143
|
-
### Provider
|
|
156
|
+
### Advanced Provider Detection
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { getEmailProviderFast, detectProviderConcurrent } from '@mikkelscheike/email-provider-links';
|
|
144
160
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
```
|
|
152
169
|
|
|
153
|
-
###
|
|
170
|
+
### Configuration Options
|
|
154
171
|
|
|
155
172
|
```typescript
|
|
156
|
-
|
|
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
|
|
179
|
+
```
|
|
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
|
|
157
192
|
async function registerUser(email: string) {
|
|
158
193
|
const canonical = normalizeEmail(email);
|
|
159
194
|
const existingUser = await findUserByEmail(canonical);
|
|
@@ -162,53 +197,24 @@ async function registerUser(email: string) {
|
|
|
162
197
|
throw new Error('Email already registered');
|
|
163
198
|
}
|
|
164
199
|
|
|
165
|
-
// Store canonical email to prevent future duplicates
|
|
166
200
|
await createUser({ email: canonical });
|
|
167
201
|
}
|
|
168
202
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const emailList = [
|
|
173
|
-
'user@gmail.com',
|
|
174
|
-
'u.s.e.r@gmail.com',
|
|
175
|
-
'user+newsletter@gmail.com',
|
|
176
|
-
'different@gmail.com'
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
const analysis = analyzeEmailAliases(emailList);
|
|
180
|
-
console.log(analysis.totalEmails); // 4
|
|
181
|
-
console.log(analysis.uniqueCanonical); // 2 (actual unique users)
|
|
182
|
-
|
|
183
|
-
// Generate test aliases
|
|
184
|
-
import { generateAliases } from '@mikkelscheike/email-provider-links';
|
|
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
|
|
185
206
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
maxDotVariations: 3
|
|
190
|
-
});
|
|
191
|
-
// Returns: ['user+work@gmail.com', 'user+personal@gmail.com', 'u.ser@gmail.com', ...]
|
|
207
|
+
// Simple normalization
|
|
208
|
+
const canonical = normalizeEmail('u.s.e.r+work@gmail.com');
|
|
209
|
+
console.log(canonical); // 'user@gmail.com'
|
|
192
210
|
```
|
|
193
211
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const result = await getEmailProviderLinkWithDNS(email, 2000);
|
|
199
|
-
|
|
200
|
-
// Check if provider is supported
|
|
201
|
-
import { isEmailProviderSupported } from '@mikkelscheike/email-provider-links';
|
|
202
|
-
const supported = isEmailProviderSupported('user@gmail.com');
|
|
203
|
-
|
|
204
|
-
// Rate limiting configuration
|
|
205
|
-
import { RateLimit } from '@mikkelscheike/email-provider-links';
|
|
206
|
-
console.log('Max requests:', RateLimit.MAX_REQUESTS); // 10
|
|
207
|
-
console.log('Time window:', RateLimit.WINDOW_MS); // 60000ms
|
|
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
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
const customLimiter = new RateLimit.SimpleRateLimiter(20, 120000); // 20 requests per 2 minutes
|
|
211
|
-
```
|
|
217
|
+
</details>
|
|
212
218
|
|
|
213
219
|
## TypeScript Support
|
|
214
220
|
|
|
@@ -221,11 +227,11 @@ interface EmailProviderResult {
|
|
|
221
227
|
proxyService?: string;
|
|
222
228
|
}
|
|
223
229
|
|
|
224
|
-
interface
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
229
235
|
}
|
|
230
236
|
```
|
|
231
237
|
|
|
@@ -236,7 +242,7 @@ This package implements **enterprise-grade security** to protect against malicio
|
|
|
236
242
|
### โ
Multi-Layer Protection
|
|
237
243
|
|
|
238
244
|
- **HTTPS-Only Enforcement**: All provider URLs must use HTTPS protocol
|
|
239
|
-
- **Domain Allowlisting**: Only pre-approved domains are allowed (
|
|
245
|
+
- **Domain Allowlisting**: Only pre-approved domains are allowed (93+ verified providers)
|
|
240
246
|
- **Malicious Pattern Detection**: Blocks IP addresses, URL shorteners, suspicious TLDs
|
|
241
247
|
- **Path Traversal Prevention**: Detects and blocks `../` and encoded variants
|
|
242
248
|
- **JavaScript Injection Protection**: Prevents `javascript:`, `data:`, and script injections
|
|
@@ -256,7 +262,7 @@ Protects against common attack vectors:
|
|
|
256
262
|
### ๐งช Security Testing
|
|
257
263
|
|
|
258
264
|
- **29 dedicated security tests** covering all attack vectors
|
|
259
|
-
- **
|
|
265
|
+
- **96% security module coverage** with edge case testing
|
|
260
266
|
- **Automated security validation** in CI/CD pipeline
|
|
261
267
|
- **Regular security audits** of provider database
|
|
262
268
|
|
|
@@ -279,6 +285,7 @@ if (result.securityReport.securityLevel === 'CRITICAL') {
|
|
|
279
285
|
|
|
280
286
|
We welcome contributions! See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines on adding new email providers.
|
|
281
287
|
|
|
288
|
+
**Quality Assurance**: This project maintains high standards with 371 comprehensive tests achieving 91.75% code coverage.
|
|
282
289
|
**Security Note**: All new providers undergo security validation and must pass our allowlist verification.
|
|
283
290
|
|
|
284
291
|
## Security
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Email Alias Detection Module
|
|
3
3
|
*
|
|
4
|
+
* Clean, focused implementation with only essential functions.
|
|
4
5
|
* Detects and normalizes email aliases across different providers.
|
|
5
|
-
* Supports plus addressing (+), dot variations, and provider-specific aliasing.
|
|
6
6
|
*/
|
|
7
7
|
export interface AliasDetectionResult {
|
|
8
8
|
/** The normalized/canonical email address */
|
|
@@ -12,64 +12,47 @@ export interface AliasDetectionResult {
|
|
|
12
12
|
/** Whether an alias was detected */
|
|
13
13
|
isAlias: boolean;
|
|
14
14
|
/** Type of alias detected */
|
|
15
|
-
aliasType: 'plus' | 'dot' | '
|
|
15
|
+
aliasType: 'plus' | 'dot' | 'none';
|
|
16
16
|
/** The alias part (if any) */
|
|
17
17
|
aliasPart?: string;
|
|
18
18
|
/** The provider that supports this alias type */
|
|
19
19
|
provider?: string;
|
|
20
20
|
}
|
|
21
|
-
export interface AliasRule {
|
|
22
|
-
/** Email domains this rule applies to */
|
|
23
|
-
domains: string[];
|
|
24
|
-
/** Whether this provider supports plus addressing (user+alias@domain.com) */
|
|
25
|
-
supportsPlusAddressing: boolean;
|
|
26
|
-
/** Whether this provider ignores dots in username (user.name@domain.com = username@domain.com) */
|
|
27
|
-
ignoresDots: boolean;
|
|
28
|
-
/** Whether this provider supports subdomain aliases (user@alias.domain.com) */
|
|
29
|
-
supportsSubdomainAlias: boolean;
|
|
30
|
-
/** Custom alias patterns specific to this provider */
|
|
31
|
-
customPatterns?: RegExp[];
|
|
32
|
-
/** Function to normalize email for this provider */
|
|
33
|
-
normalize?: (email: string) => string;
|
|
34
|
-
}
|
|
35
21
|
/**
|
|
36
22
|
* Detects and analyzes email aliases
|
|
23
|
+
*
|
|
24
|
+
* @param email - Email address to analyze
|
|
25
|
+
* @returns Detailed analysis of the email alias
|
|
37
26
|
*/
|
|
38
27
|
export declare function detectEmailAlias(email: string): AliasDetectionResult;
|
|
39
28
|
/**
|
|
40
|
-
* Normalizes an email address to its canonical form
|
|
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
41
|
*/
|
|
42
42
|
export declare function normalizeEmail(email: string): string;
|
|
43
43
|
/**
|
|
44
|
-
* Checks if two email addresses are the same when normalized
|
|
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
|
+
* ```
|
|
45
57
|
*/
|
|
46
58
|
export declare function emailsMatch(email1: string, email2: string): boolean;
|
|
47
|
-
/**
|
|
48
|
-
* Gets alias capabilities for a domain
|
|
49
|
-
*/
|
|
50
|
-
export declare function getAliasCapabilities(domain: string): AliasRule | null;
|
|
51
|
-
/**
|
|
52
|
-
* Generates potential aliases for an email address
|
|
53
|
-
*/
|
|
54
|
-
export declare function generateAliases(email: string, options?: {
|
|
55
|
-
plusAliases?: string[];
|
|
56
|
-
includeDotVariations?: boolean;
|
|
57
|
-
maxDotVariations?: number;
|
|
58
|
-
}): string[];
|
|
59
|
-
/**
|
|
60
|
-
* Analyzes email aliasing patterns in a list of emails
|
|
61
|
-
*/
|
|
62
|
-
export declare function analyzeEmailAliases(emails: string[]): {
|
|
63
|
-
totalEmails: number;
|
|
64
|
-
uniqueCanonical: number;
|
|
65
|
-
aliasGroups: Array<{
|
|
66
|
-
canonical: string;
|
|
67
|
-
aliases: string[];
|
|
68
|
-
count: number;
|
|
69
|
-
}>;
|
|
70
|
-
providerStats: Record<string, {
|
|
71
|
-
total: number;
|
|
72
|
-
aliases: number;
|
|
73
|
-
types: Record<string, number>;
|
|
74
|
-
}>;
|
|
75
|
-
};
|
package/dist/alias-detection.js
CHANGED
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Email Alias Detection Module
|
|
4
4
|
*
|
|
5
|
+
* Clean, focused implementation with only essential functions.
|
|
5
6
|
* Detects and normalizes email aliases across different providers.
|
|
6
|
-
* Supports plus addressing (+), dot variations, and provider-specific aliasing.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.detectEmailAlias = detectEmailAlias;
|
|
10
10
|
exports.normalizeEmail = normalizeEmail;
|
|
11
11
|
exports.emailsMatch = emailsMatch;
|
|
12
|
-
exports.getAliasCapabilities = getAliasCapabilities;
|
|
13
|
-
exports.generateAliases = generateAliases;
|
|
14
|
-
exports.analyzeEmailAliases = analyzeEmailAliases;
|
|
15
12
|
/**
|
|
16
13
|
* Email alias rules for major providers
|
|
17
14
|
*/
|
|
@@ -20,7 +17,6 @@ const ALIAS_RULES = [
|
|
|
20
17
|
domains: ['gmail.com', 'googlemail.com'],
|
|
21
18
|
supportsPlusAddressing: true,
|
|
22
19
|
ignoresDots: true,
|
|
23
|
-
supportsSubdomainAlias: false,
|
|
24
20
|
normalize: (email) => {
|
|
25
21
|
const [username, domain] = email.toLowerCase().split('@');
|
|
26
22
|
// Remove dots and everything after +
|
|
@@ -32,7 +28,6 @@ const ALIAS_RULES = [
|
|
|
32
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'],
|
|
33
29
|
supportsPlusAddressing: true,
|
|
34
30
|
ignoresDots: false,
|
|
35
|
-
supportsSubdomainAlias: false,
|
|
36
31
|
normalize: (email) => {
|
|
37
32
|
const [username, domain] = email.toLowerCase().split('@');
|
|
38
33
|
// Only remove plus addressing for Outlook
|
|
@@ -44,7 +39,6 @@ const ALIAS_RULES = [
|
|
|
44
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'],
|
|
45
40
|
supportsPlusAddressing: true,
|
|
46
41
|
ignoresDots: false,
|
|
47
|
-
supportsSubdomainAlias: false,
|
|
48
42
|
normalize: (email) => {
|
|
49
43
|
const [username, domain] = email.toLowerCase().split('@');
|
|
50
44
|
const cleanUsername = username.split('+')[0];
|
|
@@ -55,7 +49,6 @@ const ALIAS_RULES = [
|
|
|
55
49
|
domains: ['fastmail.com', 'fastmail.fm'],
|
|
56
50
|
supportsPlusAddressing: true,
|
|
57
51
|
ignoresDots: false,
|
|
58
|
-
supportsSubdomainAlias: true,
|
|
59
52
|
normalize: (email) => {
|
|
60
53
|
const [username, domain] = email.toLowerCase().split('@');
|
|
61
54
|
const cleanUsername = username.split('+')[0];
|
|
@@ -66,7 +59,6 @@ const ALIAS_RULES = [
|
|
|
66
59
|
domains: ['proton.me', 'protonmail.com', 'protonmail.ch', 'pm.me'],
|
|
67
60
|
supportsPlusAddressing: true,
|
|
68
61
|
ignoresDots: false,
|
|
69
|
-
supportsSubdomainAlias: false,
|
|
70
62
|
normalize: (email) => {
|
|
71
63
|
const [username, domain] = email.toLowerCase().split('@');
|
|
72
64
|
const cleanUsername = username.split('+')[0];
|
|
@@ -77,7 +69,6 @@ const ALIAS_RULES = [
|
|
|
77
69
|
domains: ['tutanota.com', 'tutanota.de', 'tutamail.com', 'tuta.io', 'keemail.me', 'tuta.com'],
|
|
78
70
|
supportsPlusAddressing: true,
|
|
79
71
|
ignoresDots: false,
|
|
80
|
-
supportsSubdomainAlias: false,
|
|
81
72
|
normalize: (email) => {
|
|
82
73
|
const [username, domain] = email.toLowerCase().split('@');
|
|
83
74
|
const cleanUsername = username.split('+')[0];
|
|
@@ -88,7 +79,6 @@ const ALIAS_RULES = [
|
|
|
88
79
|
domains: ['zoho.com', 'zohomail.com', 'zoho.eu'],
|
|
89
80
|
supportsPlusAddressing: true,
|
|
90
81
|
ignoresDots: false,
|
|
91
|
-
supportsSubdomainAlias: false,
|
|
92
82
|
normalize: (email) => {
|
|
93
83
|
const [username, domain] = email.toLowerCase().split('@');
|
|
94
84
|
const cleanUsername = username.split('+')[0];
|
|
@@ -99,7 +89,6 @@ const ALIAS_RULES = [
|
|
|
99
89
|
domains: ['icloud.com', 'me.com', 'mac.com'],
|
|
100
90
|
supportsPlusAddressing: true,
|
|
101
91
|
ignoresDots: false,
|
|
102
|
-
supportsSubdomainAlias: false,
|
|
103
92
|
normalize: (email) => {
|
|
104
93
|
const [username, domain] = email.toLowerCase().split('@');
|
|
105
94
|
const cleanUsername = username.split('+')[0];
|
|
@@ -110,7 +99,6 @@ const ALIAS_RULES = [
|
|
|
110
99
|
domains: ['mail.com'],
|
|
111
100
|
supportsPlusAddressing: true,
|
|
112
101
|
ignoresDots: false,
|
|
113
|
-
supportsSubdomainAlias: false,
|
|
114
102
|
normalize: (email) => {
|
|
115
103
|
const [username, domain] = email.toLowerCase().split('@');
|
|
116
104
|
const cleanUsername = username.split('+')[0];
|
|
@@ -121,14 +109,12 @@ const ALIAS_RULES = [
|
|
|
121
109
|
domains: ['aol.com', 'love.com', 'ygm.com', 'games.com', 'wow.com', 'aim.com'],
|
|
122
110
|
supportsPlusAddressing: false,
|
|
123
111
|
ignoresDots: false,
|
|
124
|
-
supportsSubdomainAlias: false,
|
|
125
112
|
normalize: (email) => email.toLowerCase()
|
|
126
113
|
},
|
|
127
114
|
{
|
|
128
115
|
domains: ['mail.ru'],
|
|
129
116
|
supportsPlusAddressing: true,
|
|
130
117
|
ignoresDots: false,
|
|
131
|
-
supportsSubdomainAlias: false,
|
|
132
118
|
normalize: (email) => {
|
|
133
119
|
const [username, domain] = email.toLowerCase().split('@');
|
|
134
120
|
const cleanUsername = username.split('+')[0];
|
|
@@ -139,7 +125,6 @@ const ALIAS_RULES = [
|
|
|
139
125
|
domains: ['yandex.com', 'yandex.ru'],
|
|
140
126
|
supportsPlusAddressing: true,
|
|
141
127
|
ignoresDots: false,
|
|
142
|
-
supportsSubdomainAlias: false,
|
|
143
128
|
normalize: (email) => {
|
|
144
129
|
const [username, domain] = email.toLowerCase().split('@');
|
|
145
130
|
const cleanUsername = username.split('+')[0];
|
|
@@ -162,6 +147,9 @@ function getAliasRule(domain) {
|
|
|
162
147
|
}
|
|
163
148
|
/**
|
|
164
149
|
* Detects and analyzes email aliases
|
|
150
|
+
*
|
|
151
|
+
* @param email - Email address to analyze
|
|
152
|
+
* @returns Detailed analysis of the email alias
|
|
165
153
|
*/
|
|
166
154
|
function detectEmailAlias(email) {
|
|
167
155
|
if (!isValidEmail(email)) {
|
|
@@ -215,14 +203,37 @@ function detectEmailAlias(email) {
|
|
|
215
203
|
return result;
|
|
216
204
|
}
|
|
217
205
|
/**
|
|
218
|
-
* Normalizes an email address to its canonical form
|
|
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
|
+
* ```
|
|
219
218
|
*/
|
|
220
219
|
function normalizeEmail(email) {
|
|
221
220
|
const result = detectEmailAlias(email);
|
|
222
221
|
return result.canonical;
|
|
223
222
|
}
|
|
224
223
|
/**
|
|
225
|
-
* Checks if two email addresses are the same when normalized
|
|
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
|
+
* ```
|
|
226
237
|
*/
|
|
227
238
|
function emailsMatch(email1, email2) {
|
|
228
239
|
try {
|
|
@@ -232,89 +243,3 @@ function emailsMatch(email1, email2) {
|
|
|
232
243
|
return false;
|
|
233
244
|
}
|
|
234
245
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Gets alias capabilities for a domain
|
|
237
|
-
*/
|
|
238
|
-
function getAliasCapabilities(domain) {
|
|
239
|
-
const rule = getAliasRule(domain.toLowerCase());
|
|
240
|
-
return rule ? { ...rule } : null;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Generates potential aliases for an email address
|
|
244
|
-
*/
|
|
245
|
-
function generateAliases(email, options = {}) {
|
|
246
|
-
if (!isValidEmail(email)) {
|
|
247
|
-
throw new Error('Invalid email format');
|
|
248
|
-
}
|
|
249
|
-
const [username, domain] = email.toLowerCase().split('@');
|
|
250
|
-
const rule = getAliasRule(domain);
|
|
251
|
-
const aliases = [];
|
|
252
|
-
if (!rule) {
|
|
253
|
-
return [email.toLowerCase()];
|
|
254
|
-
}
|
|
255
|
-
// Generate plus aliases
|
|
256
|
-
if (rule.supportsPlusAddressing && options.plusAliases) {
|
|
257
|
-
for (const alias of options.plusAliases) {
|
|
258
|
-
aliases.push(`${username}+${alias}@${domain}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// Generate dot variations (Gmail only)
|
|
262
|
-
if (rule.ignoresDots && options.includeDotVariations && username.length > 1) {
|
|
263
|
-
const maxVariations = Math.min(options.maxDotVariations ?? 5, username.length - 1);
|
|
264
|
-
const variations = new Set();
|
|
265
|
-
// Simple dot variations (insert dots between characters)
|
|
266
|
-
for (let i = 1; i < username.length && variations.size < maxVariations; i++) {
|
|
267
|
-
const withDot = username.slice(0, i) + '.' + username.slice(i);
|
|
268
|
-
variations.add(`${withDot}@${domain}`);
|
|
269
|
-
}
|
|
270
|
-
aliases.push(...Array.from(variations));
|
|
271
|
-
}
|
|
272
|
-
return aliases.length > 0 ? aliases : [email.toLowerCase()];
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Analyzes email aliasing patterns in a list of emails
|
|
276
|
-
*/
|
|
277
|
-
function analyzeEmailAliases(emails) {
|
|
278
|
-
const canonicalMap = new Map();
|
|
279
|
-
const providerStats = {};
|
|
280
|
-
for (const email of emails) {
|
|
281
|
-
try {
|
|
282
|
-
const result = detectEmailAlias(email);
|
|
283
|
-
const canonical = result.canonical;
|
|
284
|
-
if (!canonicalMap.has(canonical)) {
|
|
285
|
-
canonicalMap.set(canonical, []);
|
|
286
|
-
}
|
|
287
|
-
canonicalMap.get(canonical).push(email);
|
|
288
|
-
// Update provider stats
|
|
289
|
-
if (result.provider) {
|
|
290
|
-
if (!providerStats[result.provider]) {
|
|
291
|
-
providerStats[result.provider] = {
|
|
292
|
-
total: 0,
|
|
293
|
-
aliases: 0,
|
|
294
|
-
types: {}
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
providerStats[result.provider].total++;
|
|
298
|
-
if (result.isAlias) {
|
|
299
|
-
providerStats[result.provider].aliases++;
|
|
300
|
-
const type = result.aliasType;
|
|
301
|
-
providerStats[result.provider].types[type] = (providerStats[result.provider].types[type] || 0) + 1;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
catch {
|
|
306
|
-
// Skip invalid emails
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
const aliasGroups = Array.from(canonicalMap.entries()).map(([canonical, aliases]) => ({
|
|
310
|
-
canonical,
|
|
311
|
-
aliases,
|
|
312
|
-
count: aliases.length
|
|
313
|
-
})).sort((a, b) => b.count - a.count);
|
|
314
|
-
return {
|
|
315
|
-
totalEmails: emails.length,
|
|
316
|
-
uniqueCanonical: canonicalMap.size,
|
|
317
|
-
aliasGroups,
|
|
318
|
-
providerStats
|
|
319
|
-
};
|
|
320
|
-
}
|