feat(mining-admin): 审计日志记录登录失败事件
- audit_logs.adminId 改为可选字段,支持用户名不存在时的失败记录 - 登录失败时记录 LOGIN_FAILED 操作,resourceId 存储尝试的用户名,newValue 记录失败原因 - 前端审计日志页新增 LOGIN_FAILED 标签(红色)及筛选选项 - 新增 migration: 20260310_audit_log_nullable_admin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e7d852e25e
commit
ad1c889848
|
|
@ -0,0 +1,2 @@
|
|||
-- Make adminId nullable in audit_logs to support LOGIN_FAILED records without a valid admin account
|
||||
ALTER TABLE "audit_logs" ALTER COLUMN "admin_id" DROP NOT NULL;
|
||||
|
|
@ -85,8 +85,8 @@ model InitializationRecord {
|
|||
|
||||
model AuditLog {
|
||||
id String @id @default(uuid())
|
||||
adminId String
|
||||
action String // CREATE, UPDATE, DELETE, LOGIN, LOGOUT, INIT
|
||||
adminId String?
|
||||
action String // CREATE, UPDATE, DELETE, LOGIN, LOGIN_FAILED, LOGOUT, INIT
|
||||
resource String // CONFIG, USER, SYSTEM_ACCOUNT, MINING
|
||||
resourceId String?
|
||||
oldValue Json?
|
||||
|
|
@ -95,7 +95,7 @@ model AuditLog {
|
|||
userAgent String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
admin AdminUser @relation(fields: [adminId], references: [id])
|
||||
admin AdminUser? @relation(fields: [adminId], references: [id])
|
||||
|
||||
@@index([adminId])
|
||||
@@index([action])
|
||||
|
|
|
|||
|
|
@ -23,11 +23,17 @@ export class AuthService {
|
|||
async login(username: string, password: string, ipAddress?: string, userAgent?: string): Promise<{ token: string; admin: any }> {
|
||||
const admin = await this.prisma.adminUser.findUnique({ where: { username } });
|
||||
if (!admin || admin.status !== 'ACTIVE') {
|
||||
await this.prisma.auditLog.create({
|
||||
data: { action: 'LOGIN_FAILED', resource: 'AUTH', resourceId: username, newValue: { reason: admin ? 'account_disabled' : 'user_not_found' }, ipAddress, userAgent },
|
||||
});
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
const isValid = await bcrypt.compare(password, admin.password);
|
||||
if (!isValid) {
|
||||
await this.prisma.auditLog.create({
|
||||
data: { adminId: admin.id, action: 'LOGIN_FAILED', resource: 'AUTH', resourceId: username, newValue: { reason: 'wrong_password' }, ipAddress, userAgent },
|
||||
});
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,9 @@ const actionLabels: Record<string, { label: string; className: string }> = {
|
|||
CREATE: { label: '创建', className: 'bg-green-100 text-green-700' },
|
||||
UPDATE: { label: '更新', className: 'bg-blue-100 text-blue-700' },
|
||||
DELETE: { label: '删除', className: 'bg-red-100 text-red-700' },
|
||||
LOGIN: { label: '登录', className: 'bg-purple-100 text-purple-700' },
|
||||
LOGOUT: { label: '登出', className: 'bg-gray-100 text-gray-600' },
|
||||
LOGIN: { label: '登录', className: 'bg-purple-100 text-purple-700' },
|
||||
LOGIN_FAILED: { label: '登录失败', className: 'bg-red-100 text-red-700' },
|
||||
LOGOUT: { label: '登出', className: 'bg-gray-100 text-gray-600' },
|
||||
ENABLE: { label: '启用', className: 'bg-emerald-100 text-emerald-700' },
|
||||
DISABLE: { label: '禁用', className: 'bg-orange-100 text-orange-700' },
|
||||
UNLOCK: { label: '解锁', className: 'bg-yellow-100 text-yellow-700' },
|
||||
|
|
@ -93,6 +94,7 @@ export default function AuditLogsPage() {
|
|||
<SelectContent>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="LOGIN">登录</SelectItem>
|
||||
<SelectItem value="LOGIN_FAILED">登录失败</SelectItem>
|
||||
<SelectItem value="LOGOUT">登出</SelectItem>
|
||||
<SelectItem value="CREATE">创建</SelectItem>
|
||||
<SelectItem value="UPDATE">更新</SelectItem>
|
||||
|
|
|
|||
Loading…
Reference in New Issue