feat: complete V1 phases 0-7
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import type { Prisma } from '../generated/prisma/client';
|
||||
import type { AuditResult } from '../generated/prisma/enums';
|
||||
import type { AuthContext, RequestContext } from '../common/request-context';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { SensitiveDataService } from '../security/sensitive-data.service';
|
||||
|
||||
const SENSITIVE_METADATA_KEY =
|
||||
/(password|token|secret|openid|unionid|mobile|phone|address|description|content)/i;
|
||||
|
||||
export interface AuditEvent {
|
||||
action: string;
|
||||
result: AuditResult;
|
||||
actor?: AuthContext;
|
||||
actorUserId?: bigint;
|
||||
actorRole?: string;
|
||||
resourceType?: string;
|
||||
resourcePublicId?: string;
|
||||
metadata?: Record<string, string | number | boolean | null>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuditService {
|
||||
constructor(
|
||||
@Inject(PrismaService) private readonly prisma: PrismaService,
|
||||
@Inject(SensitiveDataService) private readonly sensitiveData: SensitiveDataService,
|
||||
) {}
|
||||
|
||||
async record(
|
||||
request: RequestContext,
|
||||
event: AuditEvent,
|
||||
client: Prisma.TransactionClient | PrismaService = this.prisma,
|
||||
): Promise<void> {
|
||||
const metadata = this.safeMetadata(event.metadata);
|
||||
const userAgent =
|
||||
request
|
||||
.header('user-agent')
|
||||
?.replace(/[\r\n\t]/g, ' ')
|
||||
.slice(0, 255) || null;
|
||||
await client.auditLog.create({
|
||||
data: {
|
||||
requestId: request.requestId,
|
||||
actorUserId: event.actor?.userId ?? event.actorUserId ?? null,
|
||||
actorRole: event.actor?.roles[0] ?? event.actorRole ?? null,
|
||||
action: event.action,
|
||||
resourceType: event.resourceType ?? null,
|
||||
resourcePublicId: event.resourcePublicId ?? null,
|
||||
result: event.result,
|
||||
metadataJson: metadata ?? undefined,
|
||||
ipHash: request.ip ? this.sensitiveData.auditIpHash(request.ip) : null,
|
||||
userAgentSummary: userAgent,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
safeMetadata(metadata: AuditEvent['metadata']): Prisma.InputJsonObject | undefined {
|
||||
if (!metadata) return undefined;
|
||||
const entries = Object.entries(metadata).map(([key, value]) => {
|
||||
if (SENSITIVE_METADATA_KEY.test(key)) {
|
||||
throw new Error(`Sensitive audit metadata key is not allowed: ${key}`);
|
||||
}
|
||||
return [key, value] as const;
|
||||
});
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user