Audit Logging (story 4-6): - Nouveau modèle AuditLog (userId, action, resource, metadata, ip, createdAt) - Migration 20260529143000_add_audit_log appliquée - lib/audit-log.ts : logAuditEvent (fire-and-forget) + logAuditEventAsync + getClientIp - auth.ts : LOG LOGIN / LOGOUT / USER_CREATED sur chaque event NextAuth - /api/chat : log AI_REQUEST avec tokens + byok flag dans onFinish - /api/agents/run-for-note : log AI_REQUEST avec featureKey + noteId Zero-data-retention (story 4-5): - OpenAI provider : header OpenAI-No-Training: 1 - Anthropic provider : header Anthropic-No-Train: 1 - DeepSeek provider : header X-No-Train: 1 sprint-status: 4-5 et 4-6 → done Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
import prisma from '@/lib/prisma'
|
|
|
|
export type AuditAction =
|
|
| 'LOGIN'
|
|
| 'LOGOUT'
|
|
| 'USER_CREATED'
|
|
| 'AI_REQUEST'
|
|
| 'DATA_EXPORT'
|
|
| 'ACCOUNT_DELETED'
|
|
| 'PASSWORD_RESET'
|
|
| 'AI_CONSENT_GRANTED'
|
|
| 'AI_CONSENT_REVOKED'
|
|
|
|
export interface AuditLogParams {
|
|
userId?: string | null
|
|
action: AuditAction
|
|
resource?: string
|
|
metadata?: Record<string, unknown>
|
|
ip?: string
|
|
userAgent?: string
|
|
}
|
|
|
|
/** Fire-and-forget — never throws, safe to call anywhere */
|
|
export function logAuditEvent(params: AuditLogParams): void {
|
|
prisma.auditLog
|
|
.create({
|
|
data: {
|
|
userId: params.userId ?? null,
|
|
action: params.action,
|
|
resource: params.resource ?? null,
|
|
metadata: params.metadata ? (params.metadata as any) : undefined,
|
|
ip: params.ip ?? null,
|
|
userAgent: params.userAgent ?? null,
|
|
},
|
|
})
|
|
.catch((err: unknown) => console.error('[audit-log] write failed:', err))
|
|
}
|
|
|
|
/** Awaitable version for cases where ordering matters */
|
|
export async function logAuditEventAsync(params: AuditLogParams): Promise<void> {
|
|
try {
|
|
await prisma.auditLog.create({
|
|
data: {
|
|
userId: params.userId ?? null,
|
|
action: params.action,
|
|
resource: params.resource ?? null,
|
|
metadata: params.metadata ? (params.metadata as any) : undefined,
|
|
ip: params.ip ?? null,
|
|
userAgent: params.userAgent ?? null,
|
|
},
|
|
})
|
|
} catch (err) {
|
|
console.error('[audit-log] write failed:', err)
|
|
}
|
|
}
|
|
|
|
/** Extract IP from request headers (works behind Cloudflare / nginx) */
|
|
export function getClientIp(request: Request): string | undefined {
|
|
const cf = request.headers.get('cf-connecting-ip')
|
|
if (cf) return cf
|
|
const xff = request.headers.get('x-forwarded-for')
|
|
if (xff) return xff.split(',')[0].trim()
|
|
return undefined
|
|
}
|