email-validator-ultimate
Version:
Advanced email validation library for Node.js. RFC 5322 format validation, MX records, SMTP inbox check, disposable email detection, typo suggestions, risk analysis, Gravatar detection, DNS blacklist check, batch validation, caching, and CLI tool.
275 lines (274 loc) • 8.53 kB
JavaScript
"use strict";
/**
* Email Risk Analysis Module
* Detects suspicious patterns in email addresses that may indicate
* spam, bot, or fraudulent accounts
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.analyzeRisk = analyzeRisk;
exports.isHighRisk = isHighRisk;
exports.getRiskScore = getRiskScore;
exports.getRiskLevelDescription = getRiskLevelDescription;
// Keyboard patterns to detect
const KEYBOARD_PATTERNS = [
'qwerty', 'qwertz', 'azerty', 'asdf', 'zxcv', 'qazwsx',
'1234', '12345', '123456', '1234567', '12345678', '123456789',
'abcd', 'abcde', 'abcdef',
'0000', '1111', '2222', '3333', '4444', '5555', '6666', '7777', '8888', '9999',
'aaaa', 'bbbb', 'cccc', 'dddd', 'eeee',
'test', 'demo', 'sample', 'example', 'fake', 'temp', 'tmp',
];
// Suspicious words that may indicate fake accounts
const SUSPICIOUS_WORDS = [
'noreply', 'no-reply', 'donotreply', 'do-not-reply',
'spam', 'junk', 'trash', 'disposable', 'throwaway',
'fake', 'temp', 'temporary', 'test', 'testing',
'admin', 'administrator', 'root', 'system',
'null', 'void', 'none', 'anonymous', 'anon',
'xxx', 'yyy', 'zzz', 'aaa', 'bbb',
];
// Regex patterns for detection
const PATTERNS = {
// More than 4 consecutive identical characters
consecutiveChars: /(.)\1{4,}/i,
// Random-looking string (many consonants together)
randomConsonants: /[bcdfghjklmnpqrstvwxz]{5,}/i,
// Too many numbers at the end
numbersAtEnd: /\d{4,}$/,
// Starts with numbers
startsWithNumbers: /^\d+/,
// All numbers in local part
allNumbers: /^\d+@/,
// Very short local part
tooShort: /^.{1,2}@/,
// Very long local part
tooLong: /^.{40,}@/,
// Multiple dots in a row (already invalid, but check anyway)
multipleDots: /\.{2,}/,
// Suspicious TLD patterns
suspiciousTld: /\.(xyz|top|click|link|work|date|racing|download|stream|gdn|men|loan|win|bid)$/i,
// UUID-like pattern
uuidLike: /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i,
// Hash-like pattern (32+ hex chars)
hashLike: /^[a-f0-9]{32,}@/i,
// Mixed case that looks random
randomMixedCase: /([A-Z][a-z]){4,}|([a-z][A-Z]){4,}/,
};
/**
* Analyze email for risk patterns
*/
function analyzeRisk(email) {
const factors = [];
let totalScore = 0;
const atIndex = email.lastIndexOf('@');
if (atIndex === -1) {
return {
riskLevel: 'critical',
riskScore: 100,
factors: [{ code: 'INVALID_FORMAT', description: 'Invalid email format', severity: 'critical', score: 100 }],
isHighRisk: true,
summary: 'Invalid email format',
};
}
const localPart = email.substring(0, atIndex).toLowerCase();
const domain = email.substring(atIndex + 1).toLowerCase();
// Check keyboard patterns
for (const pattern of KEYBOARD_PATTERNS) {
if (localPart.includes(pattern)) {
factors.push({
code: 'KEYBOARD_PATTERN',
description: `Contains keyboard pattern: "${pattern}"`,
severity: 'medium',
score: 15,
});
totalScore += 15;
break; // Only count once
}
}
// Check suspicious words
for (const word of SUSPICIOUS_WORDS) {
if (localPart.includes(word)) {
factors.push({
code: 'SUSPICIOUS_WORD',
description: `Contains suspicious word: "${word}"`,
severity: 'medium',
score: 20,
});
totalScore += 20;
break;
}
}
// Check consecutive characters
if (PATTERNS.consecutiveChars.test(localPart)) {
factors.push({
code: 'CONSECUTIVE_CHARS',
description: 'Contains 5+ consecutive identical characters',
severity: 'high',
score: 25,
});
totalScore += 25;
}
// Check random consonants
if (PATTERNS.randomConsonants.test(localPart)) {
factors.push({
code: 'RANDOM_STRING',
description: 'Appears to be randomly generated',
severity: 'high',
score: 30,
});
totalScore += 30;
}
// Check numbers at end
if (PATTERNS.numbersAtEnd.test(localPart)) {
factors.push({
code: 'NUMBERS_AT_END',
description: 'Has 4+ numbers at the end',
severity: 'low',
score: 10,
});
totalScore += 10;
}
// Check starts with numbers
if (PATTERNS.startsWithNumbers.test(localPart)) {
factors.push({
code: 'STARTS_WITH_NUMBERS',
description: 'Local part starts with numbers',
severity: 'low',
score: 5,
});
totalScore += 5;
}
// Check all numbers
if (PATTERNS.allNumbers.test(email)) {
factors.push({
code: 'ALL_NUMBERS',
description: 'Local part contains only numbers',
severity: 'high',
score: 35,
});
totalScore += 35;
}
// Check too short
if (PATTERNS.tooShort.test(email)) {
factors.push({
code: 'TOO_SHORT',
description: 'Local part is very short (1-2 characters)',
severity: 'medium',
score: 15,
});
totalScore += 15;
}
// Check too long
if (PATTERNS.tooLong.test(email)) {
factors.push({
code: 'TOO_LONG',
description: 'Local part is unusually long (40+ characters)',
severity: 'medium',
score: 20,
});
totalScore += 20;
}
// Check suspicious TLD
if (PATTERNS.suspiciousTld.test(domain)) {
factors.push({
code: 'SUSPICIOUS_TLD',
description: 'Domain uses a TLD commonly associated with spam',
severity: 'high',
score: 25,
});
totalScore += 25;
}
// Check UUID-like
if (PATTERNS.uuidLike.test(localPart)) {
factors.push({
code: 'UUID_PATTERN',
description: 'Contains UUID-like pattern (likely auto-generated)',
severity: 'high',
score: 40,
});
totalScore += 40;
}
// Check hash-like
if (PATTERNS.hashLike.test(email)) {
factors.push({
code: 'HASH_PATTERN',
description: 'Appears to be a hash (likely auto-generated)',
severity: 'high',
score: 45,
});
totalScore += 45;
}
// Calculate digit ratio
const digits = (localPart.match(/\d/g) || []).length;
const digitRatio = digits / localPart.length;
if (digitRatio > 0.5 && localPart.length > 5) {
factors.push({
code: 'HIGH_DIGIT_RATIO',
description: 'More than 50% of local part is numbers',
severity: 'medium',
score: 20,
});
totalScore += 20;
}
// Cap score at 100
totalScore = Math.min(100, totalScore);
// Determine risk level
let riskLevel;
if (totalScore >= 70) {
riskLevel = 'critical';
}
else if (totalScore >= 45) {
riskLevel = 'high';
}
else if (totalScore >= 20) {
riskLevel = 'medium';
}
else {
riskLevel = 'low';
}
// Generate summary
let summary;
if (factors.length === 0) {
summary = 'No suspicious patterns detected';
}
else if (factors.length === 1) {
summary = factors[0].description;
}
else {
summary = `${factors.length} risk factors detected`;
}
return {
riskLevel,
riskScore: totalScore,
factors,
isHighRisk: totalScore >= 45,
summary,
};
}
/**
* Quick check if email is high risk
*/
function isHighRisk(email) {
return analyzeRisk(email).isHighRisk;
}
/**
* Get risk score only
*/
function getRiskScore(email) {
return analyzeRisk(email).riskScore;
}
/**
* Get risk level description
*/
function getRiskLevelDescription(level) {
switch (level) {
case 'low':
return 'Low risk - Email appears legitimate';
case 'medium':
return 'Medium risk - Some suspicious patterns detected';
case 'high':
return 'High risk - Multiple suspicious patterns detected';
case 'critical':
return 'Critical risk - Email is likely fake or auto-generated';
}
}