@brilab-mailer/utils 0.0.5-4 → 0.1.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.
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import { ValidationOptions, ValidatorConstraintInterface } from 'class-validator';
|
|
1
|
+
import { ValidationOptions, ValidationArguments, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
export interface IsRealEmailOptions {
|
|
3
|
+
/** Disable the MX lookup entirely (e.g. in tests/CI). Default: false. */
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
/** Custom DNS servers for the MX lookup. Default: ['1.1.1.1', '8.8.8.8']. */
|
|
6
|
+
servers?: string[];
|
|
7
|
+
/** Lookup timeout in ms. Default: 3000. */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** Cache TTL in ms for resolved domains. Default: 300000 (5 min). */
|
|
10
|
+
cacheTtlMs?: number;
|
|
11
|
+
/** Result to return on timeout/transient DNS failure. Default: true (fail-open). */
|
|
12
|
+
failOpen?: boolean;
|
|
13
|
+
}
|
|
2
14
|
export declare class IsRealEmailConstraint implements ValidatorConstraintInterface {
|
|
3
|
-
validate(email: string): Promise<boolean>;
|
|
15
|
+
validate(email: string, args?: ValidationArguments): Promise<boolean>;
|
|
16
|
+
private lookup;
|
|
4
17
|
defaultMessage(): string;
|
|
5
18
|
}
|
|
6
|
-
export declare function IsRealEmail(
|
|
19
|
+
export declare function IsRealEmail(options?: IsRealEmailOptions & ValidationOptions): PropertyDecorator;
|
|
7
20
|
//# sourceMappingURL=is-real-email.decorator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-real-email.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/is-real-email.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"is-real-email.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/is-real-email.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EACjB,mBAAmB,EAEnB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAeD,qBACa,qBAAsB,YAAW,4BAA4B;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;YAqB7D,MAAM;IAwBpB,cAAc,IAAI,MAAM;CAGzB;AAED,wBAAgB,WAAW,CACzB,OAAO,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,GAC/C,iBAAiB,CAoBnB"}
|
|
@@ -1,26 +1,57 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { registerDecorator, ValidatorConstraint, } from 'class-validator';
|
|
3
|
-
import { Resolver
|
|
3
|
+
import { Resolver } from 'node:dns/promises';
|
|
4
|
+
const DEFAULTS = {
|
|
5
|
+
disabled: false,
|
|
6
|
+
servers: ['1.1.1.1', '8.8.8.8'],
|
|
7
|
+
timeoutMs: 3000,
|
|
8
|
+
cacheTtlMs: 5 * 60 * 1000,
|
|
9
|
+
failOpen: true,
|
|
10
|
+
};
|
|
11
|
+
const mxCache = new Map();
|
|
12
|
+
const TIMEOUT = Symbol('timeout');
|
|
4
13
|
let IsRealEmailConstraint = class IsRealEmailConstraint {
|
|
5
|
-
async validate(email) {
|
|
14
|
+
async validate(email, args) {
|
|
15
|
+
const opts = { ...DEFAULTS, ...(args?.constraints?.[0] ?? {}) };
|
|
16
|
+
if (opts.disabled)
|
|
17
|
+
return true;
|
|
18
|
+
if (typeof email !== 'string')
|
|
19
|
+
return false;
|
|
20
|
+
const domain = email.split('@').at(-1)?.trim().toLowerCase();
|
|
21
|
+
if (!domain)
|
|
22
|
+
return false;
|
|
23
|
+
const cached = mxCache.get(domain);
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
if (cached && cached.expires > now)
|
|
26
|
+
return cached.result;
|
|
27
|
+
const result = await this.lookup(domain, opts);
|
|
28
|
+
// Only cache definitive answers, not fail-open timeouts.
|
|
29
|
+
if (result.cacheable) {
|
|
30
|
+
mxCache.set(domain, { result: result.value, expires: now + opts.cacheTtlMs });
|
|
31
|
+
}
|
|
32
|
+
return result.value;
|
|
33
|
+
}
|
|
34
|
+
async lookup(domain, opts) {
|
|
35
|
+
const resolver = new Resolver();
|
|
36
|
+
if (opts.servers?.length)
|
|
37
|
+
resolver.setServers(opts.servers);
|
|
38
|
+
let timer;
|
|
39
|
+
const timeout = new Promise((resolve) => {
|
|
40
|
+
timer = setTimeout(() => resolve(TIMEOUT), opts.timeoutMs);
|
|
41
|
+
});
|
|
6
42
|
try {
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
9
|
-
return false;
|
|
10
|
-
|
|
11
|
-
let mxRecords = [];
|
|
12
|
-
if (USE_CUSTOM_RESOLVER) {
|
|
13
|
-
const resolver = new Resolver();
|
|
14
|
-
resolver.setServers(['1.1.1.1', '8.8.8.8']);
|
|
15
|
-
mxRecords = await resolver.resolveMx(domain);
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
mxRecords = await resolveMx(domain);
|
|
19
|
-
}
|
|
20
|
-
return !!mxRecords?.length;
|
|
43
|
+
const outcome = await Promise.race([resolver.resolveMx(domain), timeout]);
|
|
44
|
+
if (outcome === TIMEOUT)
|
|
45
|
+
return { value: opts.failOpen ?? true, cacheable: false };
|
|
46
|
+
return { value: Array.isArray(outcome) && outcome.length > 0, cacheable: true };
|
|
21
47
|
}
|
|
22
48
|
catch {
|
|
23
|
-
|
|
49
|
+
// Genuine resolution failure (NXDOMAIN/ENODATA): domain cannot receive mail.
|
|
50
|
+
return { value: false, cacheable: true };
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (timer)
|
|
54
|
+
clearTimeout(timer);
|
|
24
55
|
}
|
|
25
56
|
}
|
|
26
57
|
defaultMessage() {
|
|
@@ -31,14 +62,22 @@ IsRealEmailConstraint = __decorate([
|
|
|
31
62
|
ValidatorConstraint({ async: true })
|
|
32
63
|
], IsRealEmailConstraint);
|
|
33
64
|
export { IsRealEmailConstraint };
|
|
34
|
-
export function IsRealEmail(
|
|
65
|
+
export function IsRealEmail(options) {
|
|
66
|
+
const { disabled, servers, timeoutMs, cacheTtlMs, failOpen, ...validationOptions } = options ?? {};
|
|
67
|
+
const constraintOptions = {
|
|
68
|
+
disabled,
|
|
69
|
+
servers,
|
|
70
|
+
timeoutMs,
|
|
71
|
+
cacheTtlMs,
|
|
72
|
+
failOpen,
|
|
73
|
+
};
|
|
35
74
|
return function (target, propertyKey) {
|
|
36
75
|
registerDecorator({
|
|
37
76
|
name: 'IsRealEmail',
|
|
38
77
|
target: target.constructor,
|
|
39
78
|
propertyName: String(propertyKey),
|
|
40
79
|
options: validationOptions,
|
|
41
|
-
constraints: [],
|
|
80
|
+
constraints: [constraintOptions],
|
|
42
81
|
validator: IsRealEmailConstraint,
|
|
43
82
|
});
|
|
44
83
|
};
|