Advanced registration validation: email domain check, username blacklist, MX verification

This commit is contained in:
Georgiy Syralev
2026-01-01 17:10:13 +03:00
parent bdb333958a
commit fc194dd582
4 changed files with 485 additions and 27 deletions

View File

@@ -0,0 +1,156 @@
/**
* Username blacklist validation
* Prevents registration with profanity and reserved words
*/
// Profanity and inappropriate words (Russian)
const RUSSIAN_BLACKLIST = [
'хуй', 'хуя', 'хуи', 'хуе', 'хую', 'xyй', 'xуй', 'хyй', 'xyи', 'xуи', 'хyи',
'пизд', 'пизда', 'пиздец', 'пизды', 'пизде', 'пиздой', 'piзд', 'пiзд',
'ебал', 'ебать', 'ебет', 'ебут', 'ебал', 'ебала', 'ебало', 'еба',
'бля', 'блять', 'блядь', 'бляд', 'блять', 'блят', 'блядина', 'блядский',
'сука', 'суки', 'суку', 'сукой', 'cyка', 'cука', 'са',
'гандон', 'гондон', 'пидор', 'пидар', 'педик', 'пидр', 'пидарас',
'уебок', 'уебан', 'дебил', 'мудак', 'долбоеб', 'еблан',
'залупа', 'жопа', 'срака', 'говно', 'дерьмо', 'срать',
'манда', 'мандавошка', 'влагалище',
];
// Profanity and inappropriate words (English)
const ENGLISH_BLACKLIST = [
'fuck', 'fucked', 'fucker', 'fucking', 'fucks', 'fuk', 'fck', 'f**k',
'shit', 'shits', 'shitty', 'shitter', 'sh1t', 'sht',
'bitch', 'bitches', 'bitching', 'b1tch', 'btch',
'ass', 'asshole', 'arsehole', 'arse', 'a55', 'a55hole',
'dick', 'dicks', 'dickhead', 'd1ck', 'dik',
'cock', 'cocks', 'c0ck', 'cok',
'cunt', 'cunts', 'c**t', 'cnt',
'pussy', 'pussies', 'puss',
'whore', 'slut', 'sluts', 'slutty',
'bastard', 'bastards', 'wanker', 'tosser',
'nigger', 'nigga', 'negro', 'n1gger', 'n1gga',
'faggot', 'fag', 'fags', 'fagot', 'f4ggot',
'retard', 'retarded', 'tard',
'rape', 'raping', 'rapist',
'nazi', 'hitler', 'heil',
];
// Reserved system usernames
const RESERVED_NAMES = [
'admin', 'administrator', 'root', 'system', 'support',
'moderator', 'mod', 'operator', 'staff', 'team',
'official', 'help', 'info', 'contact', 'service',
'user', 'username', 'test', 'demo', 'guest',
'null', 'undefined', 'unknown', 'anonymous', 'anon',
'ospab', 'ospabhost', 'hosting', 'server',
];
// Common number patterns to avoid (like test123, user1, etc)
const GENERIC_PATTERNS = [
/^user\d+$/i,
/^test\d*$/i,
/^admin\d+$/i,
/^\d+$/, // Only numbers
];
// Combine all blacklists
const COMBINED_BLACKLIST = new Set([
...RUSSIAN_BLACKLIST,
...ENGLISH_BLACKLIST,
...RESERVED_NAMES,
]);
export type UsernameValidationResult = {
isValid: boolean;
error?: string;
};
/**
* Check if username contains blacklisted words
*/
function containsBlacklistedWord(username: string): boolean {
const lower = username.toLowerCase();
// Check exact matches
if (COMBINED_BLACKLIST.has(lower)) {
return true;
}
// Check if username contains any blacklisted word
for (const word of COMBINED_BLACKLIST) {
if (lower.includes(word)) {
return true;
}
}
// Check generic patterns
for (const pattern of GENERIC_PATTERNS) {
if (pattern.test(username)) {
return true;
}
}
return false;
}
/**
* Validate username
*/
export function validateUsername(
username: string,
locale: 'ru' | 'en' = 'ru'
): UsernameValidationResult {
const trimmed = username.trim();
// Check length
if (trimmed.length < 3) {
return {
isValid: false,
error: locale === 'en'
? 'Username must be at least 3 characters long'
: 'Имя пользователя должно быть не менее 3 символов',
};
}
if (trimmed.length > 20) {
return {
isValid: false,
error: locale === 'en'
? 'Username must be no more than 20 characters'
: 'Имя пользователя должно быть не более 20 символов',
};
}
// Check format (alphanumeric, underscore, hyphen)
if (!/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
return {
isValid: false,
error: locale === 'en'
? 'Username can only contain letters, numbers, underscore and hyphen'
: 'Имя пользователя может содержать только буквы, цифры, подчёркивание и дефис',
};
}
// Check blacklist
if (containsBlacklistedWord(trimmed)) {
return {
isValid: false,
error: locale === 'en'
? 'This username is not allowed. Please choose another one.'
: 'Это имя пользователя недопустимо. Пожалуйста, выберите другое.',
};
}
return { isValid: true };
}
/**
* Get username validation error message
*/
export function getUsernameError(
username: string,
locale: 'ru' | 'en' = 'ru'
): string | null {
const result = validateUsername(username, locale);
return result.error || null;
}