Files
guanwang/server/api/auth/login.post.ts

52 lines
2.1 KiB
TypeScript

import { compareSync } from 'bcryptjs'
import { z } from 'zod'
import { getAdminSession } from '../../utils/auth'
import { useDatabase } from '../../utils/db'
import { writeAuditLog } from '../../utils/audit'
const loginSchema = z.object({
username: z.string().trim().min(1).max(80),
password: z.string().min(8).max(200),
})
export default defineEventHandler(async (event) => {
const body = loginSchema.parse(await readBody(event))
const db = useDatabase()
const user = db.prepare(`
SELECT id, username, password_hash, display_name, status, failed_login_count, locked_until
FROM admin_users WHERE username = ?
`).get(body.username) as {
id: number
username: string
password_hash: string
display_name: string
status: string
failed_login_count: number
locked_until: string | null
} | undefined
const locked = user?.locked_until && new Date(user.locked_until) > new Date()
const valid = user && user.status === 'active' && !locked && compareSync(body.password, user.password_hash)
if (!valid) {
if (user && user.status === 'active') {
const failures = user.failed_login_count + 1
const lockedUntil = failures >= 5 ? new Date(Date.now() + 10 * 60_000).toISOString() : null
db.prepare('UPDATE admin_users SET failed_login_count = ?, locked_until = ?, updated_at = ? WHERE id = ?')
.run(failures, lockedUntil, new Date().toISOString(), user.id)
}
writeAuditLog(event, { operatorId: user?.id, action: 'login_failed', entityType: 'admin_user', entityKey: body.username })
throw createError({ statusCode: 401, statusMessage: '账号、密码或验证码错误' })
}
const timestamp = new Date().toISOString()
db.prepare('UPDATE admin_users SET failed_login_count = 0, locked_until = NULL, last_login_at = ?, updated_at = ? WHERE id = ?')
.run(timestamp, timestamp, user.id)
const session = await getAdminSession(event)
await session.update({ user: { id: user.id, username: user.username, displayName: user.display_name } })
writeAuditLog(event, { operatorId: user.id, action: 'login_success', entityType: 'admin_user', entityKey: String(user.id) })
return { user: session.data.user }
})