import crypto from 'node:crypto' import { prisma } from './prisma.js' import { sendLoginCodeEmail } from './email.js' export function normalizeEmail(email) { return String(email || '').trim().toLowerCase() } export function randomCode6() { return String(Math.floor(100000 + Math.random() * 900000)) } export function sha256(input) { return crypto.createHash('sha256').update(input).digest('hex') } export async function issueEmailCode({ email, purpose, userId = null }) { const code = randomCode6() const expiresAt = new Date(Date.now() + 10 * 60 * 1000) await prisma.authCode.create({ data: { email, purpose, userId, codeHash: sha256(`${email}:${purpose}:${code}:${userId ?? ''}`), expiresAt, }, }) await sendLoginCodeEmail({ to: email, code }) return code } function parseEnvBool(raw) { const v = String(raw ?? '').trim().toLowerCase() return v === 'true' || v === '1' || v === 'yes' } /** Тестовые стенды: принять код из переменной DEFAULT_CODE без записи в БД. */ export function isDefaultLoginCodeAccepted(codeInput) { if (!parseEnvBool(process.env.IS_DEFAULT_CODE_ENABLED)) return false const expected = String(process.env.DEFAULT_CODE ?? '').trim() if (!expected || expected.length < 4) return false return String(codeInput ?? '').trim() === expected } export async function verifyEmailCode({ email, purpose, code, userId = null }) { if (purpose === 'login' && isDefaultLoginCodeAccepted(code)) return true const now = new Date() const codeHash = sha256(`${email}:${purpose}:${code}:${userId ?? ''}`) const found = await prisma.authCode.findFirst({ where: { email, purpose, userId, codeHash, usedAt: null, expiresAt: { gt: now }, }, orderBy: { createdAt: 'desc' }, }) if (!found) return false await prisma.authCode.update({ where: { id: found.id }, data: { usedAt: now }, }) return true }