@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/dist/index.js
CHANGED
|
@@ -11,7 +11,22 @@
|
|
|
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 });
|
|
29
|
+
exports.RateLimit = void 0;
|
|
15
30
|
exports.isValidEmail = isValidEmail;
|
|
16
31
|
exports.extractDomain = extractDomain;
|
|
17
32
|
exports.findEmailProvider = findEmailProvider;
|
|
@@ -31,17 +46,77 @@ const resolveTxtAsync = (0, util_1.promisify)(dns_1.resolveTxt);
|
|
|
31
46
|
* Default timeout for DNS queries in milliseconds.
|
|
32
47
|
*/
|
|
33
48
|
const DEFAULT_DNS_TIMEOUT = 5000; // 5 seconds
|
|
49
|
+
/**
|
|
50
|
+
* Rate limiting configuration for DNS queries.
|
|
51
|
+
*/
|
|
52
|
+
const RATE_LIMIT_MAX_REQUESTS = 10; // Maximum requests per time window
|
|
53
|
+
const RATE_LIMIT_WINDOW_MS = 60000; // Time window in milliseconds (1 minute)
|
|
54
|
+
/**
|
|
55
|
+
* Simple rate limiter to prevent excessive DNS queries.
|
|
56
|
+
* Tracks request timestamps and enforces a maximum number of requests per time window.
|
|
57
|
+
*/
|
|
58
|
+
class SimpleRateLimiter {
|
|
59
|
+
constructor(maxRequests = RATE_LIMIT_MAX_REQUESTS, windowMs = RATE_LIMIT_WINDOW_MS) {
|
|
60
|
+
this.maxRequests = maxRequests;
|
|
61
|
+
this.windowMs = windowMs;
|
|
62
|
+
this.requestTimestamps = [];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Checks if a request is allowed under the current rate limit.
|
|
66
|
+
* @returns true if request is allowed, false if rate limited
|
|
67
|
+
*/
|
|
68
|
+
isAllowed() {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
// Remove old timestamps outside the current window
|
|
71
|
+
this.requestTimestamps = this.requestTimestamps.filter(timestamp => now - timestamp < this.windowMs);
|
|
72
|
+
// Check if we're under the limit
|
|
73
|
+
if (this.requestTimestamps.length < this.maxRequests) {
|
|
74
|
+
this.requestTimestamps.push(now);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Gets the current number of requests in the window.
|
|
81
|
+
*/
|
|
82
|
+
getCurrentCount() {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
this.requestTimestamps = this.requestTimestamps.filter(timestamp => now - timestamp < this.windowMs);
|
|
85
|
+
return this.requestTimestamps.length;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Gets the time until the rate limit resets (when oldest request expires).
|
|
89
|
+
* @returns milliseconds until reset, or 0 if not rate limited
|
|
90
|
+
*/
|
|
91
|
+
getTimeUntilReset() {
|
|
92
|
+
if (this.requestTimestamps.length === 0)
|
|
93
|
+
return 0;
|
|
94
|
+
const oldestTimestamp = Math.min(...this.requestTimestamps);
|
|
95
|
+
const resetTime = oldestTimestamp + this.windowMs;
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
return Math.max(0, resetTime - now);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Global rate limiter instance
|
|
101
|
+
const dnsRateLimiter = new SimpleRateLimiter();
|
|
34
102
|
/**
|
|
35
103
|
* Creates a Promise that rejects after the specified timeout.
|
|
36
104
|
*
|
|
37
105
|
* @param ms - Timeout in milliseconds
|
|
38
|
-
* @returns Promise that rejects with timeout error
|
|
106
|
+
* @returns Promise that rejects with timeout error and a cleanup function
|
|
39
107
|
* @internal
|
|
40
108
|
*/
|
|
41
109
|
function createTimeout(ms) {
|
|
42
|
-
|
|
43
|
-
|
|
110
|
+
let timeoutId;
|
|
111
|
+
const promise = new Promise((_, reject) => {
|
|
112
|
+
timeoutId = setTimeout(() => reject(new Error(`DNS query timeout after ${ms}ms`)), ms);
|
|
44
113
|
});
|
|
114
|
+
const cleanup = () => {
|
|
115
|
+
if (timeoutId) {
|
|
116
|
+
clearTimeout(timeoutId);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return { promise, cleanup };
|
|
45
120
|
}
|
|
46
121
|
/**
|
|
47
122
|
* Wraps a DNS query with a timeout.
|
|
@@ -52,7 +127,11 @@ function createTimeout(ms) {
|
|
|
52
127
|
* @internal
|
|
53
128
|
*/
|
|
54
129
|
function withTimeout(promise, timeoutMs) {
|
|
55
|
-
|
|
130
|
+
const { promise: timeoutPromise, cleanup } = createTimeout(timeoutMs);
|
|
131
|
+
return Promise.race([
|
|
132
|
+
promise.finally(() => cleanup()), // Clean up timeout when original promise settles
|
|
133
|
+
timeoutPromise
|
|
134
|
+
]);
|
|
56
135
|
}
|
|
57
136
|
// Load providers from external JSON file
|
|
58
137
|
let EMAIL_PROVIDERS = [];
|
|
@@ -318,12 +397,18 @@ function detectProxyService(mxRecords) {
|
|
|
318
397
|
*
|
|
319
398
|
* @remarks
|
|
320
399
|
* **Detection Algorithm:**
|
|
321
|
-
* 1.
|
|
322
|
-
* 2.
|
|
323
|
-
* 3.
|
|
324
|
-
* 4.
|
|
325
|
-
* 5.
|
|
326
|
-
* 6.
|
|
400
|
+
* 1. Checks rate limiting (max 10 requests per minute)
|
|
401
|
+
* 2. Performs MX record lookup for the domain
|
|
402
|
+
* 3. Checks if MX records match known proxy services (Cloudflare, etc.)
|
|
403
|
+
* 4. If proxy detected, returns null provider with proxy info
|
|
404
|
+
* 5. Otherwise, matches MX records against business email provider patterns
|
|
405
|
+
* 6. If no MX match, falls back to TXT record analysis (SPF records, etc.)
|
|
406
|
+
* 7. Returns the first matching provider or null if none found
|
|
407
|
+
*
|
|
408
|
+
* **Rate Limiting:**
|
|
409
|
+
* - Maximum 10 DNS requests per 60-second window
|
|
410
|
+
* - Rate limit exceeded returns error with retry information
|
|
411
|
+
* - Prevents abuse and excessive DNS queries
|
|
327
412
|
*
|
|
328
413
|
* **Provider Patterns Checked:**
|
|
329
414
|
* - Google Workspace: aspmx.l.google.com, aspmx2.googlemail.com, etc.
|
|
@@ -338,6 +423,11 @@ function detectProxyService(mxRecords) {
|
|
|
338
423
|
*/
|
|
339
424
|
async function detectProviderByDNS(domain, timeoutMs = DEFAULT_DNS_TIMEOUT) {
|
|
340
425
|
const normalizedDomain = domain.toLowerCase();
|
|
426
|
+
// Check rate limiting
|
|
427
|
+
if (!dnsRateLimiter.isAllowed()) {
|
|
428
|
+
const retryAfter = Math.ceil(dnsRateLimiter.getTimeUntilReset() / 1000);
|
|
429
|
+
throw new Error(`Rate limit exceeded. DNS queries limited to ${RATE_LIMIT_MAX_REQUESTS} requests per minute. Try again in ${retryAfter} seconds.`);
|
|
430
|
+
}
|
|
341
431
|
// Get providers that support custom domain detection
|
|
342
432
|
const customDomainProviders = EMAIL_PROVIDERS.filter(provider => provider.customDomainDetection &&
|
|
343
433
|
(provider.customDomainDetection.mxPatterns || provider.customDomainDetection.txtPatterns));
|
|
@@ -502,6 +592,26 @@ async function getEmailProviderLinkWithDNS(email, timeoutMs = DEFAULT_DNS_TIMEOU
|
|
|
502
592
|
loginUrl: null
|
|
503
593
|
};
|
|
504
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* Rate limiting configuration constants and utilities.
|
|
597
|
+
* @public
|
|
598
|
+
*/
|
|
599
|
+
exports.RateLimit = {
|
|
600
|
+
MAX_REQUESTS: RATE_LIMIT_MAX_REQUESTS,
|
|
601
|
+
WINDOW_MS: RATE_LIMIT_WINDOW_MS,
|
|
602
|
+
SimpleRateLimiter,
|
|
603
|
+
/**
|
|
604
|
+
* Gets the current rate limiter instance for inspection or testing.
|
|
605
|
+
* @internal
|
|
606
|
+
*/
|
|
607
|
+
getCurrentLimiter: () => dnsRateLimiter
|
|
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);
|
|
505
615
|
// Default export for convenience
|
|
506
616
|
exports.default = {
|
|
507
617
|
getEmailProviderLink,
|
|
@@ -511,5 +621,6 @@ exports.default = {
|
|
|
511
621
|
extractDomain,
|
|
512
622
|
findEmailProvider,
|
|
513
623
|
getSupportedProviders,
|
|
514
|
-
isEmailProviderSupported
|
|
624
|
+
isEmailProviderSupported,
|
|
625
|
+
RateLimit: exports.RateLimit
|
|
515
626
|
};
|
|
@@ -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
|
|
@@ -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.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"
|