59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
import { hash, verify } from '@node-rs/argon2'
|
|
|
|
/**
|
|
* argon2 держит ~19 МиБ на вызов и занимает поток libuv; без ограничителя
|
|
* шквал логинов раздувает RSS. Семафор ограничивает число одновременных хешей.
|
|
*/
|
|
let limit = 2
|
|
let active = 0
|
|
const waiters: Array<() => void> = []
|
|
|
|
export function setPasswordConcurrency(n: number): void {
|
|
limit = Math.max(1, Math.floor(n))
|
|
}
|
|
|
|
export function passwordHashStats(): { active: number; waiting: number } {
|
|
return { active, waiting: waiters.length }
|
|
}
|
|
|
|
async function withSlot<T>(fn: () => Promise<T>): Promise<T> {
|
|
if (active >= limit) {
|
|
await new Promise<void>((resolve) => waiters.push(resolve))
|
|
} else {
|
|
active += 1
|
|
}
|
|
try {
|
|
return await fn()
|
|
} finally {
|
|
const next = waiters.shift()
|
|
if (next) next() // слот передаётся ожидающему без изменения active
|
|
else active -= 1
|
|
}
|
|
}
|
|
|
|
export function hashPassword(password: string): Promise<string> {
|
|
return withSlot(() => hash(password))
|
|
}
|
|
|
|
export function verifyPassword(
|
|
passwordHash: string,
|
|
password: string,
|
|
): Promise<boolean> {
|
|
return withSlot(() => verify(passwordHash, password))
|
|
}
|
|
|
|
let dummyHash: Promise<string> | null = null
|
|
|
|
/**
|
|
* Для неизвестного/отключённого пользователя выполняем настоящую проверку
|
|
* по фиктивному хешу: время ответа не выдаёт существование учётки.
|
|
*/
|
|
export async function verifyDummyPassword(password: string): Promise<void> {
|
|
dummyHash ??= hash('dummy-password-for-timing')
|
|
try {
|
|
await withSlot(async () => verify(await dummyHash!, password))
|
|
} catch {
|
|
/* результат не важен */
|
|
}
|
|
}
|