@mikkelscheike/email-provider-links 1.5.1 โ 1.6.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 +44 -5
- package/dist/index.d.ts +60 -6
- package/dist/index.js +102 -11
- package/dist/security/hash-verifier.js +3 -3
- package/package.json +2 -2
- package/providers/emailproviders.json +709 -290
package/README.md
CHANGED
|
@@ -7,14 +7,16 @@ A TypeScript package that provides direct links to email providers based on emai
|
|
|
7
7
|
## โจ Features
|
|
8
8
|
|
|
9
9
|
- ๐ **Fast & Lightweight**: Zero dependencies, minimal footprint
|
|
10
|
-
- ๐ง **
|
|
10
|
+
- ๐ง **74 Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and more
|
|
11
|
+
- ๐ **147+ 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
|
+
- ๐งช **Thoroughly Tested**: 142+ tests with 94.69% code coverage
|
|
18
20
|
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
@@ -38,11 +40,33 @@ console.log(business.provider?.companyProvider); // "Google Workspace" (if detec
|
|
|
38
40
|
|
|
39
41
|
## Supported Providers
|
|
40
42
|
|
|
41
|
-
**
|
|
42
|
-
|
|
43
|
+
**๐ Current Coverage: 74 providers supporting 147+ domains**
|
|
44
|
+
|
|
45
|
+
**Consumer Email Providers:**
|
|
46
|
+
- **Gmail** (2 domains): gmail.com, googlemail.com
|
|
47
|
+
- **Microsoft Outlook** (15 domains): outlook.com, hotmail.com, live.com, msn.com, and 11 more
|
|
48
|
+
- **Yahoo Mail** (19 domains): yahoo.com, yahoo.co.uk, yahoo.fr, ymail.com, rocketmail.com, and 14 more
|
|
49
|
+
- **ProtonMail** (4 domains): proton.me, protonmail.com, protonmail.ch, pm.me
|
|
50
|
+
- **iCloud Mail** (3 domains): icloud.com, me.com, mac.com
|
|
51
|
+
- **Tutanota** (6 domains): tutanota.com, tutanota.de, tutamail.com, tuta.io, keemail.me, tuta.com
|
|
52
|
+
- **SimpleLogin** (10 domains): simplelogin.io, 8alias.com, aleeas.com, slmail.me, and 6 more
|
|
53
|
+
- **FastMail, Zoho Mail, AOL Mail, GMX, Web.de, Mail.ru, QQ Mail, NetEase, Yandex**, and many more
|
|
43
54
|
|
|
44
55
|
**Business Email (via DNS detection):**
|
|
45
|
-
Microsoft 365
|
|
56
|
+
- **Microsoft 365** (Business domains via MX/TXT records)
|
|
57
|
+
- **Google Workspace** (Custom domains via DNS patterns)
|
|
58
|
+
- **Amazon WorkMail** (AWS email infrastructure via awsapps.com patterns)
|
|
59
|
+
- **Zoho Workplace, FastMail Business, GoDaddy Email, Bluehost Email**
|
|
60
|
+
- **ProtonMail Business, Rackspace Email, IONOS**, and others
|
|
61
|
+
|
|
62
|
+
**Security & Privacy Focused:**
|
|
63
|
+
- **ProtonMail, Tutanota, Hushmail, CounterMail, Posteo**
|
|
64
|
+
- **Mailfence, SimpleLogin, AnonAddy**
|
|
65
|
+
|
|
66
|
+
**International Providers:**
|
|
67
|
+
- **Europe**: GMX, Web.de, Orange, Free.fr, T-Online, Libero
|
|
68
|
+
- **Asia**: QQ Mail, NetEase, Sina Mail, Rakuten, Nifty, **Naver** (Korea), **Daum** (Korea), **Biglobe** (Japan)
|
|
69
|
+
- **Other Regions**: UOL (Brazil), Telkom (South Africa), Xtra (New Zealand)
|
|
46
70
|
|
|
47
71
|
## API
|
|
48
72
|
|
|
@@ -85,6 +109,14 @@ const result = await getEmailProviderLinkWithDNS(email, 2000);
|
|
|
85
109
|
// Check if provider is supported
|
|
86
110
|
import { isEmailProviderSupported } from '@mikkelscheike/email-provider-links';
|
|
87
111
|
const supported = isEmailProviderSupported('user@gmail.com');
|
|
112
|
+
|
|
113
|
+
// Rate limiting configuration
|
|
114
|
+
import { RateLimit } from '@mikkelscheike/email-provider-links';
|
|
115
|
+
console.log('Max requests:', RateLimit.MAX_REQUESTS); // 10
|
|
116
|
+
console.log('Time window:', RateLimit.WINDOW_MS); // 60000ms
|
|
117
|
+
|
|
118
|
+
// Custom rate limiter for specific use cases
|
|
119
|
+
const customLimiter = new RateLimit.SimpleRateLimiter(20, 120000); // 20 requests per 2 minutes
|
|
88
120
|
```
|
|
89
121
|
|
|
90
122
|
## TypeScript Support
|
|
@@ -97,6 +129,13 @@ interface EmailProviderResult {
|
|
|
97
129
|
detectionMethod?: 'domain_match' | 'mx_record' | 'txt_record' | 'proxy_detected';
|
|
98
130
|
proxyService?: string;
|
|
99
131
|
}
|
|
132
|
+
|
|
133
|
+
interface RateLimitConfig {
|
|
134
|
+
MAX_REQUESTS: number; // 10 requests
|
|
135
|
+
WINDOW_MS: number; // 60000ms (1 minute)
|
|
136
|
+
SimpleRateLimiter: class; // Custom rate limiter class
|
|
137
|
+
getCurrentLimiter(): SimpleRateLimiter; // Get current global limiter
|
|
138
|
+
}
|
|
100
139
|
```
|
|
101
140
|
|
|
102
141
|
## ๐ก๏ธ Security Features
|
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,20 @@ 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
|
+
};
|
|
306
350
|
declare const _default: {
|
|
307
351
|
getEmailProviderLink: typeof getEmailProviderLink;
|
|
308
352
|
getEmailProviderLinkWithDNS: typeof getEmailProviderLinkWithDNS;
|
|
@@ -312,5 +356,15 @@ declare const _default: {
|
|
|
312
356
|
findEmailProvider: typeof findEmailProvider;
|
|
313
357
|
getSupportedProviders: typeof getSupportedProviders;
|
|
314
358
|
isEmailProviderSupported: typeof isEmailProviderSupported;
|
|
359
|
+
RateLimit: {
|
|
360
|
+
MAX_REQUESTS: number;
|
|
361
|
+
WINDOW_MS: number;
|
|
362
|
+
SimpleRateLimiter: typeof SimpleRateLimiter;
|
|
363
|
+
/**
|
|
364
|
+
* Gets the current rate limiter instance for inspection or testing.
|
|
365
|
+
* @internal
|
|
366
|
+
*/
|
|
367
|
+
getCurrentLimiter: () => SimpleRateLimiter;
|
|
368
|
+
};
|
|
315
369
|
};
|
|
316
370
|
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @packageDocumentation
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.RateLimit = void 0;
|
|
15
16
|
exports.isValidEmail = isValidEmail;
|
|
16
17
|
exports.extractDomain = extractDomain;
|
|
17
18
|
exports.findEmailProvider = findEmailProvider;
|
|
@@ -31,17 +32,77 @@ const resolveTxtAsync = (0, util_1.promisify)(dns_1.resolveTxt);
|
|
|
31
32
|
* Default timeout for DNS queries in milliseconds.
|
|
32
33
|
*/
|
|
33
34
|
const DEFAULT_DNS_TIMEOUT = 5000; // 5 seconds
|
|
35
|
+
/**
|
|
36
|
+
* Rate limiting configuration for DNS queries.
|
|
37
|
+
*/
|
|
38
|
+
const RATE_LIMIT_MAX_REQUESTS = 10; // Maximum requests per time window
|
|
39
|
+
const RATE_LIMIT_WINDOW_MS = 60000; // Time window in milliseconds (1 minute)
|
|
40
|
+
/**
|
|
41
|
+
* Simple rate limiter to prevent excessive DNS queries.
|
|
42
|
+
* Tracks request timestamps and enforces a maximum number of requests per time window.
|
|
43
|
+
*/
|
|
44
|
+
class SimpleRateLimiter {
|
|
45
|
+
constructor(maxRequests = RATE_LIMIT_MAX_REQUESTS, windowMs = RATE_LIMIT_WINDOW_MS) {
|
|
46
|
+
this.maxRequests = maxRequests;
|
|
47
|
+
this.windowMs = windowMs;
|
|
48
|
+
this.requestTimestamps = [];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a request is allowed under the current rate limit.
|
|
52
|
+
* @returns true if request is allowed, false if rate limited
|
|
53
|
+
*/
|
|
54
|
+
isAllowed() {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
// Remove old timestamps outside the current window
|
|
57
|
+
this.requestTimestamps = this.requestTimestamps.filter(timestamp => now - timestamp < this.windowMs);
|
|
58
|
+
// Check if we're under the limit
|
|
59
|
+
if (this.requestTimestamps.length < this.maxRequests) {
|
|
60
|
+
this.requestTimestamps.push(now);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets the current number of requests in the window.
|
|
67
|
+
*/
|
|
68
|
+
getCurrentCount() {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
this.requestTimestamps = this.requestTimestamps.filter(timestamp => now - timestamp < this.windowMs);
|
|
71
|
+
return this.requestTimestamps.length;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Gets the time until the rate limit resets (when oldest request expires).
|
|
75
|
+
* @returns milliseconds until reset, or 0 if not rate limited
|
|
76
|
+
*/
|
|
77
|
+
getTimeUntilReset() {
|
|
78
|
+
if (this.requestTimestamps.length === 0)
|
|
79
|
+
return 0;
|
|
80
|
+
const oldestTimestamp = Math.min(...this.requestTimestamps);
|
|
81
|
+
const resetTime = oldestTimestamp + this.windowMs;
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
return Math.max(0, resetTime - now);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Global rate limiter instance
|
|
87
|
+
const dnsRateLimiter = new SimpleRateLimiter();
|
|
34
88
|
/**
|
|
35
89
|
* Creates a Promise that rejects after the specified timeout.
|
|
36
90
|
*
|
|
37
91
|
* @param ms - Timeout in milliseconds
|
|
38
|
-
* @returns Promise that rejects with timeout error
|
|
92
|
+
* @returns Promise that rejects with timeout error and a cleanup function
|
|
39
93
|
* @internal
|
|
40
94
|
*/
|
|
41
95
|
function createTimeout(ms) {
|
|
42
|
-
|
|
43
|
-
|
|
96
|
+
let timeoutId;
|
|
97
|
+
const promise = new Promise((_, reject) => {
|
|
98
|
+
timeoutId = setTimeout(() => reject(new Error(`DNS query timeout after ${ms}ms`)), ms);
|
|
44
99
|
});
|
|
100
|
+
const cleanup = () => {
|
|
101
|
+
if (timeoutId) {
|
|
102
|
+
clearTimeout(timeoutId);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
return { promise, cleanup };
|
|
45
106
|
}
|
|
46
107
|
/**
|
|
47
108
|
* Wraps a DNS query with a timeout.
|
|
@@ -52,7 +113,11 @@ function createTimeout(ms) {
|
|
|
52
113
|
* @internal
|
|
53
114
|
*/
|
|
54
115
|
function withTimeout(promise, timeoutMs) {
|
|
55
|
-
|
|
116
|
+
const { promise: timeoutPromise, cleanup } = createTimeout(timeoutMs);
|
|
117
|
+
return Promise.race([
|
|
118
|
+
promise.finally(() => cleanup()), // Clean up timeout when original promise settles
|
|
119
|
+
timeoutPromise
|
|
120
|
+
]);
|
|
56
121
|
}
|
|
57
122
|
// Load providers from external JSON file
|
|
58
123
|
let EMAIL_PROVIDERS = [];
|
|
@@ -318,12 +383,18 @@ function detectProxyService(mxRecords) {
|
|
|
318
383
|
*
|
|
319
384
|
* @remarks
|
|
320
385
|
* **Detection Algorithm:**
|
|
321
|
-
* 1.
|
|
322
|
-
* 2.
|
|
323
|
-
* 3.
|
|
324
|
-
* 4.
|
|
325
|
-
* 5.
|
|
326
|
-
* 6.
|
|
386
|
+
* 1. Checks rate limiting (max 10 requests per minute)
|
|
387
|
+
* 2. Performs MX record lookup for the domain
|
|
388
|
+
* 3. Checks if MX records match known proxy services (Cloudflare, etc.)
|
|
389
|
+
* 4. If proxy detected, returns null provider with proxy info
|
|
390
|
+
* 5. Otherwise, matches MX records against business email provider patterns
|
|
391
|
+
* 6. If no MX match, falls back to TXT record analysis (SPF records, etc.)
|
|
392
|
+
* 7. Returns the first matching provider or null if none found
|
|
393
|
+
*
|
|
394
|
+
* **Rate Limiting:**
|
|
395
|
+
* - Maximum 10 DNS requests per 60-second window
|
|
396
|
+
* - Rate limit exceeded returns error with retry information
|
|
397
|
+
* - Prevents abuse and excessive DNS queries
|
|
327
398
|
*
|
|
328
399
|
* **Provider Patterns Checked:**
|
|
329
400
|
* - Google Workspace: aspmx.l.google.com, aspmx2.googlemail.com, etc.
|
|
@@ -338,6 +409,11 @@ function detectProxyService(mxRecords) {
|
|
|
338
409
|
*/
|
|
339
410
|
async function detectProviderByDNS(domain, timeoutMs = DEFAULT_DNS_TIMEOUT) {
|
|
340
411
|
const normalizedDomain = domain.toLowerCase();
|
|
412
|
+
// Check rate limiting
|
|
413
|
+
if (!dnsRateLimiter.isAllowed()) {
|
|
414
|
+
const retryAfter = Math.ceil(dnsRateLimiter.getTimeUntilReset() / 1000);
|
|
415
|
+
throw new Error(`Rate limit exceeded. DNS queries limited to ${RATE_LIMIT_MAX_REQUESTS} requests per minute. Try again in ${retryAfter} seconds.`);
|
|
416
|
+
}
|
|
341
417
|
// Get providers that support custom domain detection
|
|
342
418
|
const customDomainProviders = EMAIL_PROVIDERS.filter(provider => provider.customDomainDetection &&
|
|
343
419
|
(provider.customDomainDetection.mxPatterns || provider.customDomainDetection.txtPatterns));
|
|
@@ -502,6 +578,20 @@ async function getEmailProviderLinkWithDNS(email, timeoutMs = DEFAULT_DNS_TIMEOU
|
|
|
502
578
|
loginUrl: null
|
|
503
579
|
};
|
|
504
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Rate limiting configuration constants and utilities.
|
|
583
|
+
* @public
|
|
584
|
+
*/
|
|
585
|
+
exports.RateLimit = {
|
|
586
|
+
MAX_REQUESTS: RATE_LIMIT_MAX_REQUESTS,
|
|
587
|
+
WINDOW_MS: RATE_LIMIT_WINDOW_MS,
|
|
588
|
+
SimpleRateLimiter,
|
|
589
|
+
/**
|
|
590
|
+
* Gets the current rate limiter instance for inspection or testing.
|
|
591
|
+
* @internal
|
|
592
|
+
*/
|
|
593
|
+
getCurrentLimiter: () => dnsRateLimiter
|
|
594
|
+
};
|
|
505
595
|
// Default export for convenience
|
|
506
596
|
exports.default = {
|
|
507
597
|
getEmailProviderLink,
|
|
@@ -511,5 +601,6 @@ exports.default = {
|
|
|
511
601
|
extractDomain,
|
|
512
602
|
findEmailProvider,
|
|
513
603
|
getSupportedProviders,
|
|
514
|
-
isEmailProviderSupported
|
|
604
|
+
isEmailProviderSupported,
|
|
605
|
+
RateLimit: exports.RateLimit
|
|
515
606
|
};
|
|
@@ -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': '1c7925d02cc66b487046afdce81f744bd7a42f5a489e16e53a1a979575e4473b',
|
|
31
31
|
// You can add hashes for other critical files
|
|
32
|
-
'package.json': '
|
|
32
|
+
'package.json': 'ba2810fd51f2d044890119e6f49926b80d822a4cafedaff25e3f91ba8ec65c2f'
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* Calculates SHA-256 hash of a file or string content
|
|
@@ -206,7 +206,7 @@ function handleHashMismatch(result, options = {}) {
|
|
|
206
206
|
'4. Verify file integrity from trusted source',
|
|
207
207
|
'5. Report security incident if confirmed',
|
|
208
208
|
'',
|
|
209
|
-
'๐ง
|
|
209
|
+
'๐ง Report security issues: https://github.com/mikkelscheike/email-provider-links/security'
|
|
210
210
|
].join('\n');
|
|
211
211
|
if (logLevel === 'error') {
|
|
212
212
|
console.error(securityAlert);
|
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
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "A TypeScript package providing direct links to 74 email providers (147+ domains) with enterprise security features, 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": [
|