@mikkelscheike/email-provider-links 2.5.1 → 2.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 +60 -8
- package/dist/api.d.ts +2 -1
- package/dist/concurrent-dns.d.ts +1 -1
- package/dist/concurrent-dns.js +24 -3
- package/dist/{security/hash-verifier.js → hash-verifier.js} +3 -3
- package/dist/idn.d.ts +30 -0
- package/dist/idn.js +145 -0
- package/dist/{security/secure-loader.d.ts → secure-loader.d.ts} +1 -1
- package/dist/{security/secure-loader.js → secure-loader.js} +1 -1
- package/dist/{security/url-validator.js → url-validator.js} +1 -1
- package/package.json +6 -6
- package/dist/__tests__/basic.test.d.ts +0 -1
- package/dist/__tests__/basic.test.js +0 -8
- /package/dist/{security/hash-verifier.d.ts → hash-verifier.d.ts} +0 -0
- /package/dist/{security/url-validator.d.ts → url-validator.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -6,10 +6,11 @@ A TypeScript library providing direct links to **93 email providers** (178 domai
|
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
9
|
-
- 🚀 **Fast & Lightweight**: Zero dependencies,
|
|
9
|
+
- 🚀 **Fast & Lightweight**: Zero dependencies, ultra-low memory (~0.39MB initial, ~0.02MB per 1000 ops)
|
|
10
10
|
- 📧 **93 Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and many more
|
|
11
11
|
- 🌐 **178 Domains Supported**: Comprehensive international coverage
|
|
12
|
-
- 🌍 **IDN Support**: Full internationalized domain name (punycode) support
|
|
12
|
+
- 🌍 **IDN Support**: Full internationalized domain name (punycode) support with RFC compliance
|
|
13
|
+
- ✅ **Email Validation**: International email validation following RFC 5321, 5322, and 6530 standards
|
|
13
14
|
- 🏢 **Business Domain Detection**: DNS-based detection for custom domains (Google Workspace, Microsoft 365, etc.)
|
|
14
15
|
- 🔒 **Enterprise Security**: Multi-layer protection against malicious URLs and supply chain attacks
|
|
15
16
|
- 🛡️ **URL Validation**: HTTPS-only enforcement with domain allowlisting
|
|
@@ -28,15 +29,9 @@ Using npm:
|
|
|
28
29
|
npm install @mikkelscheike/email-provider-links
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
Using pnpm:
|
|
32
|
-
```bash
|
|
33
|
-
pnpm add @mikkelscheike/email-provider-links
|
|
34
|
-
```
|
|
35
|
-
|
|
36
32
|
## Requirements
|
|
37
33
|
|
|
38
34
|
- **Node.js**: `>=18.0.0` (Tested on 18.x, 20.x, 22.x, **24.x**)
|
|
39
|
-
- **Package Managers**: npm and pnpm (Tested on pnpm 18.x through 24.x)
|
|
40
35
|
- **TypeScript**: `>=4.0.0` (optional)
|
|
41
36
|
- **Zero runtime dependencies** - No external packages required
|
|
42
37
|
|
|
@@ -160,6 +155,39 @@ console.log('Max requests:', Config.MAX_DNS_REQUESTS_PER_MINUTE); // 10
|
|
|
160
155
|
console.log('Default timeout:', Config.DEFAULT_DNS_TIMEOUT); // 5000ms
|
|
161
156
|
```
|
|
162
157
|
|
|
158
|
+
## Email Validation
|
|
159
|
+
|
|
160
|
+
The library includes comprehensive email validation following international standards:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { validateInternationalEmail } from '@mikkelscheike/email-provider-links';
|
|
164
|
+
|
|
165
|
+
// Validate any email address
|
|
166
|
+
const result = validateInternationalEmail('user@example.com');
|
|
167
|
+
|
|
168
|
+
// Returns undefined for valid emails
|
|
169
|
+
console.log(result); // undefined
|
|
170
|
+
|
|
171
|
+
// Returns detailed error information for invalid emails
|
|
172
|
+
const invalid = validateInternationalEmail('user@münchen.com');
|
|
173
|
+
if (invalid) {
|
|
174
|
+
console.log(invalid.code); // IDN_VALIDATION_ERROR
|
|
175
|
+
console.log(invalid.message); // Human-readable error message
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Validation Features
|
|
180
|
+
|
|
181
|
+
- ✅ **RFC Compliance**: Follows RFC 5321, 5322, and 6530 standards
|
|
182
|
+
- 🌍 **International Support**: Full IDN (Punycode) validation
|
|
183
|
+
- 📝 **Detailed Errors**: Clear, translatable error messages
|
|
184
|
+
- 🔍 **Comprehensive Checks**:
|
|
185
|
+
- Local part validation (username)
|
|
186
|
+
- Domain format validation
|
|
187
|
+
- IDN encoding validation
|
|
188
|
+
- Length limits (local part, domain, total)
|
|
189
|
+
- TLD validation
|
|
190
|
+
|
|
163
191
|
## Advanced Usage
|
|
164
192
|
|
|
165
193
|
<details>
|
|
@@ -262,6 +290,12 @@ interface EmailProviderResult {
|
|
|
262
290
|
loginUrl: string | null;
|
|
263
291
|
detectionMethod?: 'domain_match' | 'mx_record' | 'txt_record' | 'proxy_detected';
|
|
264
292
|
proxyService?: string;
|
|
293
|
+
error?: {
|
|
294
|
+
type: 'INVALID_EMAIL' | 'DNS_TIMEOUT' | 'RATE_LIMITED' | 'UNKNOWN_DOMAIN' |
|
|
295
|
+
'NETWORK_ERROR' | 'IDN_VALIDATION_ERROR';
|
|
296
|
+
message: string;
|
|
297
|
+
idnError?: string; // Specific IDN validation error message
|
|
298
|
+
};
|
|
265
299
|
}
|
|
266
300
|
|
|
267
301
|
interface ConfigConstants {
|
|
@@ -318,6 +352,24 @@ if (result.securityReport.securityLevel === 'CRITICAL') {
|
|
|
318
352
|
}
|
|
319
353
|
```
|
|
320
354
|
|
|
355
|
+
## Performance Benchmarks
|
|
356
|
+
|
|
357
|
+
This package is designed to be extremely memory efficient and fast. We continuously monitor performance metrics through automated benchmarks that run on every PR and release.
|
|
358
|
+
|
|
359
|
+
Latest benchmark results show:
|
|
360
|
+
- Provider loading: ~0.39MB heap usage, <0.5ms
|
|
361
|
+
- Email lookups: ~0.02MB heap usage per 100 operations
|
|
362
|
+
- Concurrent DNS: ~0.03MB heap usage, ~110ms for 10 lookups
|
|
363
|
+
- Large scale (1000 ops): ~0.02MB heap usage, <3ms total
|
|
364
|
+
- Cache effectiveness: ~0.01MB impact on subsequent loads
|
|
365
|
+
|
|
366
|
+
To run benchmarks locally:
|
|
367
|
+
```bash
|
|
368
|
+
npm run benchmark
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Benchmarks are automatically run in CI to catch any performance regressions.
|
|
372
|
+
|
|
321
373
|
## Contributing
|
|
322
374
|
|
|
323
375
|
We welcome contributions! See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines on adding new email providers.
|
package/dist/api.d.ts
CHANGED
|
@@ -29,9 +29,10 @@ export interface EmailProviderResult {
|
|
|
29
29
|
proxyService?: string;
|
|
30
30
|
/** Error information if detection failed */
|
|
31
31
|
error?: {
|
|
32
|
-
type: 'INVALID_EMAIL' | 'DNS_TIMEOUT' | 'RATE_LIMITED' | 'UNKNOWN_DOMAIN' | 'NETWORK_ERROR';
|
|
32
|
+
type: 'INVALID_EMAIL' | 'DNS_TIMEOUT' | 'RATE_LIMITED' | 'UNKNOWN_DOMAIN' | 'NETWORK_ERROR' | 'IDN_VALIDATION_ERROR';
|
|
33
33
|
message: string;
|
|
34
34
|
retryAfter?: number;
|
|
35
|
+
idnError?: string;
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
/**
|
package/dist/concurrent-dns.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export interface ConcurrentDNSResult {
|
|
|
69
69
|
*/
|
|
70
70
|
export declare class ConcurrentDNSDetector {
|
|
71
71
|
private activeQueries;
|
|
72
|
-
cleanup(): void
|
|
72
|
+
cleanup(): Promise<void>;
|
|
73
73
|
private config;
|
|
74
74
|
private providers;
|
|
75
75
|
constructor(providers: EmailProvider[], config?: Partial<ConcurrentDNSConfig>);
|
package/dist/concurrent-dns.js
CHANGED
|
@@ -30,7 +30,13 @@ const DEFAULT_CONFIG = {
|
|
|
30
30
|
class ConcurrentDNSDetector {
|
|
31
31
|
// Cleanup method for tests
|
|
32
32
|
cleanup() {
|
|
33
|
+
// Cancel any in-progress timeouts
|
|
34
|
+
const timeoutError = new Error('Operation cancelled by cleanup');
|
|
35
|
+
for (const { reject } of this.activeQueries) {
|
|
36
|
+
reject(timeoutError);
|
|
37
|
+
}
|
|
33
38
|
this.activeQueries.clear();
|
|
39
|
+
return Promise.resolve();
|
|
34
40
|
}
|
|
35
41
|
constructor(providers, config = {}) {
|
|
36
42
|
// Store active query states
|
|
@@ -384,13 +390,28 @@ class ConcurrentDNSDetector {
|
|
|
384
390
|
* Wrap a promise with a timeout
|
|
385
391
|
*/
|
|
386
392
|
withTimeout(promise, ms) {
|
|
387
|
-
|
|
388
|
-
|
|
393
|
+
let rejectFn;
|
|
394
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
|
395
|
+
rejectFn = reject;
|
|
396
|
+
const timeout = setTimeout(() => reject(new Error(`DNS query timeout after ${ms}ms`)), ms).unref();
|
|
389
397
|
promise
|
|
390
398
|
.then(resolve)
|
|
391
399
|
.catch(reject)
|
|
392
|
-
.finally(() =>
|
|
400
|
+
.finally(() => {
|
|
401
|
+
clearTimeout(timeout);
|
|
402
|
+
// Clean up active query
|
|
403
|
+
const queryEntry = Array.from(this.activeQueries).find(entry => entry.promise === timeoutPromise);
|
|
404
|
+
if (queryEntry) {
|
|
405
|
+
this.activeQueries.delete(queryEntry);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
393
408
|
});
|
|
409
|
+
// Only add to active queries if we have a reject function
|
|
410
|
+
if (rejectFn) {
|
|
411
|
+
const queryEntry = { promise: timeoutPromise, reject: rejectFn };
|
|
412
|
+
this.activeQueries.add(queryEntry);
|
|
413
|
+
}
|
|
414
|
+
return timeoutPromise;
|
|
394
415
|
}
|
|
395
416
|
}
|
|
396
417
|
exports.ConcurrentDNSDetector = ConcurrentDNSDetector;
|
|
@@ -29,7 +29,7 @@ const KNOWN_GOOD_HASHES = {
|
|
|
29
29
|
// SHA-256 hash of the legitimate emailproviders.json
|
|
30
30
|
'emailproviders.json': 'f77814bf0537019c6f38bf2744ae21640f04a2d39cb67c5116f6e03160c9486f',
|
|
31
31
|
// You can add hashes for other critical files
|
|
32
|
-
'package.json': '
|
|
32
|
+
'package.json': 'da14e15d9602b7e2cdf3d08f4756ca7ff9af842906e4de1af7664c1d1cce8e9e'
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* Calculates SHA-256 hash of a file or string content
|
|
@@ -140,7 +140,7 @@ function generateSecurityHashes(basePath = __dirname) {
|
|
|
140
140
|
const hashes = {};
|
|
141
141
|
for (const file of files) {
|
|
142
142
|
try {
|
|
143
|
-
const fullPath = (0, path_1.join)(basePath, '..',
|
|
143
|
+
const fullPath = (0, path_1.join)(basePath, '..', file);
|
|
144
144
|
const hash = calculateFileHash(fullPath);
|
|
145
145
|
hashes[file.split('/').pop() || file] = hash;
|
|
146
146
|
console.log(`✅ ${file}: ${hash}`);
|
|
@@ -231,7 +231,7 @@ function handleHashMismatch(result, options = {}) {
|
|
|
231
231
|
* @returns Complete security audit result
|
|
232
232
|
*/
|
|
233
233
|
function performSecurityAudit(providersFilePath) {
|
|
234
|
-
const filePath = providersFilePath || (0, path_1.join)(__dirname, '..', '
|
|
234
|
+
const filePath = providersFilePath || (0, path_1.join)(__dirname, '..', 'providers', 'emailproviders.json');
|
|
235
235
|
const hashResult = verifyProvidersIntegrity(filePath);
|
|
236
236
|
const recommendations = [];
|
|
237
237
|
let securityLevel = 'HIGH';
|
package/dist/idn.d.ts
CHANGED
|
@@ -14,3 +14,33 @@ export declare function domainToPunycode(domain: string): string;
|
|
|
14
14
|
* @returns Email with Punycode encoded domain
|
|
15
15
|
*/
|
|
16
16
|
export declare function emailToPunycode(email: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Error codes for IDN validation
|
|
19
|
+
* These can be used as keys for translation systems
|
|
20
|
+
*/
|
|
21
|
+
export declare enum IDNValidationError {
|
|
22
|
+
MISSING_INPUT = "MISSING_INPUT",
|
|
23
|
+
EMAIL_TOO_LONG = "EMAIL_TOO_LONG",
|
|
24
|
+
MISSING_AT_SYMBOL = "MISSING_AT_SYMBOL",
|
|
25
|
+
LOCAL_PART_EMPTY = "LOCAL_PART_EMPTY",
|
|
26
|
+
LOCAL_PART_TOO_LONG = "LOCAL_PART_TOO_LONG",
|
|
27
|
+
LOCAL_PART_INVALID = "LOCAL_PART_INVALID",
|
|
28
|
+
DOMAIN_EMPTY = "DOMAIN_EMPTY",
|
|
29
|
+
DOMAIN_TOO_LONG = "DOMAIN_TOO_LONG",
|
|
30
|
+
DOMAIN_INVALID_FORMAT = "DOMAIN_INVALID_FORMAT",
|
|
31
|
+
MISSING_TLD = "MISSING_TLD",
|
|
32
|
+
NUMERIC_TLD = "NUMERIC_TLD",
|
|
33
|
+
INVALID_ENCODING = "INVALID_ENCODING"
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validates an email address according to international standards (IDNA)
|
|
37
|
+
* This implementation follows RFC 5321, 5322, and 6530 standards for email addresses
|
|
38
|
+
*
|
|
39
|
+
* @param email The email address to validate
|
|
40
|
+
* @returns Error information if validation fails, undefined if valid
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateInternationalEmail(email: string): {
|
|
43
|
+
type: 'IDN_VALIDATION_ERROR';
|
|
44
|
+
code: IDNValidationError;
|
|
45
|
+
message: string;
|
|
46
|
+
} | undefined;
|
package/dist/idn.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Zero-dependency implementation for domain name handling
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.IDNValidationError = void 0;
|
|
7
8
|
exports.domainToPunycode = domainToPunycode;
|
|
8
9
|
exports.emailToPunycode = emailToPunycode;
|
|
10
|
+
exports.validateInternationalEmail = validateInternationalEmail;
|
|
9
11
|
const BASE = 36;
|
|
10
12
|
const INITIAL_N = 128;
|
|
11
13
|
const INITIAL_BIAS = 72;
|
|
@@ -101,3 +103,146 @@ function emailToPunycode(email) {
|
|
|
101
103
|
return email;
|
|
102
104
|
return `${local}@${domainToPunycode(domain)}`;
|
|
103
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Error codes for IDN validation
|
|
108
|
+
* These can be used as keys for translation systems
|
|
109
|
+
*/
|
|
110
|
+
var IDNValidationError;
|
|
111
|
+
(function (IDNValidationError) {
|
|
112
|
+
IDNValidationError["MISSING_INPUT"] = "MISSING_INPUT";
|
|
113
|
+
IDNValidationError["EMAIL_TOO_LONG"] = "EMAIL_TOO_LONG";
|
|
114
|
+
IDNValidationError["MISSING_AT_SYMBOL"] = "MISSING_AT_SYMBOL";
|
|
115
|
+
IDNValidationError["LOCAL_PART_EMPTY"] = "LOCAL_PART_EMPTY";
|
|
116
|
+
IDNValidationError["LOCAL_PART_TOO_LONG"] = "LOCAL_PART_TOO_LONG";
|
|
117
|
+
IDNValidationError["LOCAL_PART_INVALID"] = "LOCAL_PART_INVALID";
|
|
118
|
+
IDNValidationError["DOMAIN_EMPTY"] = "DOMAIN_EMPTY";
|
|
119
|
+
IDNValidationError["DOMAIN_TOO_LONG"] = "DOMAIN_TOO_LONG";
|
|
120
|
+
IDNValidationError["DOMAIN_INVALID_FORMAT"] = "DOMAIN_INVALID_FORMAT";
|
|
121
|
+
IDNValidationError["MISSING_TLD"] = "MISSING_TLD";
|
|
122
|
+
IDNValidationError["NUMERIC_TLD"] = "NUMERIC_TLD";
|
|
123
|
+
IDNValidationError["INVALID_ENCODING"] = "INVALID_ENCODING";
|
|
124
|
+
})(IDNValidationError || (exports.IDNValidationError = IDNValidationError = {}));
|
|
125
|
+
/**
|
|
126
|
+
* Validates an email address according to international standards (IDNA)
|
|
127
|
+
* This implementation follows RFC 5321, 5322, and 6530 standards for email addresses
|
|
128
|
+
*
|
|
129
|
+
* @param email The email address to validate
|
|
130
|
+
* @returns Error information if validation fails, undefined if valid
|
|
131
|
+
*/
|
|
132
|
+
function validateInternationalEmail(email) {
|
|
133
|
+
// Basic checks
|
|
134
|
+
if (!email || typeof email !== 'string') {
|
|
135
|
+
return {
|
|
136
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
137
|
+
code: IDNValidationError.MISSING_INPUT,
|
|
138
|
+
message: 'The email field cannot be empty'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Split into local and domain parts
|
|
142
|
+
const atIndex = email.lastIndexOf('@');
|
|
143
|
+
if (atIndex === -1) {
|
|
144
|
+
return {
|
|
145
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
146
|
+
code: IDNValidationError.MISSING_AT_SYMBOL,
|
|
147
|
+
message: 'The email address must contain an @ symbol'
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const local = email.slice(0, atIndex);
|
|
151
|
+
const domain = email.slice(atIndex + 1);
|
|
152
|
+
// Check for max length - RFC 5321
|
|
153
|
+
if (email.length > 254) {
|
|
154
|
+
return {
|
|
155
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
156
|
+
code: IDNValidationError.EMAIL_TOO_LONG,
|
|
157
|
+
message: 'The email address is too long'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Validate domain part
|
|
161
|
+
if (domain.length === 0) {
|
|
162
|
+
return {
|
|
163
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
164
|
+
code: IDNValidationError.DOMAIN_EMPTY,
|
|
165
|
+
message: 'The domain part of the email cannot be empty'
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (domain.length > 255) {
|
|
169
|
+
return {
|
|
170
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
171
|
+
code: IDNValidationError.DOMAIN_TOO_LONG,
|
|
172
|
+
message: 'The domain part of the email is too long'
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Validate local part
|
|
176
|
+
if (local.length === 0) {
|
|
177
|
+
return {
|
|
178
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
179
|
+
code: IDNValidationError.LOCAL_PART_EMPTY,
|
|
180
|
+
message: 'The username part of the email cannot be empty'
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (local.length > 64) {
|
|
184
|
+
return {
|
|
185
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
186
|
+
code: IDNValidationError.LOCAL_PART_TOO_LONG,
|
|
187
|
+
message: 'The username part of the email is too long'
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// Check local part characters
|
|
191
|
+
// Allows: letters, numbers, and !#$%&'*+-/=?^_`{|}~.
|
|
192
|
+
// Dot can't be first, last, or consecutive
|
|
193
|
+
if (!/^[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~]([a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~.]*[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~])?$/.test(local) || local.includes('..')) {
|
|
194
|
+
return {
|
|
195
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
196
|
+
code: IDNValidationError.LOCAL_PART_INVALID,
|
|
197
|
+
message: 'The username contains invalid characters or dots in wrong places'
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Check domain format (including IDN domains)
|
|
201
|
+
try {
|
|
202
|
+
// Check for lone surrogates and control characters
|
|
203
|
+
if (/[\uD800-\uDFFF]/.test(domain)) {
|
|
204
|
+
return {
|
|
205
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
206
|
+
code: IDNValidationError.INVALID_ENCODING,
|
|
207
|
+
message: 'The domain contains invalid characters or encoding'
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Convert to punycode to handle IDN
|
|
211
|
+
const punycodeDomain = domainToPunycode(domain);
|
|
212
|
+
// Check basic domain format
|
|
213
|
+
// Allows: letters, numbers, hyphens (not first/last), dots separating labels
|
|
214
|
+
if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/.test(punycodeDomain)) {
|
|
215
|
+
return {
|
|
216
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
217
|
+
code: IDNValidationError.DOMAIN_INVALID_FORMAT,
|
|
218
|
+
message: 'The domain format is invalid'
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// Check if domain has at least one dot (TLD required)
|
|
222
|
+
if (!punycodeDomain.includes('.')) {
|
|
223
|
+
return {
|
|
224
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
225
|
+
code: IDNValidationError.MISSING_TLD,
|
|
226
|
+
message: 'The email domain must include a top-level domain (like .com or .org)'
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Check TLD is not all-numeric
|
|
230
|
+
const tld = punycodeDomain.split('.').pop();
|
|
231
|
+
if (/^[0-9]+$/.test(tld)) {
|
|
232
|
+
return {
|
|
233
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
234
|
+
code: IDNValidationError.NUMERIC_TLD,
|
|
235
|
+
message: 'The top-level domain cannot be all numbers'
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return {
|
|
241
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
242
|
+
code: IDNValidationError.INVALID_ENCODING,
|
|
243
|
+
message: 'The domain contains invalid characters or encoding'
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// If we get here, the email is valid
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Integrates URL validation and hash verification to create a secure
|
|
5
5
|
* loading system for email provider data.
|
|
6
6
|
*/
|
|
7
|
-
import type { EmailProvider } from '
|
|
7
|
+
import type { EmailProvider } from './index';
|
|
8
8
|
export interface SecureLoadResult {
|
|
9
9
|
success: boolean;
|
|
10
10
|
providers: EmailProvider[];
|
|
@@ -21,7 +21,7 @@ const hash_verifier_1 = require("./hash-verifier");
|
|
|
21
21
|
* @returns Secure load result with validation details
|
|
22
22
|
*/
|
|
23
23
|
function secureLoadProviders(providersPath, expectedHash) {
|
|
24
|
-
const filePath = providersPath || (0, path_1.join)(__dirname, '..', '
|
|
24
|
+
const filePath = providersPath || (0, path_1.join)(__dirname, '..', 'providers', 'emailproviders.json');
|
|
25
25
|
const issues = [];
|
|
26
26
|
let providers = [];
|
|
27
27
|
// Step 1: Hash verification
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikkelscheike/email-provider-links",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "TypeScript library for email provider detection with 93 providers (178 domains), concurrent DNS resolution, optimized performance, 91.75% test coverage, and enterprise security for login and password reset flows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"dist/**/*.js",
|
|
9
9
|
"dist/**/*.d.ts",
|
|
10
10
|
"providers/emailproviders.json",
|
|
11
|
-
"!dist/**/*.map"
|
|
12
|
-
"!dist/**/security-demo.*"
|
|
11
|
+
"!dist/**/*.map"
|
|
13
12
|
],
|
|
14
13
|
"scripts": {
|
|
15
14
|
"build": "tsc",
|
|
@@ -24,7 +23,8 @@
|
|
|
24
23
|
"update-hashes": "tsx scripts/update-hashes.ts",
|
|
25
24
|
"release:major": "tsx scripts/prepare-release.ts",
|
|
26
25
|
"release:minor": "tsx scripts/prepare-release.ts",
|
|
27
|
-
"release:patch": "tsx scripts/prepare-release.ts"
|
|
26
|
+
"release:patch": "tsx scripts/prepare-release.ts",
|
|
27
|
+
"benchmark": "tsx --expose-gc benchmark/memory.ts"
|
|
28
28
|
},
|
|
29
29
|
"keywords": [
|
|
30
30
|
"email",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
"access": "public"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
+
"@jest/globals": "^30.0.2",
|
|
64
65
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
65
|
-
"@semantic-release/exec": "^
|
|
66
|
+
"@semantic-release/exec": "^7.1.0",
|
|
66
67
|
"@semantic-release/git": "^10.0.1",
|
|
67
68
|
"@semantic-release/github": "^11.0.3",
|
|
68
69
|
"@semantic-release/npm": "^12.0.1",
|
|
69
70
|
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
70
|
-
"@jest/globals": "^30.0.2",
|
|
71
71
|
"@types/jest": "^30.0.0",
|
|
72
72
|
"@types/node": "^24.0.3",
|
|
73
73
|
"conventional-changelog-conventionalcommits": "^9.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const globals_1 = require("@jest/globals");
|
|
4
|
-
(0, globals_1.describe)('Basic Test', () => {
|
|
5
|
-
(0, globals_1.test)('true should be true', () => {
|
|
6
|
-
(0, globals_1.expect)(true).toBe(true);
|
|
7
|
-
});
|
|
8
|
-
});
|
|
File without changes
|
|
File without changes
|