diff --git a/src/app/api/api-keys/[id]/route.ts b/src/app/api/api-keys/[id]/route.ts index 077b076..524389b 100644 --- a/src/app/api/api-keys/[id]/route.ts +++ b/src/app/api/api-keys/[id]/route.ts @@ -3,7 +3,7 @@ import { cookies } from 'next/headers' import { getDb } from '@/lib/db' import { verifyToken } from '@/lib/auth' import { checkPermission } from '@/lib/permissions' -import { writeAuditLog, getClientIP } from '@/lib/audit' +import { writeAuditLog, getClientIP, diffObjects } from '@/lib/audit' async function getSession() { const cookieStore = await cookies() @@ -20,7 +20,8 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id: } const { id } = await params - const existing = getDb().prepare('SELECT id FROM api_keys WHERE id = ?').get(id) + type ApiKeyRow = { id: number; name: string; permissions: string; expires_at: string | null; is_active: number } + const existing = getDb().prepare<[string | number], ApiKeyRow>('SELECT id, name, permissions, expires_at, is_active FROM api_keys WHERE id = ?').get(id) if (!existing) return NextResponse.json({ error: 'API Key 不存在' }, { status: 404 }) try { @@ -35,6 +36,20 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id: is_active !== undefined ? (is_active ? 1 : 0) : 1, id ) + + // 审计日志 + const changes = diffObjects(existing as unknown as Record, body) + if (Object.keys(changes).length > 0) { + writeAuditLog({ + userId: session.id, + action: 'update', + entityType: 'api_key', + entityId: Number(id), + details: { changes }, + ipAddress: getClientIP(request), + }) + } + return NextResponse.json({ success: true }) } catch (e) { const msg = e instanceof Error ? e.message : '更新失败' diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts index d759f29..c78ef6d 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/auth/logout/route.ts @@ -1,5 +1,6 @@ // GET + POST /api/auth/logout — 退出登录(清除 cookie + 302 跳转 /login) -import { NextResponse } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' +import { writeAuditLog, getClientIP } from '@/lib/audit' const cookieDomain = process.env.COOKIE_DOMAIN || '.tlyq.ai' @@ -22,4 +23,28 @@ function logoutResponse(): NextResponse { } export async function GET() { return logoutResponse() } -export async function POST() { return logoutResponse() } + +export async function POST(request: NextRequest) { + // 从 session cookie 提取用户名用于审计 + let username = 'anonymous' + try { + const token = + request.cookies.get('tlyq_session')?.value || + request.cookies.get('session_issue')?.value + if (token) { + const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()) + username = payload.username || 'unknown' + } + } catch { /* 解析失败,使用默认值 */ } + + writeAuditLog({ + userId: null, + username, + action: 'logout', + entityType: 'auth', + details: { message: '用户登出' }, + ipAddress: getClientIP(request), + }) + + return logoutResponse() +} diff --git a/src/app/api/reports/[id]/route.ts b/src/app/api/reports/[id]/route.ts index 28c1af1..6adc807 100644 --- a/src/app/api/reports/[id]/route.ts +++ b/src/app/api/reports/[id]/route.ts @@ -3,6 +3,7 @@ import { getDb } from '@/lib/db' import { initDatabase } from '@/lib/db-schema' import { getCurrentUser } from '@/lib/auth' import { hasPermission } from '@/lib/permissions' +import { writeAuditLog, getClientIP } from '@/lib/audit' import fs from 'fs' export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) { @@ -89,6 +90,16 @@ export async function DELETE(_request: NextRequest, { params }: { params: Promis // 删除数据库记录 db.prepare('DELETE FROM reports WHERE id = ?').run(id) + writeAuditLog({ + userId: user.id, + apiKeyId: null, + action: 'delete', + entityType: 'report', + entityId: Number(id), + details: { deleted: { id: Number(id), title: report.title, type: report.type, period_start: report.period_start, period_end: report.period_end, file_path: report.file_path } }, + ipAddress: getClientIP(_request), + }) + return NextResponse.json({ success: true }) } catch (e) { const msg = e instanceof Error ? e.message : '删除失败' diff --git a/src/app/api/reports/download/route.ts b/src/app/api/reports/download/route.ts index 1a86568..5200667 100644 --- a/src/app/api/reports/download/route.ts +++ b/src/app/api/reports/download/route.ts @@ -3,6 +3,7 @@ import { getDb } from '@/lib/db' import { initDatabase } from '@/lib/db-schema' import { getCurrentUser } from '@/lib/auth' import { hasPermission } from '@/lib/permissions' +import { writeAuditLog, getClientIP } from '@/lib/audit' import JSZip from 'jszip' import fs from 'fs' @@ -43,6 +44,16 @@ export async function POST(request: NextRequest) { const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' }) + writeAuditLog({ + userId: user.id, + apiKeyId: null, + action: 'export', + entityType: 'report', + entityId: null, + details: { exported_count: ids.length, ids }, + ipAddress: getClientIP(request), + }) + const d = new Date() const today = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}` const downloadName = `reports_${today}.zip`