概览页增加后台登录记录

登录成功/失败写入 admin_login_logs,系统概览下方展示全部账号的时间、IP、设备等信息。
This commit is contained in:
yml2213
2026-07-10 21:55:32 +08:00
parent f6ea07912f
commit fa58381aa7
10 changed files with 609 additions and 11 deletions
@@ -13,6 +13,7 @@ import {
import { addHours, nowIso } from '../../utils/time.js'
import { createHttpError } from '../../utils/http.js'
import { normalizePage, normalizePageSize } from './admin-query-utils.js'
import { recordAdminLoginLog } from './admin-login-log-service.js'
type AdminUserRow = NonNullable<Awaited<ReturnType<typeof getAdminUserById>>>
@@ -58,13 +59,27 @@ export async function ensureAdminUsersBootstrapped(): Promise<void> {
}
}
export async function loginAdmin(username: unknown, password: unknown): Promise<JsonObject> {
export async function loginAdmin(
username: unknown,
password: unknown,
meta: {
ip?: string
userAgent?: string
location?: string
} = {},
): Promise<JsonObject> {
ensureAdminAuthConfigured()
const normalizedUsername = String(username || '').trim().toLowerCase()
const normalizedPassword = String(password || '').trim()
if (!normalizedUsername || !normalizedPassword) {
await recordAdminLoginLog({
username: normalizedUsername,
success: false,
failureReason: 'missing_credentials',
...pickLoginMeta(meta),
})
throw createHttpError('缺少后台账号或密码', {
statusCode: 400,
errorCode: 'admin_credentials_required',
@@ -73,13 +88,29 @@ export async function loginAdmin(username: unknown, password: unknown): Promise<
const user = await getAdminUserByUsername(normalizedUsername)
if (!user || user.status !== 'active' || !verifyAdminPassword(normalizedPassword, user.password_hash)) {
await recordAdminLoginLog({
userId: user ? Number(user.id) : null,
username: normalizedUsername,
role: user ? normalizeAdminRole(user.role) : '',
success: false,
failureReason: 'invalid_credentials',
...pickLoginMeta(meta),
})
throw createHttpError('账号或密码错误', {
statusCode: 401,
errorCode: 'admin_login_failed',
})
}
return createAdminSession(user)
const session = createAdminSession(user)
await recordAdminLoginLog({
userId: Number(user.id),
username: String(user.username || normalizedUsername),
role: normalizeAdminRole(user.role),
success: true,
...pickLoginMeta(meta),
})
return session
}
export async function verifyAdminSessionToken(token: unknown): Promise<AdminSession> {
@@ -523,3 +554,27 @@ function mapAdminUser(user: AdminUserRow): JsonObject {
updatedAt: user.updated_at,
}
}
function pickLoginMeta(meta: {
ip?: string
userAgent?: string
location?: string
} = {}) {
const result: {
ip?: string
userAgent?: string
location?: string
} = {}
if (typeof meta.ip === 'string' && meta.ip.trim()) {
result.ip = meta.ip.trim()
}
if (typeof meta.userAgent === 'string' && meta.userAgent.trim()) {
result.userAgent = meta.userAgent.trim()
}
if (typeof meta.location === 'string' && meta.location.trim()) {
result.location = meta.location.trim()
}
return result
}