@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 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
- - ๐Ÿ“ง **64+ Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and more
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
- - ๐Ÿงช **Thoroughly Tested**: 83+ tests including comprehensive security coverage
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
- **Consumer Email:**
42
- Gmail, Outlook, Yahoo Mail, iCloud, ProtonMail, Zoho, AOL, GMX, Web.de, Mail.ru, QQ Mail, NetEase, Yandex, and more.
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, Google Workspace, ProtonMail Business, Hostinger, FastMail, GoDaddy, Tutanota, Zoho Workplace, and others.
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. Performs MX record lookup for the domain
230
- * 2. Checks if MX records match known proxy services (Cloudflare, etc.)
231
- * 3. If proxy detected, returns null provider with proxy info
232
- * 4. Otherwise, matches MX records against business email provider patterns
233
- * 5. If no MX match, falls back to TXT record analysis (SPF records, etc.)
234
- * 6. Returns the first matching provider or null if none found
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
- return new Promise((_, reject) => {
43
- setTimeout(() => reject(new Error(`DNS query timeout after ${ms}ms`)), ms);
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
- return Promise.race([promise, createTimeout(timeoutMs)]);
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. 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
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': 'da7a856fe04b11e326230d195fcc3d44f078e481b8929cf4fb5040276e05ffd0',
30
+ 'emailproviders.json': '1c7925d02cc66b487046afdce81f744bd7a42f5a489e16e53a1a979575e4473b',
31
31
  // You can add hashes for other critical files
32
- 'package.json': '4fec42bf25d33615a2b19bfe573b6d706404d39bfcdd6464a6958e70fca2a579'
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
- '๐Ÿ“ง 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.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": [