@mikkelscheike/email-provider-links 1.5.1 โ 1.7.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 +137 -7
- package/dist/alias-detection.d.ts +75 -0
- package/dist/alias-detection.js +320 -0
- package/dist/index.d.ts +64 -6
- package/dist/index.js +122 -11
- package/dist/security/hash-verifier.js +3 -3
- package/package.json +7 -2
- package/providers/emailproviders.json +864 -290
package/README.md
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
# Email Provider Links
|
|
2
2
|
|
|
3
|
-
๐ **Enterprise-grade secure email provider detection
|
|
3
|
+
๐ **Enterprise-grade secure email provider detection with advanced alias normalization**
|
|
4
4
|
|
|
5
|
-
A TypeScript package
|
|
5
|
+
A TypeScript package providing direct links to **93 email providers** (180+ domains) with **email alias detection**, comprehensive security features, and international coverage 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
|
+
- ๐ **180+ Domains Supported**: Comprehensive international coverage
|
|
11
12
|
- ๐ข **Business Domain Detection**: DNS-based detection for custom domains (Google Workspace, Microsoft 365, etc.)
|
|
12
13
|
- ๐ **Enterprise Security**: Multi-layer protection against malicious URLs and supply chain attacks
|
|
13
14
|
- ๐ก๏ธ **URL Validation**: HTTPS-only enforcement with domain allowlisting
|
|
14
15
|
- ๐ **Integrity Verification**: Cryptographic hash verification for data integrity
|
|
15
16
|
- ๐ **Type Safe**: Full TypeScript support with comprehensive interfaces
|
|
16
17
|
- โก **Performance Optimized**: Smart DNS fallback with configurable timeouts
|
|
17
|
-
-
|
|
18
|
+
- ๐ฆ **Rate Limiting**: Built-in DNS query rate limiting to prevent abuse
|
|
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**: 188+ tests with comprehensive coverage
|
|
18
22
|
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
@@ -38,11 +42,34 @@ console.log(business.provider?.companyProvider); // "Google Workspace" (if detec
|
|
|
38
42
|
|
|
39
43
|
## Supported Providers
|
|
40
44
|
|
|
41
|
-
**
|
|
42
|
-
|
|
45
|
+
**๐ Current Coverage: 93 providers supporting 180+ domains**
|
|
46
|
+
|
|
47
|
+
**Consumer Email Providers:**
|
|
48
|
+
- **Gmail** (2 domains): gmail.com, googlemail.com
|
|
49
|
+
- **Microsoft Outlook** (15 domains): outlook.com, hotmail.com, live.com, msn.com, and 11 more
|
|
50
|
+
- **Yahoo Mail** (19 domains): yahoo.com, yahoo.co.uk, yahoo.fr, ymail.com, rocketmail.com, and 14 more
|
|
51
|
+
- **ProtonMail** (4 domains): proton.me, protonmail.com, protonmail.ch, pm.me
|
|
52
|
+
- **iCloud Mail** (3 domains): icloud.com, me.com, mac.com
|
|
53
|
+
- **Tutanota** (6 domains): tutanota.com, tutanota.de, tutamail.com, tuta.io, keemail.me, tuta.com
|
|
54
|
+
- **SimpleLogin** (10 domains): simplelogin.io, 8alias.com, aleeas.com, slmail.me, and 6 more
|
|
55
|
+
- **FastMail, Zoho Mail, AOL Mail, GMX, Web.de, Mail.ru, QQ Mail, NetEase, Yandex**, and many more
|
|
43
56
|
|
|
44
57
|
**Business Email (via DNS detection):**
|
|
45
|
-
Microsoft 365
|
|
58
|
+
- **Microsoft 365** (Business domains via MX/TXT records)
|
|
59
|
+
- **Google Workspace** (Custom domains via DNS patterns)
|
|
60
|
+
- **Amazon WorkMail** (AWS email infrastructure via awsapps.com patterns)
|
|
61
|
+
- **Zoho Workplace, FastMail Business, GoDaddy Email, Bluehost Email**
|
|
62
|
+
- **ProtonMail Business, Rackspace Email, IONOS**, and others
|
|
63
|
+
|
|
64
|
+
**Security & Privacy Focused:**
|
|
65
|
+
- **ProtonMail, Tutanota, Hushmail, CounterMail, Posteo**
|
|
66
|
+
- **Mailfence, SimpleLogin, AnonAddy**
|
|
67
|
+
|
|
68
|
+
**International Providers:**
|
|
69
|
+
- **Europe**: GMX, Web.de, Orange, Free.fr, T-Online, Libero, Virgilio, Telekom, Tiscali, Skynet, Telenet, Xs4All, Planet.nl, Bluewin, Eircom
|
|
70
|
+
- **Asia**: QQ Mail, NetEase, Sina Mail, Alibaba Mail, Rakuten, Nifty, **Naver** (Korea), **Daum** (Korea), **Biglobe** (Japan), Sify, IndiatTimes (India)
|
|
71
|
+
- **Eastern Europe**: Centrum (Czech/Slovak), Interia, Onet (Poland), Rambler (Russia)
|
|
72
|
+
- **Other Regions**: UOL, Terra (Brazil), Telkom (South Africa), Xtra (New Zealand)
|
|
46
73
|
|
|
47
74
|
## API
|
|
48
75
|
|
|
@@ -76,6 +103,94 @@ async function handlePasswordReset(email: string) {
|
|
|
76
103
|
}
|
|
77
104
|
```
|
|
78
105
|
|
|
106
|
+
## ๐ Email Alias Detection
|
|
107
|
+
|
|
108
|
+
**NEW in v1.7.0** - Advanced email alias detection and normalization to prevent duplicate accounts and improve user experience.
|
|
109
|
+
|
|
110
|
+
### Features
|
|
111
|
+
|
|
112
|
+
- **Gmail Dot Normalization**: `u.s.e.r@gmail.com` โ `user@gmail.com`
|
|
113
|
+
- **Plus Addressing**: `user+newsletter@provider.com` โ `user@provider.com`
|
|
114
|
+
- **Provider-Specific Rules**: Different providers have different aliasing capabilities
|
|
115
|
+
- **Fraud Prevention**: Detect when users try to create multiple accounts with aliases
|
|
116
|
+
- **Email Deduplication**: Normalize email lists to get accurate unique user counts
|
|
117
|
+
|
|
118
|
+
### Quick Start
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import {
|
|
122
|
+
detectEmailAlias,
|
|
123
|
+
normalizeEmail,
|
|
124
|
+
emailsMatch
|
|
125
|
+
} from '@mikkelscheike/email-provider-links';
|
|
126
|
+
|
|
127
|
+
// Detect and analyze aliases
|
|
128
|
+
const result = detectEmailAlias('u.s.e.r+work@gmail.com');
|
|
129
|
+
console.log(result.canonical); // 'user@gmail.com'
|
|
130
|
+
console.log(result.isAlias); // true
|
|
131
|
+
console.log(result.aliasType); // 'plus'
|
|
132
|
+
console.log(result.aliasPart); // 'work'
|
|
133
|
+
|
|
134
|
+
// Normalize any email to canonical form
|
|
135
|
+
const canonical = normalizeEmail('U.S.E.R+Newsletter@GMAIL.COM');
|
|
136
|
+
console.log(canonical); // 'user@gmail.com'
|
|
137
|
+
|
|
138
|
+
// Check if two emails are the same person
|
|
139
|
+
const match = emailsMatch('user@gmail.com', 'u.s.e.r+work@gmail.com');
|
|
140
|
+
console.log(match); // true
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Provider Support
|
|
144
|
+
|
|
145
|
+
| Provider | Plus Addressing | Dots Ignored | Example |
|
|
146
|
+
|----------|----------------|--------------|----------|
|
|
147
|
+
| **Gmail** | โ
Yes | โ
Yes | `u.s.e.r+tag@gmail.com` โ `user@gmail.com` |
|
|
148
|
+
| **Outlook** | โ
Yes | โ No | `user+tag@outlook.com` โ `user@outlook.com` |
|
|
149
|
+
| **Yahoo** | โ
Yes | โ No | `user+tag@yahoo.com` โ `user@yahoo.com` |
|
|
150
|
+
| **ProtonMail** | โ
Yes | โ No | `user+tag@proton.me` โ `user@proton.me` |
|
|
151
|
+
| **AOL** | โ No | โ No | No aliasing support |
|
|
152
|
+
|
|
153
|
+
### Advanced Usage
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Prevent duplicate account creation
|
|
157
|
+
async function registerUser(email: string) {
|
|
158
|
+
const canonical = normalizeEmail(email);
|
|
159
|
+
const existingUser = await findUserByEmail(canonical);
|
|
160
|
+
|
|
161
|
+
if (existingUser) {
|
|
162
|
+
throw new Error('Email already registered');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Store canonical email to prevent future duplicates
|
|
166
|
+
await createUser({ email: canonical });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Analyze email list for duplicates
|
|
170
|
+
import { analyzeEmailAliases } from '@mikkelscheike/email-provider-links';
|
|
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';
|
|
185
|
+
|
|
186
|
+
const aliases = generateAliases('user@gmail.com', {
|
|
187
|
+
plusAliases: ['work', 'personal'],
|
|
188
|
+
includeDotVariations: true,
|
|
189
|
+
maxDotVariations: 3
|
|
190
|
+
});
|
|
191
|
+
// Returns: ['user+work@gmail.com', 'user+personal@gmail.com', 'u.ser@gmail.com', ...]
|
|
192
|
+
```
|
|
193
|
+
|
|
79
194
|
## Configuration
|
|
80
195
|
|
|
81
196
|
```typescript
|
|
@@ -85,6 +200,14 @@ const result = await getEmailProviderLinkWithDNS(email, 2000);
|
|
|
85
200
|
// Check if provider is supported
|
|
86
201
|
import { isEmailProviderSupported } from '@mikkelscheike/email-provider-links';
|
|
87
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
|
|
208
|
+
|
|
209
|
+
// Custom rate limiter for specific use cases
|
|
210
|
+
const customLimiter = new RateLimit.SimpleRateLimiter(20, 120000); // 20 requests per 2 minutes
|
|
88
211
|
```
|
|
89
212
|
|
|
90
213
|
## TypeScript Support
|
|
@@ -97,6 +220,13 @@ interface EmailProviderResult {
|
|
|
97
220
|
detectionMethod?: 'domain_match' | 'mx_record' | 'txt_record' | 'proxy_detected';
|
|
98
221
|
proxyService?: string;
|
|
99
222
|
}
|
|
223
|
+
|
|
224
|
+
interface RateLimitConfig {
|
|
225
|
+
MAX_REQUESTS: number; // 10 requests
|
|
226
|
+
WINDOW_MS: number; // 60000ms (1 minute)
|
|
227
|
+
SimpleRateLimiter: class; // Custom rate limiter class
|
|
228
|
+
getCurrentLimiter(): SimpleRateLimiter; // Get current global limiter
|
|
229
|
+
}
|
|
100
230
|
```
|
|
101
231
|
|
|
102
232
|
## ๐ก๏ธ Security Features
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Alias Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Detects and normalizes email aliases across different providers.
|
|
5
|
+
* Supports plus addressing (+), dot variations, and provider-specific aliasing.
|
|
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' | 'subdomain' | 'none';
|
|
16
|
+
/** The alias part (if any) */
|
|
17
|
+
aliasPart?: string;
|
|
18
|
+
/** The provider that supports this alias type */
|
|
19
|
+
provider?: string;
|
|
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
|
+
/**
|
|
36
|
+
* Detects and analyzes email aliases
|
|
37
|
+
*/
|
|
38
|
+
export declare function detectEmailAlias(email: string): AliasDetectionResult;
|
|
39
|
+
/**
|
|
40
|
+
* Normalizes an email address to its canonical form
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizeEmail(email: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Checks if two email addresses are the same when normalized
|
|
45
|
+
*/
|
|
46
|
+
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
|
+
};
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Email Alias Detection Module
|
|
4
|
+
*
|
|
5
|
+
* Detects and normalizes email aliases across different providers.
|
|
6
|
+
* Supports plus addressing (+), dot variations, and provider-specific aliasing.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.detectEmailAlias = detectEmailAlias;
|
|
10
|
+
exports.normalizeEmail = normalizeEmail;
|
|
11
|
+
exports.emailsMatch = emailsMatch;
|
|
12
|
+
exports.getAliasCapabilities = getAliasCapabilities;
|
|
13
|
+
exports.generateAliases = generateAliases;
|
|
14
|
+
exports.analyzeEmailAliases = analyzeEmailAliases;
|
|
15
|
+
/**
|
|
16
|
+
* Email alias rules for major providers
|
|
17
|
+
*/
|
|
18
|
+
const ALIAS_RULES = [
|
|
19
|
+
{
|
|
20
|
+
domains: ['gmail.com', 'googlemail.com'],
|
|
21
|
+
supportsPlusAddressing: true,
|
|
22
|
+
ignoresDots: true,
|
|
23
|
+
supportsSubdomainAlias: false,
|
|
24
|
+
normalize: (email) => {
|
|
25
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
26
|
+
// Remove dots and everything after +
|
|
27
|
+
const cleanUsername = username.replace(/\./g, '').split('+')[0];
|
|
28
|
+
return `${cleanUsername}@${domain}`;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
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
|
+
supportsPlusAddressing: true,
|
|
34
|
+
ignoresDots: false,
|
|
35
|
+
supportsSubdomainAlias: false,
|
|
36
|
+
normalize: (email) => {
|
|
37
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
38
|
+
// Only remove plus addressing for Outlook
|
|
39
|
+
const cleanUsername = username.split('+')[0];
|
|
40
|
+
return `${cleanUsername}@${domain}`;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
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
|
+
supportsPlusAddressing: true,
|
|
46
|
+
ignoresDots: false,
|
|
47
|
+
supportsSubdomainAlias: false,
|
|
48
|
+
normalize: (email) => {
|
|
49
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
50
|
+
const cleanUsername = username.split('+')[0];
|
|
51
|
+
return `${cleanUsername}@${domain}`;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
domains: ['fastmail.com', 'fastmail.fm'],
|
|
56
|
+
supportsPlusAddressing: true,
|
|
57
|
+
ignoresDots: false,
|
|
58
|
+
supportsSubdomainAlias: true,
|
|
59
|
+
normalize: (email) => {
|
|
60
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
61
|
+
const cleanUsername = username.split('+')[0];
|
|
62
|
+
return `${cleanUsername}@${domain}`;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
domains: ['proton.me', 'protonmail.com', 'protonmail.ch', 'pm.me'],
|
|
67
|
+
supportsPlusAddressing: true,
|
|
68
|
+
ignoresDots: false,
|
|
69
|
+
supportsSubdomainAlias: false,
|
|
70
|
+
normalize: (email) => {
|
|
71
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
72
|
+
const cleanUsername = username.split('+')[0];
|
|
73
|
+
return `${cleanUsername}@${domain}`;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
domains: ['tutanota.com', 'tutanota.de', 'tutamail.com', 'tuta.io', 'keemail.me', 'tuta.com'],
|
|
78
|
+
supportsPlusAddressing: true,
|
|
79
|
+
ignoresDots: false,
|
|
80
|
+
supportsSubdomainAlias: false,
|
|
81
|
+
normalize: (email) => {
|
|
82
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
83
|
+
const cleanUsername = username.split('+')[0];
|
|
84
|
+
return `${cleanUsername}@${domain}`;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
domains: ['zoho.com', 'zohomail.com', 'zoho.eu'],
|
|
89
|
+
supportsPlusAddressing: true,
|
|
90
|
+
ignoresDots: false,
|
|
91
|
+
supportsSubdomainAlias: 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: ['icloud.com', 'me.com', 'mac.com'],
|
|
100
|
+
supportsPlusAddressing: true,
|
|
101
|
+
ignoresDots: false,
|
|
102
|
+
supportsSubdomainAlias: false,
|
|
103
|
+
normalize: (email) => {
|
|
104
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
105
|
+
const cleanUsername = username.split('+')[0];
|
|
106
|
+
return `${cleanUsername}@${domain}`;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
domains: ['mail.com'],
|
|
111
|
+
supportsPlusAddressing: true,
|
|
112
|
+
ignoresDots: false,
|
|
113
|
+
supportsSubdomainAlias: false,
|
|
114
|
+
normalize: (email) => {
|
|
115
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
116
|
+
const cleanUsername = username.split('+')[0];
|
|
117
|
+
return `${cleanUsername}@${domain}`;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
domains: ['aol.com', 'love.com', 'ygm.com', 'games.com', 'wow.com', 'aim.com'],
|
|
122
|
+
supportsPlusAddressing: false,
|
|
123
|
+
ignoresDots: false,
|
|
124
|
+
supportsSubdomainAlias: false,
|
|
125
|
+
normalize: (email) => email.toLowerCase()
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
domains: ['mail.ru'],
|
|
129
|
+
supportsPlusAddressing: true,
|
|
130
|
+
ignoresDots: false,
|
|
131
|
+
supportsSubdomainAlias: false,
|
|
132
|
+
normalize: (email) => {
|
|
133
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
134
|
+
const cleanUsername = username.split('+')[0];
|
|
135
|
+
return `${cleanUsername}@${domain}`;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
domains: ['yandex.com', 'yandex.ru'],
|
|
140
|
+
supportsPlusAddressing: true,
|
|
141
|
+
ignoresDots: false,
|
|
142
|
+
supportsSubdomainAlias: false,
|
|
143
|
+
normalize: (email) => {
|
|
144
|
+
const [username, domain] = email.toLowerCase().split('@');
|
|
145
|
+
const cleanUsername = username.split('+')[0];
|
|
146
|
+
return `${cleanUsername}@${domain}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
/**
|
|
151
|
+
* Validates email format
|
|
152
|
+
*/
|
|
153
|
+
function isValidEmail(email) {
|
|
154
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
155
|
+
return emailRegex.test(email);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Gets the alias rule for a given domain
|
|
159
|
+
*/
|
|
160
|
+
function getAliasRule(domain) {
|
|
161
|
+
return ALIAS_RULES.find(rule => rule.domains.includes(domain.toLowerCase()));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Detects and analyzes email aliases
|
|
165
|
+
*/
|
|
166
|
+
function detectEmailAlias(email) {
|
|
167
|
+
if (!isValidEmail(email)) {
|
|
168
|
+
throw new Error('Invalid email format');
|
|
169
|
+
}
|
|
170
|
+
const originalEmail = email.trim();
|
|
171
|
+
const [username, domain] = originalEmail.toLowerCase().split('@');
|
|
172
|
+
const rule = getAliasRule(domain);
|
|
173
|
+
const result = {
|
|
174
|
+
canonical: originalEmail.toLowerCase(),
|
|
175
|
+
original: originalEmail,
|
|
176
|
+
isAlias: false,
|
|
177
|
+
aliasType: 'none'
|
|
178
|
+
};
|
|
179
|
+
if (!rule) {
|
|
180
|
+
// No specific rule, just normalize case
|
|
181
|
+
result.canonical = originalEmail.toLowerCase();
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
result.provider = domain;
|
|
185
|
+
// Check for plus addressing
|
|
186
|
+
if (rule.supportsPlusAddressing && username.includes('+')) {
|
|
187
|
+
const plusIndex = username.indexOf('+');
|
|
188
|
+
const baseUsername = username.substring(0, plusIndex);
|
|
189
|
+
const aliasPart = username.substring(plusIndex + 1);
|
|
190
|
+
result.isAlias = true;
|
|
191
|
+
result.aliasType = 'plus';
|
|
192
|
+
result.aliasPart = aliasPart;
|
|
193
|
+
result.canonical = rule.normalize ? rule.normalize(originalEmail) : `${baseUsername}@${domain}`;
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
// Check for dot variations (Gmail)
|
|
197
|
+
if (rule.ignoresDots && username.includes('.')) {
|
|
198
|
+
const baseUsername = username.replace(/\./g, '');
|
|
199
|
+
if (baseUsername !== username) {
|
|
200
|
+
result.isAlias = true;
|
|
201
|
+
result.aliasType = 'dot';
|
|
202
|
+
result.aliasPart = username;
|
|
203
|
+
result.canonical = rule.normalize ? rule.normalize(originalEmail) : `${baseUsername}@${domain}`;
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Apply provider-specific normalization
|
|
208
|
+
if (rule.normalize) {
|
|
209
|
+
const normalized = rule.normalize(originalEmail);
|
|
210
|
+
if (normalized !== originalEmail.toLowerCase()) {
|
|
211
|
+
result.isAlias = true;
|
|
212
|
+
result.canonical = normalized;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Normalizes an email address to its canonical form
|
|
219
|
+
*/
|
|
220
|
+
function normalizeEmail(email) {
|
|
221
|
+
const result = detectEmailAlias(email);
|
|
222
|
+
return result.canonical;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Checks if two email addresses are the same when normalized
|
|
226
|
+
*/
|
|
227
|
+
function emailsMatch(email1, email2) {
|
|
228
|
+
try {
|
|
229
|
+
return normalizeEmail(email1) === normalizeEmail(email2);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
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
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,30 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @packageDocumentation
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* Simple rate limiter to prevent excessive DNS queries.
|
|
15
|
+
* Tracks request timestamps and enforces a maximum number of requests per time window.
|
|
16
|
+
*/
|
|
17
|
+
declare class SimpleRateLimiter {
|
|
18
|
+
private maxRequests;
|
|
19
|
+
private windowMs;
|
|
20
|
+
private requestTimestamps;
|
|
21
|
+
constructor(maxRequests?: number, windowMs?: number);
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a request is allowed under the current rate limit.
|
|
24
|
+
* @returns true if request is allowed, false if rate limited
|
|
25
|
+
*/
|
|
26
|
+
isAllowed(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Gets the current number of requests in the window.
|
|
29
|
+
*/
|
|
30
|
+
getCurrentCount(): number;
|
|
31
|
+
/**
|
|
32
|
+
* Gets the time until the rate limit resets (when oldest request expires).
|
|
33
|
+
* @returns milliseconds until reset, or 0 if not rate limited
|
|
34
|
+
*/
|
|
35
|
+
getTimeUntilReset(): number;
|
|
36
|
+
}
|
|
13
37
|
/**
|
|
14
38
|
* Represents an email provider with its associated domains and login URL.
|
|
15
39
|
*
|
|
@@ -226,12 +250,18 @@ export declare function isEmailProviderSupported(email: string): boolean;
|
|
|
226
250
|
*
|
|
227
251
|
* @remarks
|
|
228
252
|
* **Detection Algorithm:**
|
|
229
|
-
* 1.
|
|
230
|
-
* 2.
|
|
231
|
-
* 3.
|
|
232
|
-
* 4.
|
|
233
|
-
* 5.
|
|
234
|
-
* 6.
|
|
253
|
+
* 1. Checks rate limiting (max 10 requests per minute)
|
|
254
|
+
* 2. Performs MX record lookup for the domain
|
|
255
|
+
* 3. Checks if MX records match known proxy services (Cloudflare, etc.)
|
|
256
|
+
* 4. If proxy detected, returns null provider with proxy info
|
|
257
|
+
* 5. Otherwise, matches MX records against business email provider patterns
|
|
258
|
+
* 6. If no MX match, falls back to TXT record analysis (SPF records, etc.)
|
|
259
|
+
* 7. Returns the first matching provider or null if none found
|
|
260
|
+
*
|
|
261
|
+
* **Rate Limiting:**
|
|
262
|
+
* - Maximum 10 DNS requests per 60-second window
|
|
263
|
+
* - Rate limit exceeded returns error with retry information
|
|
264
|
+
* - Prevents abuse and excessive DNS queries
|
|
235
265
|
*
|
|
236
266
|
* **Provider Patterns Checked:**
|
|
237
267
|
* - Google Workspace: aspmx.l.google.com, aspmx2.googlemail.com, etc.
|
|
@@ -303,6 +333,24 @@ export declare function detectProviderByDNS(domain: string, timeoutMs?: number):
|
|
|
303
333
|
* - Business domain analysis for enterprise features
|
|
304
334
|
*/
|
|
305
335
|
export declare function getEmailProviderLinkWithDNS(email: string, timeoutMs?: number): Promise<EmailProviderResult>;
|
|
336
|
+
/**
|
|
337
|
+
* Rate limiting configuration constants and utilities.
|
|
338
|
+
* @public
|
|
339
|
+
*/
|
|
340
|
+
export declare const RateLimit: {
|
|
341
|
+
MAX_REQUESTS: number;
|
|
342
|
+
WINDOW_MS: number;
|
|
343
|
+
SimpleRateLimiter: typeof SimpleRateLimiter;
|
|
344
|
+
/**
|
|
345
|
+
* Gets the current rate limiter instance for inspection or testing.
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
348
|
+
getCurrentLimiter: () => SimpleRateLimiter;
|
|
349
|
+
};
|
|
350
|
+
export * from './alias-detection';
|
|
351
|
+
export * from './security/url-validator';
|
|
352
|
+
export * from './security/hash-verifier';
|
|
353
|
+
export * from './security/secure-loader';
|
|
306
354
|
declare const _default: {
|
|
307
355
|
getEmailProviderLink: typeof getEmailProviderLink;
|
|
308
356
|
getEmailProviderLinkWithDNS: typeof getEmailProviderLinkWithDNS;
|
|
@@ -312,5 +360,15 @@ declare const _default: {
|
|
|
312
360
|
findEmailProvider: typeof findEmailProvider;
|
|
313
361
|
getSupportedProviders: typeof getSupportedProviders;
|
|
314
362
|
isEmailProviderSupported: typeof isEmailProviderSupported;
|
|
363
|
+
RateLimit: {
|
|
364
|
+
MAX_REQUESTS: number;
|
|
365
|
+
WINDOW_MS: number;
|
|
366
|
+
SimpleRateLimiter: typeof SimpleRateLimiter;
|
|
367
|
+
/**
|
|
368
|
+
* Gets the current rate limiter instance for inspection or testing.
|
|
369
|
+
* @internal
|
|
370
|
+
*/
|
|
371
|
+
getCurrentLimiter: () => SimpleRateLimiter;
|
|
372
|
+
};
|
|
315
373
|
};
|
|
316
374
|
export default _default;
|