valid-email-checker 1.0.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.
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Spam Email Detector
3
+ * Detects spam patterns in email addresses
4
+ */
5
+
6
+ const { extractLocal, extractDomain } = require('./formatValidator');
7
+
8
+ // Spam patterns in local part of email
9
+ const SPAM_LOCAL_PATTERNS = [
10
+ // Adult content patterns
11
+ /^(xxx|sex|porn|adult|nude|naked|hot|sexy)/i,
12
+ /(xxx|sex|porn|adult|nude|naked)$/i,
13
+
14
+ // Scam patterns
15
+ /^(lottery|winner|prize|cash|money|profit|income)/i,
16
+ /(lottery|winner|prize|cash|money|profit)$/i,
17
+ /^(free|bonus|discount|deal|offer|promo)/i,
18
+
19
+ // Suspicious number patterns
20
+ /^\d{8,}$/, // Only numbers, 8+ digits
21
+ /^[a-z]\d{6,}$/i, // Letter followed by many numbers
22
+ /^\d{3,}[a-z]{1,2}\d{3,}$/i, // Numbers-letters-numbers pattern
23
+
24
+ // Random looking patterns
25
+ /^[a-z]{1,2}\d{4,}[a-z]{1,2}$/i, // a1234b pattern
26
+ /^[bcdfghjklmnpqrstvwxyz]{5,}$/i, // Only consonants
27
+
28
+ // Bot/automated patterns
29
+ /^(bot|robot|auto|system|noreply|no-reply|donotreply)/i,
30
+ /^(admin|administrator|support|help|info|contact)$/i,
31
+
32
+ // Test patterns
33
+ /^(test|testing|demo|sample|example|dummy)/i,
34
+ /^(asdf|qwerty|zxcv|aaa|bbb|abc123)/i,
35
+
36
+ // Suspicious keywords
37
+ /^(click|subscribe|unsubscribe|confirm|verify|validate)/i,
38
+ /(marketing|newsletter|promo|sales|advertising)/i,
39
+
40
+ // Impersonation patterns
41
+ /^(paypal|amazon|google|microsoft|apple|facebook|instagram|twitter)\d/i,
42
+ /^(support|help|service)[-_]?(paypal|amazon|google|microsoft)/i,
43
+
44
+ // Excessive special characters in local part
45
+ /[._-]{3,}/, // Three or more consecutive special chars
46
+ /^[._-]/, // Starting with special char
47
+ /[._-]$/, // Ending with special char
48
+
49
+ // Gibberish patterns
50
+ /([a-z])\1{4,}/i, // Same letter repeated 5+ times
51
+ /\d{10,}/, // 10+ consecutive digits
52
+ ];
53
+
54
+ // Spam patterns in domain
55
+ const SPAM_DOMAIN_PATTERNS = [
56
+ // Suspicious TLD patterns
57
+ /\.(xyz|top|wang|win|bid|stream|download|racing|date|accountant|science|party|loan|trade|webcam|gdn|men|work|click)$/i,
58
+
59
+ // IP-like domains
60
+ /^\d{1,3}[-_.]\d{1,3}[-_.]\d{1,3}[-_.]\d{1,3}/,
61
+
62
+ // Very long domains
63
+ /^[a-z0-9-]{40,}\./i,
64
+
65
+ // Random-looking domains
66
+ /^[a-z]{15,}\.(com|net|org)$/i,
67
+
68
+ // Typosquatting patterns for popular services
69
+ /^(g00gle|googl3|go0gle|gooogle|googlee)/i,
70
+ /^(faceb00k|facebo0k|facebok|faceboook)/i,
71
+ /^(paypall|paypa1|payp4l|paypa|paypl)/i,
72
+ /^(amaz0n|amazn|amazzon|amazonn)/i,
73
+ /^(micr0soft|mircosoft|microsft|m1crosoft)/i,
74
+ /^(app1e|aple|applle|4pple)/i,
75
+
76
+ // Suspicious patterns
77
+ /-(secure|login|verify|account|update|confirm)\./i,
78
+ /\.(secure|login|verify)-/i,
79
+ ];
80
+
81
+ // Known spam/phishing domain keywords
82
+ const SPAM_DOMAIN_KEYWORDS = [
83
+ 'phishing',
84
+ 'scam',
85
+ 'malware',
86
+ 'virus',
87
+ 'hack',
88
+ 'crack',
89
+ 'warez',
90
+ 'pirate',
91
+ 'torrent',
92
+ 'download-free',
93
+ 'free-download',
94
+ 'win-prize',
95
+ 'get-rich',
96
+ 'make-money',
97
+ 'earn-cash',
98
+ 'crypto-profit',
99
+ 'bitcoin-profit',
100
+ 'forex-profit',
101
+ ];
102
+
103
+ // Suspicious email characteristics scores
104
+ const SPAM_SCORES = {
105
+ suspiciousLocalPattern: 15,
106
+ suspiciousDomainPattern: 20,
107
+ suspiciousDomainKeyword: 25,
108
+ excessiveNumbers: 10,
109
+ excessiveSpecialChars: 10,
110
+ randomLookingLocal: 15,
111
+ suspiciousTld: 15,
112
+ typosquatting: 30,
113
+ veryLongLocal: 10,
114
+ veryLongDomain: 10,
115
+ impersonation: 25,
116
+ };
117
+
118
+ /**
119
+ * Check if email matches spam patterns
120
+ * @param {string} email - Email address to check
121
+ * @param {object} options - Options for checking
122
+ * @returns {object} Result with isSpam flag and details
123
+ */
124
+ function isSpamEmail(email, options = {}) {
125
+ const {
126
+ customPatterns = [],
127
+ threshold = 30, // Score threshold to consider as spam
128
+ strictMode = false
129
+ } = options;
130
+
131
+ const result = {
132
+ isSpam: false,
133
+ email: email,
134
+ score: 0,
135
+ maxScore: 100,
136
+ reasons: [],
137
+ details: {
138
+ localPart: null,
139
+ domain: null,
140
+ matches: []
141
+ }
142
+ };
143
+
144
+ if (!email || typeof email !== 'string') {
145
+ result.reasons.push('Invalid email provided');
146
+ return result;
147
+ }
148
+
149
+ const local = extractLocal(email);
150
+ const domain = extractDomain(email);
151
+
152
+ if (!local || !domain) {
153
+ result.reasons.push('Could not parse email');
154
+ return result;
155
+ }
156
+
157
+ result.details.localPart = local;
158
+ result.details.domain = domain;
159
+
160
+ let score = 0;
161
+
162
+ // Check local part patterns
163
+ for (const pattern of SPAM_LOCAL_PATTERNS) {
164
+ if (pattern.test(local)) {
165
+ score += SPAM_SCORES.suspiciousLocalPattern;
166
+ result.reasons.push(`Local part matches spam pattern: ${pattern.toString()}`);
167
+ result.details.matches.push({ type: 'local_pattern', pattern: pattern.toString() });
168
+ break; // Only count first match
169
+ }
170
+ }
171
+
172
+ // Check custom patterns
173
+ for (const pattern of customPatterns) {
174
+ if (pattern instanceof RegExp && pattern.test(email)) {
175
+ score += SPAM_SCORES.suspiciousLocalPattern;
176
+ result.reasons.push('Matches custom spam pattern');
177
+ result.details.matches.push({ type: 'custom_pattern', pattern: pattern.toString() });
178
+ }
179
+ }
180
+
181
+ // Check domain patterns
182
+ for (const pattern of SPAM_DOMAIN_PATTERNS) {
183
+ if (pattern.test(domain)) {
184
+ // Check if it's a typosquatting pattern
185
+ if (pattern.toString().includes('g00gle') ||
186
+ pattern.toString().includes('faceb00k') ||
187
+ pattern.toString().includes('paypall') ||
188
+ pattern.toString().includes('amaz0n') ||
189
+ pattern.toString().includes('micr0soft') ||
190
+ pattern.toString().includes('app1e')) {
191
+ score += SPAM_SCORES.typosquatting;
192
+ result.reasons.push('Domain appears to be typosquatting a known brand');
193
+ } else {
194
+ score += SPAM_SCORES.suspiciousDomainPattern;
195
+ result.reasons.push(`Domain matches spam pattern: ${pattern.toString()}`);
196
+ }
197
+ result.details.matches.push({ type: 'domain_pattern', pattern: pattern.toString() });
198
+ break;
199
+ }
200
+ }
201
+
202
+ // Check domain keywords
203
+ for (const keyword of SPAM_DOMAIN_KEYWORDS) {
204
+ if (domain.includes(keyword)) {
205
+ score += SPAM_SCORES.suspiciousDomainKeyword;
206
+ result.reasons.push(`Domain contains spam keyword: ${keyword}`);
207
+ result.details.matches.push({ type: 'domain_keyword', keyword });
208
+ break;
209
+ }
210
+ }
211
+
212
+ // Check for excessive numbers in local part
213
+ const numberCount = (local.match(/\d/g) || []).length;
214
+ if (numberCount > local.length * 0.6) {
215
+ score += SPAM_SCORES.excessiveNumbers;
216
+ result.reasons.push('Local part contains excessive numbers');
217
+ result.details.matches.push({ type: 'excessive_numbers', count: numberCount });
218
+ }
219
+
220
+ // Check for very long local part
221
+ if (local.length > 40) {
222
+ score += SPAM_SCORES.veryLongLocal;
223
+ result.reasons.push('Local part is unusually long');
224
+ result.details.matches.push({ type: 'long_local', length: local.length });
225
+ }
226
+
227
+ // Check for very long domain
228
+ const domainWithoutTld = domain.split('.')[0];
229
+ if (domainWithoutTld.length > 30) {
230
+ score += SPAM_SCORES.veryLongDomain;
231
+ result.reasons.push('Domain name is unusually long');
232
+ result.details.matches.push({ type: 'long_domain', length: domainWithoutTld.length });
233
+ }
234
+
235
+ // Strict mode: Additional checks
236
+ if (strictMode) {
237
+ // Check for low-quality TLDs
238
+ const tld = domain.split('.').pop();
239
+ const lowQualityTlds = ['xyz', 'top', 'wang', 'win', 'bid', 'stream', 'download', 'racing', 'date', 'click', 'link', 'gdn', 'men'];
240
+ if (lowQualityTlds.includes(tld)) {
241
+ score += SPAM_SCORES.suspiciousTld;
242
+ result.reasons.push(`Uses potentially suspicious TLD: .${tld}`);
243
+ result.details.matches.push({ type: 'suspicious_tld', tld });
244
+ }
245
+
246
+ // Check for impersonation patterns
247
+ const impersonationPatterns = [
248
+ /^(support|help|service|account|secure|verify)[-_]?/i,
249
+ /[-_]?(support|help|service|account|secure|verify)$/i
250
+ ];
251
+
252
+ for (const pattern of impersonationPatterns) {
253
+ if (pattern.test(local)) {
254
+ const knownBrands = ['paypal', 'amazon', 'google', 'microsoft', 'apple', 'facebook', 'instagram', 'twitter', 'netflix', 'bank'];
255
+ for (const brand of knownBrands) {
256
+ if (domain.toLowerCase().includes(brand) && !domain.endsWith(`${brand}.com`)) {
257
+ score += SPAM_SCORES.impersonation;
258
+ result.reasons.push(`Possible impersonation of ${brand}`);
259
+ result.details.matches.push({ type: 'impersonation', brand });
260
+ break;
261
+ }
262
+ }
263
+ break;
264
+ }
265
+ }
266
+ }
267
+
268
+ result.score = Math.min(score, 100); // Cap at 100
269
+ result.isSpam = result.score >= threshold;
270
+
271
+ // Classify spam level
272
+ if (result.score >= 70) {
273
+ result.spamLevel = 'high';
274
+ } else if (result.score >= 40) {
275
+ result.spamLevel = 'medium';
276
+ } else if (result.score >= 20) {
277
+ result.spamLevel = 'low';
278
+ } else {
279
+ result.spamLevel = 'none';
280
+ }
281
+
282
+ return result;
283
+ }
284
+
285
+ /**
286
+ * Quick check - returns boolean only
287
+ * @param {string} email - Email to check
288
+ * @param {number} threshold - Score threshold
289
+ * @returns {boolean} True if spam
290
+ */
291
+ function isSpam(email, threshold = 30) {
292
+ return isSpamEmail(email, { threshold }).isSpam;
293
+ }
294
+
295
+ /**
296
+ * Get spam score for an email
297
+ * @param {string} email - Email to check
298
+ * @returns {number} Spam score (0-100)
299
+ */
300
+ function getSpamScore(email) {
301
+ return isSpamEmail(email).score;
302
+ }
303
+
304
+ /**
305
+ * Check if domain is suspicious
306
+ * @param {string} domain - Domain to check
307
+ * @returns {boolean} True if suspicious
308
+ */
309
+ function isSuspiciousDomain(domain) {
310
+ if (!domain) return false;
311
+
312
+ // Check domain patterns
313
+ for (const pattern of SPAM_DOMAIN_PATTERNS) {
314
+ if (pattern.test(domain)) {
315
+ return true;
316
+ }
317
+ }
318
+
319
+ // Check domain keywords
320
+ for (const keyword of SPAM_DOMAIN_KEYWORDS) {
321
+ if (domain.includes(keyword)) {
322
+ return true;
323
+ }
324
+ }
325
+
326
+ return false;
327
+ }
328
+
329
+ module.exports = {
330
+ isSpamEmail,
331
+ isSpam,
332
+ getSpamScore,
333
+ isSuspiciousDomain,
334
+ SPAM_LOCAL_PATTERNS,
335
+ SPAM_DOMAIN_PATTERNS,
336
+ SPAM_DOMAIN_KEYWORDS,
337
+ SPAM_SCORES
338
+ };
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Temporary/Disposable Email Detector
3
+ * Detects if an email is from a known disposable email service
4
+ */
5
+
6
+ const { DISPOSABLE_DOMAINS, DISPOSABLE_PATTERNS } = require('../data/disposableDomains');
7
+ const { extractDomain } = require('./formatValidator');
8
+
9
+ /**
10
+ * Check if email domain is a known disposable email service
11
+ * @param {string} email - Email address to check
12
+ * @param {object} options - Options for checking
13
+ * @returns {object} Result with isTemporary flag and details
14
+ */
15
+ function isTemporaryEmail(email, options = {}) {
16
+ const {
17
+ customDomains = [],
18
+ customPatterns = [],
19
+ strictMode = false
20
+ } = options;
21
+
22
+ const result = {
23
+ isTemporary: false,
24
+ email: email,
25
+ domain: null,
26
+ reason: null,
27
+ confidence: 'none',
28
+ matchType: null
29
+ };
30
+
31
+ if (!email || typeof email !== 'string') {
32
+ result.reason = 'Invalid email provided';
33
+ return result;
34
+ }
35
+
36
+ const domain = extractDomain(email);
37
+ if (!domain) {
38
+ result.reason = 'Could not extract domain from email';
39
+ return result;
40
+ }
41
+
42
+ result.domain = domain;
43
+
44
+ // Check custom domains first
45
+ const customDomainSet = new Set(customDomains.map(d => d.toLowerCase()));
46
+ if (customDomainSet.has(domain)) {
47
+ result.isTemporary = true;
48
+ result.reason = 'Domain matches custom disposable domain list';
49
+ result.confidence = 'high';
50
+ result.matchType = 'custom_domain';
51
+ return result;
52
+ }
53
+
54
+ // Check known disposable domains
55
+ if (DISPOSABLE_DOMAINS.has(domain)) {
56
+ result.isTemporary = true;
57
+ result.reason = 'Known disposable email domain';
58
+ result.confidence = 'high';
59
+ result.matchType = 'known_domain';
60
+ return result;
61
+ }
62
+
63
+ // Check subdomain of known disposable domain
64
+ const domainParts = domain.split('.');
65
+ if (domainParts.length > 2) {
66
+ const parentDomain = domainParts.slice(-2).join('.');
67
+ if (DISPOSABLE_DOMAINS.has(parentDomain)) {
68
+ result.isTemporary = true;
69
+ result.reason = `Subdomain of known disposable email domain (${parentDomain})`;
70
+ result.confidence = 'high';
71
+ result.matchType = 'subdomain';
72
+ return result;
73
+ }
74
+ }
75
+
76
+ // Check custom patterns
77
+ for (const pattern of customPatterns) {
78
+ if (pattern instanceof RegExp && pattern.test(domain)) {
79
+ result.isTemporary = true;
80
+ result.reason = 'Domain matches custom disposable pattern';
81
+ result.confidence = 'medium';
82
+ result.matchType = 'custom_pattern';
83
+ return result;
84
+ }
85
+ }
86
+
87
+ // Check known disposable patterns
88
+ for (const pattern of DISPOSABLE_PATTERNS) {
89
+ if (pattern.test(domain)) {
90
+ result.isTemporary = true;
91
+ result.reason = `Domain matches disposable pattern: ${pattern.toString()}`;
92
+ result.confidence = 'medium';
93
+ result.matchType = 'pattern';
94
+ return result;
95
+ }
96
+ }
97
+
98
+ // Strict mode: Additional heuristic checks
99
+ if (strictMode) {
100
+ // Check for suspicious TLDs commonly used by disposable services
101
+ const suspiciousTlds = ['tk', 'ml', 'ga', 'cf', 'gq', 'cc', 'pw'];
102
+ const tld = domainParts[domainParts.length - 1];
103
+
104
+ if (suspiciousTlds.includes(tld)) {
105
+ // Check if domain looks auto-generated
106
+ const domainWithoutTld = domainParts.slice(0, -1).join('.');
107
+ if (/^[a-z]{3,6}\d{2,}$/i.test(domainWithoutTld) ||
108
+ /^\d+[a-z]+$/i.test(domainWithoutTld)) {
109
+ result.isTemporary = true;
110
+ result.reason = 'Domain appears auto-generated with suspicious TLD';
111
+ result.confidence = 'low';
112
+ result.matchType = 'heuristic';
113
+ return result;
114
+ }
115
+ }
116
+
117
+ // Check for very long random-looking domains
118
+ if (domain.length > 30 && /^[a-z0-9-]+\.[a-z]{2,4}$/i.test(domain)) {
119
+ const entropy = calculateEntropy(domainParts[0]);
120
+ if (entropy > 3.5) {
121
+ result.isTemporary = true;
122
+ result.reason = 'Domain appears randomly generated';
123
+ result.confidence = 'low';
124
+ result.matchType = 'heuristic';
125
+ return result;
126
+ }
127
+ }
128
+ }
129
+
130
+ return result;
131
+ }
132
+
133
+ /**
134
+ * Calculate Shannon entropy of a string
135
+ * Higher entropy = more random
136
+ * @param {string} str - String to calculate entropy for
137
+ * @returns {number} Entropy value
138
+ */
139
+ function calculateEntropy(str) {
140
+ const len = str.length;
141
+ const frequencies = {};
142
+
143
+ for (const char of str) {
144
+ frequencies[char] = (frequencies[char] || 0) + 1;
145
+ }
146
+
147
+ let entropy = 0;
148
+ for (const char in frequencies) {
149
+ const p = frequencies[char] / len;
150
+ entropy -= p * Math.log2(p);
151
+ }
152
+
153
+ return entropy;
154
+ }
155
+
156
+ /**
157
+ * Quick check - returns boolean only
158
+ * @param {string} email - Email to check
159
+ * @returns {boolean} True if temporary
160
+ */
161
+ function isTempEmail(email) {
162
+ return isTemporaryEmail(email).isTemporary;
163
+ }
164
+
165
+ /**
166
+ * Get the domain from an email and check if it's disposable
167
+ * @param {string} domain - Domain to check
168
+ * @returns {boolean} True if disposable
169
+ */
170
+ function isDisposableDomain(domain) {
171
+ if (!domain) return false;
172
+ const normalizedDomain = domain.toLowerCase().trim();
173
+ return DISPOSABLE_DOMAINS.has(normalizedDomain);
174
+ }
175
+
176
+ /**
177
+ * Add custom disposable domains to check
178
+ * @param {string[]} domains - Array of domains to add
179
+ */
180
+ function addDisposableDomains(domains) {
181
+ if (Array.isArray(domains)) {
182
+ domains.forEach(domain => {
183
+ if (typeof domain === 'string') {
184
+ DISPOSABLE_DOMAINS.add(domain.toLowerCase().trim());
185
+ }
186
+ });
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Get count of known disposable domains
192
+ * @returns {number} Count of disposable domains
193
+ */
194
+ function getDisposableDomainCount() {
195
+ return DISPOSABLE_DOMAINS.size;
196
+ }
197
+
198
+ module.exports = {
199
+ isTemporaryEmail,
200
+ isTempEmail,
201
+ isDisposableDomain,
202
+ addDisposableDomains,
203
+ getDisposableDomainCount,
204
+ calculateEntropy
205
+ };