@mikkelscheike/email-provider-links 3.0.2 → 4.0.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
@@ -1,6 +1,8 @@
1
1
  # Email Provider Links
2
2
 
3
- 🔒 **Modern email provider detection library with enhanced TypeScript support and enterprise security**
3
+ [![npm version](https://badge.fury.io/js/@mikkelscheike%2Femail-provider-links.svg)](https://www.npmjs.com/package/@mikkelscheike/email-provider-links)
4
+
5
+ > **Generate direct login links for any email address across 93+ providers (Gmail, Outlook, Yahoo, etc.) to streamline user authentication flows.**
4
6
 
5
7
  A robust TypeScript library providing direct links to **93 email providers** (180 domains) with **concurrent DNS resolution**, **optimized performance**, **comprehensive email validation**, and advanced security features for login and password reset flows.
6
8
 
@@ -28,9 +30,9 @@ A robust TypeScript library providing direct links to **93 email providers** (18
28
30
  - 🌍 **Full IDN Support**: International domain names with RFC compliance and Punycode
29
31
  - ✅ **Advanced Email Validation**: International email validation with detailed error reporting
30
32
  - 🏢 **Business Domain Detection**: DNS-based detection for custom domains (Google Workspace, Microsoft 365, etc.)
31
- - 🔒 **Enterprise Security**: Multi-layer protection against malicious URLs and supply chain attacks
32
- - 🛡️ **URL Validation**: HTTPS-only enforcement with domain allowlisting
33
- - 🔐 **Integrity Verification**: Cryptographic hash verification for data integrity
33
+ - 🔒 **Built-in Security**: Multi-layer protection with cryptographic hash verification, URL validation, and supply chain attack prevention
34
+ - 🛡️ **Zero-Trust Architecture**: All provider data undergoes integrity verification - no insecure fallbacks
35
+ - 🔐 **HTTPS-Only**: Strict HTTPS enforcement with domain allowlisting and malicious pattern detection
34
36
  - 📝 **Type Safe**: Full TypeScript support with comprehensive interfaces
35
37
  - ⚡ **Performance Optimized**: Smart DNS fallback with configurable timeouts
36
38
  - 🚦 **Rate Limiting**: Built-in DNS query rate limiting to prevent abuse
@@ -253,7 +255,7 @@ Extensively optimized for both speed and memory efficiency:
253
255
  **Speed Metrics**:
254
256
  - Initial provider load: ~0.5ms
255
257
  - Known provider lookup: <1ms
256
- - DNS-based detection: ~27ms average
258
+ - DNS-based detection: ~10ms average
257
259
  - Batch processing: 1000 operations in ~1.1ms
258
260
  - Email validation: <1ms for complex IDN domains
259
261
 
@@ -268,7 +270,7 @@ Extensively optimized for both speed and memory efficiency:
268
270
  - 50,000+ operations/second for known providers
269
271
  - 100 concurrent DNS lookups in <1 second
270
272
  - Average latency: <1ms for cached lookups
271
- - Maximum latency: <5ms per lookup
273
+ - Maximum latency: <25ms per lookup
272
274
 
273
275
  To run benchmarks:
274
276
  ```bash
@@ -287,7 +289,8 @@ npm run benchmark:dns
287
289
  We welcome contributions! See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines on adding new email providers.
288
290
 
289
291
  **Quality Assurance**: This project maintains high standards with 445 comprehensive tests achieving 94.65% code coverage (95.95% function coverage).
290
- **Security Note**: All new providers undergo security validation and must pass our allowlist verification.
292
+
293
+ **Security**: All provider data is protected by cryptographic hash verification, URL validation, and strict security controls. The library uses a zero-trust architecture with no insecure fallbacks - ensuring all data is verified before use.
291
294
 
292
295
  ## Security
293
296
 
package/dist/api.js CHANGED
@@ -13,7 +13,7 @@ exports.normalizeEmail = normalizeEmail;
13
13
  exports.emailsMatch = emailsMatch;
14
14
  exports.getEmailProviderFast = getEmailProviderFast;
15
15
  const concurrent_dns_1 = require("./concurrent-dns");
16
- const loader_1 = require("./loader");
16
+ const provider_loader_1 = require("./provider-loader");
17
17
  /**
18
18
  * Get email provider information for any email address.
19
19
  *
@@ -92,7 +92,19 @@ async function getEmailProvider(email, timeout) {
92
92
  };
93
93
  }
94
94
  // Fall back to DNS detection for business domains
95
- const { providers } = (0, loader_1.loadProviders)();
95
+ const loadResult = (0, provider_loader_1.loadProviders)();
96
+ if (!loadResult.success) {
97
+ return {
98
+ provider: null,
99
+ email,
100
+ loginUrl: null,
101
+ error: {
102
+ type: 'NETWORK_ERROR',
103
+ message: 'Service temporarily unavailable'
104
+ }
105
+ };
106
+ }
107
+ const providers = loadResult.providers;
96
108
  const concurrentResult = await (0, concurrent_dns_1.detectProviderConcurrent)(domain, providers, {
97
109
  timeout: timeout || 5000,
98
110
  enableParallel: true,
@@ -215,9 +227,48 @@ function getEmailProviderSync(email) {
215
227
  }
216
228
  };
217
229
  }
218
- // Use cached providers and domain map for efficient lookup
219
- const { domainMap } = (0, loader_1.loadProviders)();
220
- const provider = domainMap.get(domain);
230
+ // Load providers with verification
231
+ let provider = null;
232
+ try {
233
+ const result = (0, provider_loader_1.loadProviders)();
234
+ // Ensure providers loaded successfully
235
+ if (!result.success) {
236
+ if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
237
+ console.error('🚨 Provider lookup blocked due to validation failure');
238
+ }
239
+ return {
240
+ provider: null,
241
+ email,
242
+ loginUrl: null,
243
+ error: {
244
+ type: 'NETWORK_ERROR',
245
+ message: 'Service temporarily unavailable'
246
+ }
247
+ };
248
+ }
249
+ const domainMap = new Map();
250
+ // Build domain map from loaded providers
251
+ for (const loadedProvider of result.providers) {
252
+ for (const domain of loadedProvider.domains) {
253
+ domainMap.set(domain.toLowerCase(), loadedProvider);
254
+ }
255
+ }
256
+ provider = domainMap.get(domain) || null;
257
+ }
258
+ catch (error) {
259
+ if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
260
+ console.error('🚨 Provider lookup failed:', error);
261
+ }
262
+ return {
263
+ provider: null,
264
+ email,
265
+ loginUrl: null,
266
+ error: {
267
+ type: 'NETWORK_ERROR',
268
+ message: 'Service temporarily unavailable'
269
+ }
270
+ };
271
+ }
221
272
  const result = {
222
273
  provider: provider || null,
223
274
  email,
@@ -277,9 +328,24 @@ function normalizeEmail(email) {
277
328
  }
278
329
  let localPart = lowercaseEmail.slice(0, atIndex);
279
330
  const domainPart = lowercaseEmail.slice(atIndex + 1);
280
- // Use cached providers for domain lookup
281
- const { domainMap } = (0, loader_1.loadProviders)();
282
- const provider = domainMap.get(domainPart);
331
+ // Use providers for domain lookup
332
+ let provider = null;
333
+ try {
334
+ const result = (0, provider_loader_1.loadProviders)();
335
+ if (!result.success) {
336
+ return lowercaseEmail; // Return as-is if providers can't be loaded
337
+ }
338
+ const domainMap = new Map();
339
+ for (const loadedProvider of result.providers) {
340
+ for (const domain of loadedProvider.domains) {
341
+ domainMap.set(domain.toLowerCase(), loadedProvider);
342
+ }
343
+ }
344
+ provider = domainMap.get(domainPart) || null;
345
+ }
346
+ catch (error) {
347
+ return lowercaseEmail; // Return as-is if error occurs
348
+ }
283
349
  if (provider?.alias) {
284
350
  // Provider supports aliasing
285
351
  if (provider.alias.dots) {
@@ -396,7 +462,19 @@ async function getEmailProviderFast(email, options = {}) {
396
462
  };
397
463
  }
398
464
  // Fall back to concurrent DNS detection for business domains
399
- const { providers } = (0, loader_1.loadProviders)();
465
+ const result = (0, provider_loader_1.loadProviders)();
466
+ if (!result.success) {
467
+ return {
468
+ provider: null,
469
+ email,
470
+ loginUrl: null,
471
+ error: {
472
+ type: 'NETWORK_ERROR',
473
+ message: 'Service temporarily unavailable'
474
+ }
475
+ };
476
+ }
477
+ const providers = result.providers;
400
478
  const concurrentResult = await (0, concurrent_dns_1.detectProviderConcurrent)(domain, providers, {
401
479
  timeout,
402
480
  enableParallel,
@@ -29,7 +29,7 @@ const KNOWN_GOOD_HASHES = {
29
29
  // SHA-256 hash of the legitimate emailproviders.json
30
30
  'emailproviders.json': '65c136a13cbcd31507241b7ddc8850f81b98196d2e57fcd9ce6060825a010cef',
31
31
  // You can add hashes for other critical files
32
- 'package.json': '0da24b9a8625598e89d0b7fced272ff358538ba83875c422314068c55c2e180b',
32
+ 'package.json': 'f25435adf952a4433447a2c4584249514ceec466fd353b5456352d06b65f737d',
33
33
  };
34
34
  /**
35
35
  * Calculates SHA-256 hash of a file or string content
package/dist/loader.js CHANGED
@@ -120,7 +120,6 @@ function buildDomainMap(providers) {
120
120
  if (cachedDomainMap) {
121
121
  return cachedDomainMap;
122
122
  }
123
- const startTime = Date.now();
124
123
  const domainMap = new Map();
125
124
  for (const provider of providers) {
126
125
  for (const domain of provider.domains) {
@@ -128,9 +127,6 @@ function buildDomainMap(providers) {
128
127
  }
129
128
  }
130
129
  cachedDomainMap = domainMap;
131
- if (loadingStats) {
132
- console.log(`🗺️ Domain map built in ${Date.now() - startTime}ms (${domainMap.size} entries)`);
133
- }
134
130
  return domainMap;
135
131
  }
136
132
  /**
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Secure Loader for Email Providers
2
+ * Email Provider Loader
3
3
  *
4
- * Integrates URL validation and hash verification to create a secure
5
- * loading system for email provider data.
4
+ * Integrates URL validation and hash verification to load and validate
5
+ * email provider data with security checks.
6
6
  */
7
7
  import type { EmailProvider } from './index';
8
- export interface SecureLoadResult {
8
+ export interface LoadResult {
9
9
  success: boolean;
10
10
  providers: EmailProvider[];
11
11
  securityReport: {
@@ -19,31 +19,36 @@ export interface SecureLoadResult {
19
19
  };
20
20
  }
21
21
  /**
22
- * Securely loads and validates email provider data
22
+ * Clear the cache (useful for testing or when providers file changes)
23
+ */
24
+ export declare function clearCache(): void;
25
+ /**
26
+ * Loads and validates email provider data with security checks
23
27
  *
24
28
  * @param providersPath - Path to the providers JSON file
25
29
  * @param expectedHash - Optional expected hash for verification
26
- * @returns Secure load result with validation details
30
+ * @returns Load result with validation details
27
31
  */
28
- export declare function secureLoadProviders(providersPath?: string, expectedHash?: string): SecureLoadResult;
32
+ export declare function loadProviders(providersPath?: string, expectedHash?: string): LoadResult;
29
33
  /**
30
34
  * Development utility to generate and display current hashes
31
35
  */
32
36
  export declare function initializeSecurity(): Record<string, string>;
33
37
  /**
34
- * Express middleware for secure provider loading (if using in web apps)
38
+ * Express middleware for provider loading with security checks (if using in web apps)
35
39
  */
36
40
  interface SecurityMiddlewareOptions {
37
41
  expectedHash?: string;
38
42
  allowInvalidUrls?: boolean;
39
- onSecurityIssue?: (report: SecureLoadResult['securityReport']) => void;
40
- getProviders?: () => SecureLoadResult;
43
+ onSecurityIssue?: (report: LoadResult['securityReport']) => void;
44
+ getProviders?: () => LoadResult;
41
45
  }
42
46
  export declare function createSecurityMiddleware(options?: SecurityMiddlewareOptions): (req: any, res: any, next: any) => any;
43
47
  declare const _default: {
44
- secureLoadProviders: typeof secureLoadProviders;
48
+ loadProviders: typeof loadProviders;
45
49
  initializeSecurity: typeof initializeSecurity;
46
50
  createSecurityMiddleware: typeof createSecurityMiddleware;
51
+ clearCache: typeof clearCache;
47
52
  };
48
53
  export default _default;
49
- //# sourceMappingURL=secure-loader.d.ts.map
54
+ //# sourceMappingURL=provider-loader.d.ts.map
@@ -1,26 +1,69 @@
1
1
  "use strict";
2
2
  /**
3
- * Secure Loader for Email Providers
3
+ * Email Provider Loader
4
4
  *
5
- * Integrates URL validation and hash verification to create a secure
6
- * loading system for email provider data.
5
+ * Integrates URL validation and hash verification to load and validate
6
+ * email provider data with security checks.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.secureLoadProviders = secureLoadProviders;
9
+ exports.clearCache = clearCache;
10
+ exports.loadProviders = loadProviders;
10
11
  exports.initializeSecurity = initializeSecurity;
11
12
  exports.createSecurityMiddleware = createSecurityMiddleware;
12
13
  const fs_1 = require("fs");
13
14
  const path_1 = require("path");
14
15
  const url_validator_1 = require("./url-validator");
15
16
  const hash_verifier_1 = require("./hash-verifier");
17
+ const schema_1 = require("./schema");
18
+ // Cache for load results
19
+ let cachedLoadResult = null;
16
20
  /**
17
- * Securely loads and validates email provider data
21
+ * Clear the cache (useful for testing or when providers file changes)
22
+ */
23
+ function clearCache() {
24
+ cachedLoadResult = null;
25
+ }
26
+ /**
27
+ * Convert compressed provider to EmailProvider format
28
+ */
29
+ function convertProviderToEmailProvider(compressedProvider) {
30
+ if (!compressedProvider.type) {
31
+ console.warn(`Missing type for provider ${compressedProvider.id}`);
32
+ }
33
+ const provider = {
34
+ companyProvider: compressedProvider.companyProvider,
35
+ loginUrl: compressedProvider.loginUrl || null,
36
+ domains: compressedProvider.domains || [],
37
+ type: compressedProvider.type,
38
+ alias: compressedProvider.alias
39
+ };
40
+ // Include DNS detection patterns for business email services and proxy services
41
+ const needsCustomDomainDetection = compressedProvider.type === 'custom_provider' ||
42
+ compressedProvider.type === 'proxy_service';
43
+ if (needsCustomDomainDetection && (compressedProvider.mx?.length || compressedProvider.txt?.length)) {
44
+ provider.customDomainDetection = {};
45
+ if (compressedProvider.mx?.length) {
46
+ provider.customDomainDetection.mxPatterns = compressedProvider.mx;
47
+ }
48
+ if (compressedProvider.txt?.length) {
49
+ // Decompress TXT patterns
50
+ provider.customDomainDetection.txtPatterns = compressedProvider.txt.map(schema_1.decompressTxtPattern);
51
+ }
52
+ }
53
+ return provider;
54
+ }
55
+ /**
56
+ * Loads and validates email provider data with security checks
18
57
  *
19
58
  * @param providersPath - Path to the providers JSON file
20
59
  * @param expectedHash - Optional expected hash for verification
21
- * @returns Secure load result with validation details
60
+ * @returns Load result with validation details
22
61
  */
23
- function secureLoadProviders(providersPath, expectedHash) {
62
+ function loadProviders(providersPath, expectedHash) {
63
+ // Return cached result if available (both success and failure)
64
+ if (cachedLoadResult) {
65
+ return cachedLoadResult;
66
+ }
24
67
  const filePath = providersPath || (0, path_1.join)(__dirname, '..', 'providers', 'emailproviders.json');
25
68
  const issues = [];
26
69
  let providers = [];
@@ -42,7 +85,12 @@ function secureLoadProviders(providersPath, expectedHash) {
42
85
  try {
43
86
  const fileContent = (0, fs_1.readFileSync)(filePath, 'utf8');
44
87
  const data = JSON.parse(fileContent);
45
- providers = data.providers || [];
88
+ // Validate format
89
+ if (!data.version || !data.providers || !Array.isArray(data.providers)) {
90
+ throw new Error('Invalid provider data format');
91
+ }
92
+ // Convert to EmailProvider format
93
+ providers = data.providers.map(convertProviderToEmailProvider);
46
94
  }
47
95
  catch (error) {
48
96
  issues.push(`Failed to load providers file: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -91,7 +139,7 @@ function secureLoadProviders(providersPath, expectedHash) {
91
139
  else if (urlAudit.invalid > 0 || issues.length > 0) {
92
140
  securityLevel = 'WARNING';
93
141
  }
94
- return {
142
+ const loadResult = {
95
143
  success: securityLevel !== 'CRITICAL',
96
144
  providers: secureProviders,
97
145
  securityReport: {
@@ -104,6 +152,9 @@ function secureLoadProviders(providersPath, expectedHash) {
104
152
  issues
105
153
  }
106
154
  };
155
+ // Cache the result for future calls
156
+ cachedLoadResult = loadResult;
157
+ return loadResult;
107
158
  }
108
159
  /**
109
160
  * Development utility to generate and display current hashes
@@ -121,7 +172,7 @@ function initializeSecurity() {
121
172
  function createSecurityMiddleware(options = {}) {
122
173
  return (req, res, next) => {
123
174
  // If a custom providers getter is provided, use that instead of loading from file
124
- const result = options.getProviders ? options.getProviders() : secureLoadProviders(undefined, options.expectedHash);
175
+ const result = options.getProviders ? options.getProviders() : loadProviders(undefined, options.expectedHash);
125
176
  // Handle security level
126
177
  if (result.securityReport.securityLevel === 'CRITICAL' && !options.allowInvalidUrls) {
127
178
  if (options.onSecurityIssue) {
@@ -139,8 +190,9 @@ function createSecurityMiddleware(options = {}) {
139
190
  };
140
191
  }
141
192
  exports.default = {
142
- secureLoadProviders,
193
+ loadProviders,
143
194
  initializeSecurity,
144
- createSecurityMiddleware
195
+ createSecurityMiddleware,
196
+ clearCache
145
197
  };
146
- //# sourceMappingURL=secure-loader.js.map
198
+ //# sourceMappingURL=provider-loader.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikkelscheike/email-provider-links",
3
- "version": "3.0.2",
3
+ "version": "4.0.0",
4
4
  "description": "TypeScript library for email provider detection with 93 providers (207 domains), concurrent DNS resolution, optimized performance, 94.65% test coverage, and enterprise security for login and password reset flows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,9 +23,6 @@
23
23
  "pretest": "node scripts/sync-versions.js",
24
24
  "prebuild": "node scripts/sync-versions.js",
25
25
  "update-hashes": "tsx scripts/update-hashes.ts",
26
- "release:major": "tsx scripts/prepare-release.ts",
27
- "release:minor": "tsx scripts/prepare-release.ts",
28
- "release:patch": "tsx scripts/prepare-release.ts",
29
26
  "benchmark:memory": "tsx --expose-gc scripts/benchmark-memory.ts",
30
27
  "benchmark:dns": "tsx scripts/benchmark-concurrent-dns.ts"
31
28
  },