@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/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
- return new Promise((_, reject) => {
43
- setTimeout(() => reject(new Error(`DNS query timeout after ${ms}ms`)), ms);
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
- return Promise.race([promise, createTimeout(timeoutMs)]);
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. Performs MX record lookup for the domain
322
- * 2. Checks if MX records match known proxy services (Cloudflare, etc.)
323
- * 3. If proxy detected, returns null provider with proxy info
324
- * 4. Otherwise, matches MX records against business email provider patterns
325
- * 5. If no MX match, falls back to TXT record analysis (SPF records, etc.)
326
- * 6. Returns the first matching provider or null if none found
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': 'da7a856fe04b11e326230d195fcc3d44f078e481b8929cf4fb5040276e05ffd0',
30
+ 'emailproviders.json': '41d2084580270f77ae2c2c5593995ebace7877a74f7d1c1e1bc7ed3b37611aa4',
31
31
  // You can add hashes for other critical files
32
- 'package.json': '4fec42bf25d33615a2b19bfe573b6d706404d39bfcdd6464a6958e70fca2a579'
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
- '📧 Consider reporting to: security@[your-domain].com'
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.5.1",
4
- "description": "A TypeScript package that provides direct links to email providers based on email addresses to streamline login and password reset flows",
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"