Last active
February 27, 2023 23:30
-
-
Save taxilian/cc117d234c44a2d230ac0b49546b1f39 to your computer and use it in GitHub Desktop.
Tool that I use to verify email addresses
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import emailaddr from 'email-addresses'; | |
import disposable_domains from 'disposable-email-domains'; | |
import dns from 'dns'; | |
import { promisify } from 'util'; | |
let resolveMx = promisify(dns.resolveMx); | |
let resolve4 = promisify(dns.resolve4); | |
let resolve6 = promisify(dns.resolve6); | |
function reverseLookup(ip: string) { | |
return new Promise<string[]>((resolve, reject) => { | |
dns.reverse(ip, (err, domains) => { | |
if (err) { | |
if (err.code === 'ENOTFOUND') { | |
resolve([]); | |
} else { | |
reject(err); | |
} | |
} else { | |
resolve(domains); | |
} | |
}); | |
}); | |
} | |
async function getARecord(domain: string) { | |
try { | |
// Lists all IPv4 addresses for the given domain | |
const addr = await resolve4(domain); | |
return addr; | |
} catch { | |
return []; | |
} | |
} | |
async function getAAAARecord(domain: string) { | |
try { | |
// Lists all IPv6 addresses for the given domain | |
const addr = await resolve6(domain); | |
return addr; | |
} catch { | |
return []; | |
} | |
} | |
async function getMxRecord(domain: string) { | |
try { | |
// Lists all MX records for the given domain | |
const records = await resolveMx(domain); | |
return records; | |
} catch { | |
return []; | |
} | |
} | |
export const emailValidateRegex = /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/; | |
export const emailListValidateRegex = /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))(?:, *(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\])))*$/; | |
import _ from 'lodash'; | |
import { loadModels } from '../models/slim'; | |
async function getMxDomains(domain: string) { | |
domain = domain.toLowerCase().trim(); | |
let out = new Set([domain]); | |
let [mxRec, a4Rec, a6Rec] = await Promise.all([ | |
getMxRecord(domain), | |
getARecord(domain), | |
getAAAARecord(domain), | |
]); | |
const allIpRecords = [...a4Rec, ...a6Rec]; | |
if (!mxRec.length && !allIpRecords.length) { | |
// If there are no A or MX records then it's not a valid email domain | |
return []; | |
} | |
const ipDomains = await Promise.all(allIpRecords.map(async (ip) => { | |
try { | |
const rev = await reverseLookup(ip); | |
return rev; | |
} catch { | |
return []; | |
} | |
})); | |
for (const domainList of ipDomains) { | |
for (const d of domainList) { | |
out.add(d.toLowerCase()); | |
} | |
} | |
for (let item of mxRec) { | |
let exch = item.exchange.toLowerCase(); | |
out.add(exch); | |
out.add(exch.substring(exch.indexOf('.') + 1)); | |
} | |
return Array.from(out); | |
} | |
export enum EmailValidationStatus { | |
AllGood = 0, | |
InvalidDomain, | |
Disposable, | |
InvalidSyntax, | |
Blacklisted, | |
} | |
export async function validateEmailAddress(email: string) : Promise<EmailValidationStatus> { | |
const {EmailBlacklist: EmailBlacklistModel} = loadModels("EmailBlacklist"); | |
email = email.toLowerCase().trim(); | |
if (!emailValidateRegex.test(email)) { | |
return EmailValidationStatus.InvalidSyntax; | |
} else if (await EmailBlacklistModel.checkBlacklist(email)) { | |
return EmailValidationStatus.Blacklisted; | |
} | |
let parts = emailaddr.parseOneAddress(email) as emailaddr.ParsedMailbox; | |
let domains: string[]; | |
try { | |
domains = await getMxDomains(parts.domain); | |
} catch (ex) { | |
// Generally speaking if that failed then it's an invalid domain | |
domains = []; | |
} | |
if (!domains.length) { | |
return EmailValidationStatus.InvalidDomain; | |
} | |
for (const d of domains) { | |
if (disposable_domains.indexOf(d) != -1) { | |
return EmailValidationStatus.Disposable; | |
} | |
} | |
return EmailValidationStatus.AllGood; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment