73 lines
2.0 KiB
JavaScript
73 lines
2.0 KiB
JavaScript
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
|
|
}
|
|
|
|
|