Add password login, set-password, business images migration, and user serialization.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-23 12:10:30 +03:30
co-authored by Cursor
parent 715ca585a8
commit 97a9ee022f
11 changed files with 290 additions and 36 deletions
+35
View File
@@ -0,0 +1,35 @@
import { randomBytes, scrypt, timingSafeEqual } from 'crypto';
import { promisify } from 'util';
const scryptAsync = promisify(scrypt);
const KEY_LENGTH = 64;
export async function hashPassword(password: string): Promise<string> {
const salt = randomBytes(16).toString('hex');
const derived = (await scryptAsync(password, salt, KEY_LENGTH)) as Buffer;
return `scrypt:${salt}:${derived.toString('hex')}`;
}
export async function verifyPassword(
password: string,
stored: string,
): Promise<boolean> {
const parts = stored.split(':');
if (parts.length !== 3 || parts[0] !== 'scrypt') {
return false;
}
const [, salt, hashHex] = parts;
if (!salt || !hashHex) {
return false;
}
try {
const derived = (await scryptAsync(password, salt, KEY_LENGTH)) as Buffer;
const expected = Buffer.from(hashHex, 'hex');
if (derived.length !== expected.length) {
return false;
}
return timingSafeEqual(derived, expected);
} catch {
return false;
}
}
+25
View File
@@ -0,0 +1,25 @@
import { UserEntity } from '../../entities/user.entity';
export type PublicUser = {
id: string;
mobile: string;
fullName: string | null;
role: string;
isActive: boolean;
hasPassword: boolean;
createdAt?: Date;
updatedAt?: Date;
};
export function serializeUser(user: UserEntity): PublicUser {
return {
id: user.id,
mobile: user.mobile,
fullName: user.fullName,
role: user.role,
isActive: user.isActive,
hasPassword: Boolean(user.passwordHash),
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
}