3v324v23's picture
初始化:2FA/TOTP 全闭环(中文)
1034ef4
Raw
History Blame Contribute Delete
2.36 kB
import crypto from 'node:crypto'
export function normalizeEmail(email: string) {
return email.trim().toLowerCase()
}
export function isValidEmail(email: string) {
const e = normalizeEmail(email)
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)
}
export function isStrongPassword(password: string) {
if (password.length < 8) return false
const hasLetter = /[a-zA-Z]/.test(password)
const hasNumber = /\d/.test(password)
return hasLetter && hasNumber
}
export async function hashPassword(password: string) {
const salt = crypto.randomBytes(16)
const key = await new Promise<Buffer>((resolve, reject) => {
crypto.scrypt(password, salt, 64, { N: 16384, r: 8, p: 1 }, (err, derivedKey) => {
if (err) reject(err)
else resolve(derivedKey as Buffer)
})
})
return `scrypt$${salt.toString('hex')}$${key.toString('hex')}`
}
export async function verifyPassword(password: string, stored: string) {
const parts = stored.split('$')
if (parts.length !== 3 || parts[0] !== 'scrypt') return false
const salt = Buffer.from(parts[1], 'hex')
const expected = Buffer.from(parts[2], 'hex')
const key = await new Promise<Buffer>((resolve, reject) => {
crypto.scrypt(password, salt, 64, { N: 16384, r: 8, p: 1 }, (err, derivedKey) => {
if (err) reject(err)
else resolve(derivedKey as Buffer)
})
})
if (key.length !== expected.length) return false
return crypto.timingSafeEqual(key, expected)
}
export function hashBackupCode(code: string, saltHex?: string) {
const salt = saltHex ? Buffer.from(saltHex, 'hex') : crypto.randomBytes(16)
const h = crypto.createHash('sha256').update(salt).update(code).digest('hex')
return `sha256$${salt.toString('hex')}$${h}`
}
export function verifyBackupCode(code: string, stored: string) {
const parts = stored.split('$')
if (parts.length !== 3 || parts[0] !== 'sha256') return false
const saltHex = parts[1]
const expected = parts[2]
const actual = hashBackupCode(code, saltHex).split('$')[2]
return crypto.timingSafeEqual(Buffer.from(actual), Buffer.from(expected))
}
export function generateBackupCodes(count: number) {
const codes: string[] = []
for (let i = 0; i < count; i++) {
const raw = crypto.randomBytes(5).toString('hex').slice(0, 10).toUpperCase()
codes.push(`${raw.slice(0, 5)}-${raw.slice(5)}`)
}
return codes
}