Files
iistwin/server/utils/password.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

52 lines
1.6 KiB
TypeScript

import bcrypt from 'bcrypt';
import crypto from 'crypto';
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> {
if (password.length < 8) {
throw new Error('Пароль должен содержать минимум 8 символов');
}
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
if (!hasUpperCase || !hasLowerCase || !hasNumbers || !hasSpecialChar) {
throw new Error('Пароль должен содержать заглавные и строчные буквы, цифры и специальные символы');
}
return await bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
}
export function generateTempPassword(): string {
const lower = 'abcdefghijklmnopqrstuvwxyz';
const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const digits = '0123456789';
const special = '!@#$%^&*';
const charset = lower + upper + digits + special;
const pick = (set: string) => set[crypto.randomInt(set.length)];
const chars: string[] = [
pick(lower),
pick(upper),
pick(digits),
pick(special),
...Array.from({ length: 8 }, () => pick(charset)),
];
// Shuffle using Fisher-Yates with crypto.randomInt
for (let i = chars.length - 1; i > 0; i--) {
const j = crypto.randomInt(i + 1);
[chars[i], chars[j]] = [chars[j], chars[i]];
}
return chars.join('');
}