feat: 初始化慧遇书院官网一期
This commit is contained in:
51
server/api/auth/login.post.ts
Normal file
51
server/api/auth/login.post.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
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 }
|
||||
})
|
||||
7
server/api/auth/logout.post.ts
Normal file
7
server/api/auth/logout.post.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { getAdminSession } from '../../utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getAdminSession(event)
|
||||
await session.clear()
|
||||
return { success: true }
|
||||
})
|
||||
3
server/api/auth/me.get.ts
Normal file
3
server/api/auth/me.get.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { requireAdmin } from '../../utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => ({ user: await requireAdmin(event) }))
|
||||
20
server/api/auth/password.put.ts
Normal file
20
server/api/auth/password.put.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { compareSync, hashSync } from 'bcryptjs'
|
||||
import { z } from 'zod'
|
||||
import { requireAdmin } from '../../utils/auth'
|
||||
import { useDatabase } from '../../utils/db'
|
||||
|
||||
const passwordSchema = z.object({
|
||||
currentPassword: z.string().min(8).max(200),
|
||||
newPassword: z.string().min(12).max(200).regex(/[a-z]/, '新密码需包含小写字母').regex(/[A-Z]/, '新密码需包含大写字母').regex(/\d/, '新密码需包含数字').regex(/[^A-Za-z0-9]/, '新密码需包含特殊字符'),
|
||||
}).refine(input => input.currentPassword !== input.newPassword, { message: '新密码不能与当前密码相同', path: ['newPassword'] })
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = await requireAdmin(event)
|
||||
const input = passwordSchema.parse(await readBody(event))
|
||||
const db = useDatabase()
|
||||
const row = db.prepare('SELECT password_hash FROM admin_users WHERE id = ?').get(user.id) as { password_hash: string } | undefined
|
||||
if (!row || !compareSync(input.currentPassword, row.password_hash)) throw createError({ statusCode: 400, statusMessage: '当前密码不正确' })
|
||||
db.prepare('UPDATE admin_users SET password_hash = ?, updated_at = ? WHERE id = ?').run(hashSync(input.newPassword, 12), new Date().toISOString(), user.id)
|
||||
writeAuditLog(event, { operatorId: user.id, action: 'password.change', entityType: 'admin_user', entityKey: String(user.id) })
|
||||
return { success: true }
|
||||
})
|
||||
Reference in New Issue
Block a user