245 lines
9.8 KiB
TypeScript
245 lines
9.8 KiB
TypeScript
/**
|
||
* Username blacklist validation
|
||
* Prevents registration with profanity and reserved words
|
||
*/
|
||
|
||
// Profanity and inappropriate words (Russian)
|
||
const RUSSIAN_BLACKLIST = [
|
||
// Мат - основные слова
|
||
'хуй', 'хуя', 'хуи', 'хуе', 'хую', 'хуем', 'хуев', 'хуйня', 'хуёвый', 'хуёво',
|
||
'xyй', 'xуй', 'хyй', 'xyи', 'xуи', 'хyи', 'хyйня', 'хyево',
|
||
'пизд', 'пизда', 'пиздец', 'пизды', 'пизде', 'пиздой', 'пиздюк', 'пиздёж',
|
||
'piзд', 'пiзд', 'пiзда', 'пизд0ц',
|
||
'ебал', 'ебать', 'ебет', 'ебут', 'ебал', 'ебала', 'ебало', 'еба', 'ебаный', 'ебанутый',
|
||
'ебош', 'ебло', 'ебля', 'выебон', 'заебал', 'заебись', 'наебал', 'поебать',
|
||
'бля', 'блять', 'блядь', 'бляд', 'блять', 'блят', 'блядина', 'блядский', 'блядство',
|
||
'бл@дь', 'бл@ть', 'блять', 'бл*дь',
|
||
'сука', 'суки', 'суку', 'сукой', 'сучка', 'сучара', 'cyка', 'cука', 'сyка', 'сyka',
|
||
'gандон', 'гандон', 'гондон', 'презерватив',
|
||
'пидор', 'пидар', 'педик', 'пидр', 'пидарас', 'пидрила', 'пидорас', 'педофил',
|
||
'пeдик', 'пидop', 'п1дор', 'пiдор',
|
||
'уебок', 'уебан', 'уебище', 'ублюдок', 'выродок',
|
||
'дебил', 'дебилоид', 'даун', 'долбоеб', 'еблан', 'ебланище',
|
||
'мудак', 'мудило', 'мудачье', 'мудачина', 'мудила', 'мудозвон',
|
||
'залупа', 'залупить', 'жопа', 'жопный', 'срака', 'сракотан',
|
||
'говно', 'говнюк', 'говноед', 'дерьмо', 'срать', 'высрал', 'обосрал',
|
||
'манда', 'мандавошка', 'влагалище', 'пилотка',
|
||
'шлюха', 'шалава', 'потаскуха', 'проститутка', 'блудница',
|
||
'гнида', 'падла', 'сволочь', 'мразь', 'тварь', 'скотина',
|
||
'чмо', 'чмошник', 'лох', 'лохматый', 'лошара', 'даун',
|
||
// Национальные оскорбления
|
||
'хохол', 'хохлы', 'хохлятина', 'хохляндия', 'укроп', 'укропы',
|
||
'москаль', 'москали', 'кацап', 'кацапы', 'кацапня',
|
||
'жид', 'жиды', 'жидовка', 'жидовня', 'жидяра', 'жидовский',
|
||
'еврей', 'еврейка', 'евреи', 'еврейский', 'жидовский', 'иудей',
|
||
'чурка', 'чурбан', 'чучмек', 'чучело', 'узбек', 'хачик', 'хач',
|
||
'азер', 'азиат', 'узкоглазый', 'косоглазый', 'раскосый',
|
||
'ниггер', 'негр', 'черножопый', 'чернож0пый', 'черномазый',
|
||
'пиндос', 'пиндосы', 'пиндосия', 'америкос', 'пендос',
|
||
// Нацистская тематика
|
||
'гитлер', 'гитлеровец', 'гиммлер', 'геббельс', 'геринг',
|
||
'нацист', 'нацисты', 'нацизм', 'наци', 'фашист', 'фашисты', 'фашизм',
|
||
'свастика', 'хайль', 'зиг', 'хайль', 'третийрейх', 'рейх',
|
||
'эсэс', 'сс', 'нсдап', 'вермахт',
|
||
'холокост', 'освенцим', 'концлагерь',
|
||
'ариец', 'арийцы', 'арийский', 'арийская',
|
||
// Экстремистские коды
|
||
'1488', '88', '14', 'авб', 'акб', 'ддт',
|
||
];
|
||
|
||
// Profanity and inappropriate words (English)
|
||
const ENGLISH_BLACKLIST = [
|
||
// Basic profanity
|
||
'fuck', 'fucked', 'fucker', 'fucking', 'fucks', 'fuk', 'fck', 'f**k', 'fock', 'fuk', 'phuck',
|
||
'motherfucker', 'motherfuck', 'mofo', 'mf',
|
||
'shit', 'shits', 'shitty', 'shitter', 'sh1t', 'sht', 'shite', 'bullshit', 'horseshit',
|
||
'bitch', 'bitches', 'bitching', 'b1tch', 'btch', 'biatch', 'beyotch',
|
||
'ass', 'asshole', 'arsehole', 'arse', 'a55', 'a55hole', 'asses', 'arsewipe',
|
||
'dick', 'dicks', 'dickhead', 'd1ck', 'dik', 'dickwad', 'dickface',
|
||
'cock', 'cocks', 'c0ck', 'cok', 'cocksucker', 'cocksuck',
|
||
'cunt', 'cunts', 'c**t', 'cnt', 'kunt',
|
||
'pussy', 'pussies', 'puss', 'pussycat',
|
||
'whore', 'slut', 'sluts', 'slutty', 'hoe', 'ho', 'tramp', 'prostitute',
|
||
'bastard', 'bastards', 'wanker', 'tosser', 'bellend', 'knobhead',
|
||
'damn', 'damned', 'goddamn', 'dammit', 'damnit',
|
||
'piss', 'pissed', 'pissing', 'pisser',
|
||
'twat', 'wank', 'bollocks', 'bugger',
|
||
// Racial slurs
|
||
'nigger', 'nigga', 'niggaz', 'niger', 'negro', 'n1gger', 'n1gga', 'nig', 'nigg',
|
||
'coon', 'jigaboo', 'spook', 'darkie', 'darky',
|
||
'chink', 'chinky', 'gook', 'zipperhead',
|
||
'spic', 'beaner', 'wetback', 'greaser',
|
||
'kike', 'yid', 'heeb', 'hymie', 'sheeny',
|
||
'towelhead', 'raghead', 'sandnigger', 'camel', 'muzzie',
|
||
'cracker', 'honky', 'whitey', 'paleface',
|
||
'paki', 'curry', 'dothead',
|
||
'abo', 'boong', 'coon',
|
||
// Homophobic slurs
|
||
'faggot', 'fag', 'fags', 'fagot', 'f4ggot', 'fagget', 'fagg0t',
|
||
'queer', 'dyke', 'homo', 'poof', 'fairy', 'fudgepacker',
|
||
'tranny', 'shemale', 'heshe',
|
||
// Mental health slurs
|
||
'retard', 'retarded', 'tard', 'retart', 'r3tard',
|
||
'mongoloid', 'mongo', 'downie', 'spastic', 'spaz',
|
||
'psycho', 'nutjob', 'lunatic', 'mental',
|
||
// Sexual content
|
||
'rape', 'raping', 'rapist', 'molest', 'pedophile', 'pedo',
|
||
'porn', 'porno', 'xxx', 'sex', 'anal', 'blowjob', 'handjob',
|
||
'masturbate', 'orgasm', 'cumshot', 'jizz', 'sperm',
|
||
// Nazi/Extremist terms
|
||
'nazi', 'nazy', 'n4zi', 'nаzi',
|
||
'hitler', 'adolf', 'fuhrer', 'fuehrer', 'gitler',
|
||
'heil', 'sieg', 'swastika', 'hakenkreuz',
|
||
'ss', 'gestapo', 'wehrmacht', 'nsdap',
|
||
'holocaust', 'auschwitz', 'konzentrationslager',
|
||
'aryan', 'kkk', 'klan', 'klux',
|
||
'skinhead', 'whitepower', 'whitesupremacy',
|
||
// Extremist codes
|
||
'1488', '14/88', '1488', 'fourteeneightyfour',
|
||
'acab', 'antifa', 'blm',
|
||
// Violence
|
||
'kill', 'murder', 'genocide', 'terrorist', 'bomb', 'shoot',
|
||
];
|
||
|
||
// 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',
|
||
'api', 'backup', 'cdn', 'database', 'email',
|
||
'ftp', 'mail', 'mysql', 'news', 'payment',
|
||
'postmaster', 'security', 'webmaster', 'www',
|
||
// Nazi leaders
|
||
'hitler', 'adolf', 'goebbels', 'himmler', 'goering', 'mengele',
|
||
'eichmann', 'bormann', 'hess', 'speer', 'heydrich',
|
||
// Extremist figures
|
||
'breivik', 'tarrant', 'roof', 'mcveigh',
|
||
];
|
||
|
||
// Common number patterns to avoid (like test123, user1, etc)
|
||
const GENERIC_PATTERNS = [
|
||
/^user\d+$/i,
|
||
/^test\d*$/i,
|
||
/^admin\d+$/i,
|
||
/^\d+$/, // Only numbers
|
||
// Extremist numeric codes
|
||
/1488/,
|
||
/88$/, // HH = Heil Hitler
|
||
/^88/,
|
||
/14$/,
|
||
/^14/,
|
||
// Nazi combinations
|
||
/(hitler|adolf|nazi|fuhrer).*(88|1488|14)/i,
|
||
/(88|1488|14).*(hitler|adolf|nazi|fuhrer)/i,
|
||
// Anti-semitic patterns
|
||
/(jew|jude|kike).*(kill|death|die|hate|burn)/i,
|
||
/(kill|death|die|hate|burn).*(jew|jude|kike)/i,
|
||
// Violence patterns
|
||
/(kill|murder|shoot).*(all|every)/i,
|
||
];
|
||
|
||
// 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;
|
||
}
|