Files
photo-editor/src/core/auth/password.js
Coder 401969cfdb
Some checks failed
test / test (push) Failing after 24s
v2: модульный монолит — очередь, персистентность, безопасность, SSE, тесты
Реализация целевого дизайна (docs/architecture/target-design.md):
- src/: модульная структура (config, logger, errors, http, core: auth/history/jobs/engines/comfy/codex/images)
- Очередь заданий: FIFO, кониурентность codex=1 / comfy-upscale=2, MAX_QUEUED_JOBS=5, JSONL-журнал и восстановление после рестарта (running → interrupted, upscale requeue)
- Персистентные сессии (sha256-хеши токенов), scrypt-хеш пароля, rate-limit входа, Origin-проверка, magic-byte валидация аплоадов
- Атомарная запись манифеста истории, каскадное удаление, URL с фактическим расширением + 302-алиас /image.jpg, миграция history/ → data/history
- Даунскейл sharp до 1024px перед Codex + масштабирование области референса
- SSE /api/events с фолбэком на polling, фронтенд переведён на ES-модули (public/js/)
- Тесты node:test + supertest (27), CI workflow test.yml, Docker/деплой: том kadr-data
- Документация: README, docs/architecture/
2026-08-27 12:52:27 +07:00

115 lines
3.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
const crypto = require("crypto");
const { promisify } = require("util");
const scryptAsync = promisify(crypto.scrypt);
const KEY_LEN = 64;
const SALT_LEN = 16;
const SCRYPT_OPTIONS = {
N: 16384,
r: 8,
p: 1,
maxmem: 32 * 1024 * 1024,
};
/**
* Хеширует пароль с помощью scrypt и случайной соли.
* @param {string} password
* @returns {Promise<string>} Формат: "saltHex:derivedKeyHex"
*/
async function hashPassword(password) {
if (typeof password !== "string") {
throw new TypeError("Пароль должен быть строкой");
}
const salt = crypto.randomBytes(SALT_LEN);
const derivedKey = await scryptAsync(password, salt, KEY_LEN, SCRYPT_OPTIONS);
return `${salt.toString("hex")}:${derivedKey.toString("hex")}`;
}
/**
* Синхронное хеширование (для инициализации при старте).
* @param {string} password
* @returns {string}
*/
function hashPasswordSync(password) {
if (typeof password !== "string") {
throw new TypeError("Пароль должен быть строкой");
}
const salt = crypto.randomBytes(SALT_LEN);
const derivedKey = crypto.scryptSync(password, salt, KEY_LEN, SCRYPT_OPTIONS);
return `${salt.toString("hex")}:${derivedKey.toString("hex")}`;
}
/**
* Проверяет пароль против сохранённого scrypt-хеша с защитой от тайминг-атак.
* @param {string} password
* @param {string} storedHash
* @returns {Promise<boolean>}
*/
async function verifyPassword(password, storedHash) {
if (typeof password !== "string" || typeof storedHash !== "string") {
return false;
}
const parts = storedHash.split(":");
if (parts.length !== 2) {
return false;
}
const [saltHex, keyHex] = parts;
try {
const salt = Buffer.from(saltHex, "hex");
const originalKey = Buffer.from(keyHex, "hex");
if (salt.length !== SALT_LEN || originalKey.length !== KEY_LEN) {
return false;
}
const derivedKey = await scryptAsync(password, salt, KEY_LEN, SCRYPT_OPTIONS);
return crypto.timingSafeEqual(originalKey, derivedKey);
} catch {
return false;
}
}
/**
* Синхронная проверка пароля.
* @param {string} password
* @param {string} storedHash
* @returns {boolean}
*/
function verifyPasswordSync(password, storedHash) {
if (typeof password !== "string" || typeof storedHash !== "string") {
return false;
}
const parts = storedHash.split(":");
if (parts.length !== 2) {
return false;
}
const [saltHex, keyHex] = parts;
try {
const salt = Buffer.from(saltHex, "hex");
const originalKey = Buffer.from(keyHex, "hex");
if (salt.length !== SALT_LEN || originalKey.length !== KEY_LEN) {
return false;
}
const derivedKey = crypto.scryptSync(password, salt, KEY_LEN, SCRYPT_OPTIONS);
return crypto.timingSafeEqual(originalKey, derivedKey);
} catch {
return false;
}
}
module.exports = {
hashPassword,
hashPasswordSync,
verifyPassword,
verifyPasswordSync,
};