@mikkelscheike/email-provider-links 1.6.0 โ 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 +100 -9
- package/dist/alias-detection.d.ts +75 -0
- package/dist/alias-detection.js +320 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/security/hash-verifier.js +2 -2
- package/package.json +7 -2
- package/providers/emailproviders.json +156 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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
|
-
- ๐ง **
|
|
11
|
-
- ๐ **
|
|
10
|
+
- ๐ง **93 Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and many more
|
|
11
|
+
- ๐ **180+ 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**: 188+ tests with comprehensive coverage
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
22
24
|
|
|
@@ -40,7 +42,7 @@ console.log(business.provider?.companyProvider); // "Google Workspace" (if detec
|
|
|
40
42
|
|
|
41
43
|
## Supported Providers
|
|
42
44
|
|
|
43
|
-
**๐ Current Coverage:
|
|
45
|
+
**๐ Current Coverage: 93 providers supporting 180+ domains**
|
|
44
46
|
|
|
45
47
|
**Consumer Email Providers:**
|
|
46
48
|
- **Gmail** (2 domains): gmail.com, googlemail.com
|
|
@@ -64,9 +66,10 @@ console.log(business.provider?.companyProvider); // "Google Workspace" (if detec
|
|
|
64
66
|
- **Mailfence, SimpleLogin, AnonAddy**
|
|
65
67
|
|
|
66
68
|
**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
|
-
- **
|
|
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)
|
|
70
73
|
|
|
71
74
|
## API
|
|
72
75
|
|
|
@@ -100,6 +103,94 @@ async function handlePasswordReset(email: string) {
|
|
|
100
103
|
}
|
|
101
104
|
```
|
|
102
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
|
+
|
|
103
194
|
## Configuration
|
|
104
195
|
|
|
105
196
|
```typescript
|
|
@@ -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
|
@@ -347,6 +347,10 @@ export declare const RateLimit: {
|
|
|
347
347
|
*/
|
|
348
348
|
getCurrentLimiter: () => SimpleRateLimiter;
|
|
349
349
|
};
|
|
350
|
+
export * from './alias-detection';
|
|
351
|
+
export * from './security/url-validator';
|
|
352
|
+
export * from './security/hash-verifier';
|
|
353
|
+
export * from './security/secure-loader';
|
|
350
354
|
declare const _default: {
|
|
351
355
|
getEmailProviderLink: typeof getEmailProviderLink;
|
|
352
356
|
getEmailProviderLinkWithDNS: typeof getEmailProviderLinkWithDNS;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,20 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @packageDocumentation
|
|
13
13
|
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
26
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
27
|
+
};
|
|
14
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
29
|
exports.RateLimit = void 0;
|
|
16
30
|
exports.isValidEmail = isValidEmail;
|
|
@@ -592,6 +606,12 @@ exports.RateLimit = {
|
|
|
592
606
|
*/
|
|
593
607
|
getCurrentLimiter: () => dnsRateLimiter
|
|
594
608
|
};
|
|
609
|
+
// Export alias detection module
|
|
610
|
+
__exportStar(require("./alias-detection"), exports);
|
|
611
|
+
// Export security modules
|
|
612
|
+
__exportStar(require("./security/url-validator"), exports);
|
|
613
|
+
__exportStar(require("./security/hash-verifier"), exports);
|
|
614
|
+
__exportStar(require("./security/secure-loader"), exports);
|
|
595
615
|
// Default export for convenience
|
|
596
616
|
exports.default = {
|
|
597
617
|
getEmailProviderLink,
|
|
@@ -27,9 +27,9 @@ const path_1 = require("path");
|
|
|
27
27
|
*/
|
|
28
28
|
const KNOWN_GOOD_HASHES = {
|
|
29
29
|
// SHA-256 hash of the legitimate emailproviders.json
|
|
30
|
-
'emailproviders.json': '
|
|
30
|
+
'emailproviders.json': '41d2084580270f77ae2c2c5593995ebace7877a74f7d1c1e1bc7ed3b37611aa4',
|
|
31
31
|
// You can add hashes for other critical files
|
|
32
|
-
'package.json': '
|
|
32
|
+
'package.json': 'c73f9f005cc4204d321cb1382f80354e932dffc38804600cdcf307ef11a0525e'
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* Calculates SHA-256 hash of a file or string content
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikkelscheike/email-provider-links",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A TypeScript package providing direct links to
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "A TypeScript package providing direct links to 93 email providers (180+ domains) with enterprise security features, email alias detection, rate limiting, and comprehensive international coverage for login and password reset flows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"gmail",
|
|
31
31
|
"outlook",
|
|
32
32
|
"yahoo",
|
|
33
|
+
"alias-detection",
|
|
34
|
+
"email-normalization",
|
|
35
|
+
"plus-addressing",
|
|
36
|
+
"fraud-prevention",
|
|
37
|
+
"deduplication",
|
|
33
38
|
"typescript",
|
|
34
39
|
"npm",
|
|
35
40
|
"utility"
|
|
@@ -853,6 +853,161 @@
|
|
|
853
853
|
"domains": [
|
|
854
854
|
"zoho.eu"
|
|
855
855
|
]
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
"companyProvider": "Alibaba Mail",
|
|
859
|
+
"loginUrl": "https://mail.aliyun.com",
|
|
860
|
+
"domains": [
|
|
861
|
+
"aliyun.com",
|
|
862
|
+
"alibaba.com",
|
|
863
|
+
"taobao.com",
|
|
864
|
+
"tmall.com"
|
|
865
|
+
],
|
|
866
|
+
"customDomainDetection": {
|
|
867
|
+
"mxPatterns": [
|
|
868
|
+
"mx1.qiye.aliyun.com",
|
|
869
|
+
"mx2.qiye.aliyun.com",
|
|
870
|
+
"mx3.qiye.aliyun.com"
|
|
871
|
+
],
|
|
872
|
+
"txtPatterns": [
|
|
873
|
+
"v=spf1 include:spf.qiye.aliyun.com"
|
|
874
|
+
]
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"companyProvider": "Sify Mail",
|
|
879
|
+
"loginUrl": "https://mail.sify.com",
|
|
880
|
+
"domains": [
|
|
881
|
+
"sify.com",
|
|
882
|
+
"sifymail.com"
|
|
883
|
+
]
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
"companyProvider": "IndiatTimes Mail",
|
|
887
|
+
"loginUrl": "https://mail.indiatimes.com",
|
|
888
|
+
"domains": [
|
|
889
|
+
"indiatimes.com"
|
|
890
|
+
]
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
"companyProvider": "Virgilio Mail",
|
|
894
|
+
"loginUrl": "https://mail.virgilio.it",
|
|
895
|
+
"domains": [
|
|
896
|
+
"virgilio.it",
|
|
897
|
+
"alice.it",
|
|
898
|
+
"tin.it"
|
|
899
|
+
]
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
"companyProvider": "Telekom Mail",
|
|
903
|
+
"loginUrl": "https://email.magenta.de",
|
|
904
|
+
"domains": [
|
|
905
|
+
"magenta.de",
|
|
906
|
+
"telekom.de"
|
|
907
|
+
]
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
"companyProvider": "Tiscali Mail",
|
|
911
|
+
"loginUrl": "https://mail.tiscali.it",
|
|
912
|
+
"domains": [
|
|
913
|
+
"tiscali.it"
|
|
914
|
+
]
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
"companyProvider": "Skynet Mail",
|
|
918
|
+
"loginUrl": "https://webmail.skynet.be",
|
|
919
|
+
"domains": [
|
|
920
|
+
"skynet.be"
|
|
921
|
+
]
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
"companyProvider": "Telenet Mail",
|
|
925
|
+
"loginUrl": "https://webmail.telenet.be",
|
|
926
|
+
"domains": [
|
|
927
|
+
"telenet.be"
|
|
928
|
+
]
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
"companyProvider": "Xs4All",
|
|
932
|
+
"loginUrl": "https://webmail.xs4all.nl",
|
|
933
|
+
"domains": [
|
|
934
|
+
"xs4all.nl"
|
|
935
|
+
]
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
"companyProvider": "Planet.nl",
|
|
939
|
+
"loginUrl": "https://webmail.planet.nl",
|
|
940
|
+
"domains": [
|
|
941
|
+
"planet.nl"
|
|
942
|
+
]
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"companyProvider": "Bluewin Mail",
|
|
946
|
+
"loginUrl": "https://webmail.bluewin.ch",
|
|
947
|
+
"domains": [
|
|
948
|
+
"bluewin.ch"
|
|
949
|
+
]
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
"companyProvider": "Eircom Mail",
|
|
953
|
+
"loginUrl": "https://webmail.eircom.net",
|
|
954
|
+
"domains": [
|
|
955
|
+
"eircom.net"
|
|
956
|
+
]
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
"companyProvider": "Centrum Mail",
|
|
960
|
+
"loginUrl": "https://www.centrum.cz",
|
|
961
|
+
"domains": [
|
|
962
|
+
"centrum.cz",
|
|
963
|
+
"centrum.sk"
|
|
964
|
+
]
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
"companyProvider": "Interia Mail",
|
|
968
|
+
"loginUrl": "https://poczta.interia.pl",
|
|
969
|
+
"domains": [
|
|
970
|
+
"interia.pl"
|
|
971
|
+
]
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
"companyProvider": "Onet Mail",
|
|
975
|
+
"loginUrl": "https://poczta.onet.pl",
|
|
976
|
+
"domains": [
|
|
977
|
+
"onet.pl",
|
|
978
|
+
"poczta.onet.pl",
|
|
979
|
+
"op.pl"
|
|
980
|
+
]
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
"companyProvider": "Rambler Mail",
|
|
984
|
+
"loginUrl": "https://mail.rambler.ru",
|
|
985
|
+
"domains": [
|
|
986
|
+
"rambler.ru",
|
|
987
|
+
"lenta.ru",
|
|
988
|
+
"autorambler.ru"
|
|
989
|
+
]
|
|
990
|
+
},
|
|
991
|
+
{
|
|
992
|
+
"companyProvider": "Nate Mail",
|
|
993
|
+
"loginUrl": "https://mail.nate.com",
|
|
994
|
+
"domains": [
|
|
995
|
+
"nate.com"
|
|
996
|
+
]
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"companyProvider": "Hanmail",
|
|
1000
|
+
"loginUrl": "https://mail.hanmail.net",
|
|
1001
|
+
"domains": [
|
|
1002
|
+
"hanmail.net"
|
|
1003
|
+
]
|
|
1004
|
+
},
|
|
1005
|
+
{
|
|
1006
|
+
"companyProvider": "Live.jp",
|
|
1007
|
+
"loginUrl": "https://login.live.com",
|
|
1008
|
+
"domains": [
|
|
1009
|
+
"live.jp"
|
|
1010
|
+
]
|
|
856
1011
|
}
|
|
857
1012
|
]
|
|
858
|
-
}
|
|
1013
|
+
}
|