-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.js
More file actions
105 lines (89 loc) · 3.64 KB
/
Copy pathindex.js
File metadata and controls
105 lines (89 loc) · 3.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*global fetch*/
const {isUndefined, isNil, isEmpty, isString, assign} = require("lodash");
const looksLikeEmail = require("validator/lib/isEmail");
const Promise = require("bluebird");
const URI = require("urijs");
const tlds = require("tlds");
// Ensure we have 'fetch' (eg: in Node)
if(isUndefined(global.fetch)) {
require("cross-fetch/polyfill"); // Lets us test the file in Node
}
const atSymbol = "@";
const getDnsOverHttpUri = (
(baseUri) => (domainName) => baseUri.clone().setQuery({name: domainName}).toString()
)(new URI("https://cloudflare-dns.com/dns-query").setQuery({
"type": "MX",
"do": true,
"cd": false,
}));
const getBurnerCheckUri = (domainName) => new URI("https://open.kickbox.com/v1/disposable/beewell.health").filename(domainName).toString();
const doFetch = (uri, customOptions={}) => Promise.try(() => fetch(uri, assign({
credentials: "omit",
mode: "cors",
method: "GET",
}, customOptions))).tap((response) => {
if(!response.ok) {
console.warn("Could not perform fetch: response was not ok", {response, uri, customOptions});
throw new Error(`Network error while fetching: ${uri}`);
}
}).call("json");
module.exports = (addr) => {
const onError = (msg) => (error) => {
console.warn(`Error while ${msg}; returning true by default: ${error.message}`, {addr, error}, error);
return true;
};
const logResults = (msg) => (result) => console.debug(
`Results of ${msg}`, {addr, result}
);
const timeLimitMs = 5000;
const runCheck = (msg, func) => Promise.try(() => func(addr)).timeout(timeLimitMs).tap(
(result) => console.debug(`Successfully completed ${msg}.`, result)
).catch(onError(msg)).tap(logResults(msg));
const validatingName = "validating email address";
return runCheck(validatingName, () => {
// Basic sanity checks.
if(isNil(addr)) return false;
if(!(isString(addr))) return false;
if(isEmpty(addr.trim())) return false;
// Ensure that '@' is not the first or last char.
if(addr.startsWith(atSymbol)) return false;
if(addr.endsWith(atSymbol)) return false;
// Now split to get the username and domain name
const [username, domainName, ...addrExtra] = addr.split(atSymbol);
if(!(isNil(addrExtra) || isEmpty(addrExtra))) return false;
if(isNil(username) || isNil(domainName)) return false;
if(isEmpty(username.trim()) || isEmpty(domainName.trim())) return false;
// Now check that it's sane: valid TLD and looks like an e-mail as per Validator
if(!tlds.includes(domainName.split(".").pop())) return false;
if(!looksLikeEmail(addr)) return false;
// Construct the checks.
const mxRecordCheckName = "performing the MX record DNS check";
const mxRecordCheck = runCheck(
mxRecordCheckName,
() => doFetch(
getDnsOverHttpUri(domainName),
{headers: {accept: "application/dns-json"}}
).tap((result) => {
if(result.Status !== 0) {
throw new Error(`Bad status code from DNS query: ${result.Status}`);
}
}).then(
(it) => it.Answer
).then(
(answer) => !(isNil(answer) || isEmpty(answer))
)
);
const burnerName = "performing burner e-mail check";
const burnerCheck = runCheck(
burnerName,
() => doFetch(getBurnerCheckUri(domainName)).then((it) => Boolean(it.disposable)).then((result) => {
if(isNil(result)) {
throw new Error(`No result returned: ${result}`);
}
return !result;
})
);
const compilingName = "compiling results";
return runCheck(compilingName, () => Promise.filter([mxRecordCheck,burnerCheck], (it) => !it).then((failures) => isNil(failures) || isEmpty(failures)));
});
};