Skip to content

Instantly share code, notes, and snippets.

@taxilian
Last active February 27, 2023 23:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taxilian/cc117d234c44a2d230ac0b49546b1f39 to your computer and use it in GitHub Desktop.
Save taxilian/cc117d234c44a2d230ac0b49546b1f39 to your computer and use it in GitHub Desktop.
Tool that I use to verify email addresses
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