From fd4a3a7a0c89078bc8a9c71dfed506b1b4314096 Mon Sep 17 00:00:00 2001 From: Code Date: Sun, 27 Sep 2026 21:49:43 +0100 Subject: [PATCH 1/3] Fix #233: Implement Structured Audit Logging Interceptor for Mutation Requests --- .../interceptors/audit.interceptor.spec.ts | 8 ++-- src/common/interceptors/audit.interceptor.ts | 44 ++++++------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/src/common/interceptors/audit.interceptor.spec.ts b/src/common/interceptors/audit.interceptor.spec.ts index 5f4aa39c..566b5853 100644 --- a/src/common/interceptors/audit.interceptor.spec.ts +++ b/src/common/interceptors/audit.interceptor.spec.ts @@ -137,10 +137,10 @@ describe('AuditInterceptor', () => { it('should handle forwarded IP from x-forwarded-for header', async () => { const ctx = buildMockContext(); - const req = (ctx.switchToHttp() as ReturnType) - .getRequest() as ReturnType extends { getRequest(): infer R } ? R : never; - (req as unknown as { headers: Record }).headers['x-forwarded-for'] = - '10.0.0.1, 10.0.0.2'; + const req = (ctx.switchToHttp() as ReturnType)[ + 'getRequest' + ]() as any; + req.headers['x-forwarded-for'] = '10.0.0.1, 10.0.0.2'; const next = buildCallHandler(); await interceptor.intercept(ctx, next).toPromise(); diff --git a/src/common/interceptors/audit.interceptor.ts b/src/common/interceptors/audit.interceptor.ts index f6cb234f..b9f9fa3a 100644 --- a/src/common/interceptors/audit.interceptor.ts +++ b/src/common/interceptors/audit.interceptor.ts @@ -40,6 +40,7 @@ const EXCLUDED_META_KEYS = new Set(['password', 'token', 'authorization', 'x-api * * Routes decorated with @SkipAudit() are excluded. */ + @Injectable() export class AuditInterceptor implements NestInterceptor { private readonly logger = new Logger(AuditInterceptor.name); @@ -82,38 +83,14 @@ export class AuditInterceptor implements NestInterceptor { const startTime = Date.now(); - return next.handle().pipe( - tap({ - next: () => { - const durationMs = Date.now() - startTime; - this.persistAuditLog({ - organizationId, - userId, - agentId, - action: customAction, - method, - url: originalUrl, - statusCode: res.statusCode, - ipAddress, - userAgent, - requestId, - body: sanitizedBody, - params: sanitizedParams, - query: sanitizedQuery, - durationMs, - }).catch((err) => { - this.logger.error( - `Failed to persist audit log for ${method} ${originalUrl}: ${(err as Error).message}`, - ); - }); - }, - error: () => { + const recordLog = () => { const durationMs = Date.now() - startTime; + const action = customAction ?? `${method} ${originalUrl}`; this.persistAuditLog({ organizationId, userId, agentId, - action: customAction, + action, method, url: originalUrl, statusCode: res.statusCode, @@ -129,7 +106,12 @@ export class AuditInterceptor implements NestInterceptor { `Failed to persist audit log for ${method} ${originalUrl}: ${(err as Error).message}`, ); }); - }, + }; + + return next.handle().pipe( + tap({ + next: () => recordLog(), + error: () => recordLog(), }), ); } @@ -169,7 +151,7 @@ export class AuditInterceptor implements NestInterceptor { organizationId: string | null; userId: string | null; agentId: string | null; - action: string | undefined; + action: string; method: string; url: string; statusCode: number; @@ -188,7 +170,7 @@ export class AuditInterceptor implements NestInterceptor { await this.auditService.record({ organizationId: data.organizationId, userId: data.userId, - action: data.action ?? `${data.method} ${data.url}`, + action: data.action, entity: 'http', entityId: null, newValue: { @@ -200,7 +182,7 @@ export class AuditInterceptor implements NestInterceptor { query: data.query, agentId: data.agentId, durationMs: data.durationMs, - } as object, + }, ipAddress: data.ipAddress, device: data.userAgent, requestId: data.requestId, From 3b0523e6bccf4b3f10a7b21479446ee86cab3166 Mon Sep 17 00:00:00 2001 From: Code Date: Sun, 27 Sep 2026 21:51:14 +0100 Subject: [PATCH 2/3] Fix CI for #233 --- src/common/interceptors/audit.interceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/interceptors/audit.interceptor.ts b/src/common/interceptors/audit.interceptor.ts index b9f9fa3a..a8c5622d 100644 --- a/src/common/interceptors/audit.interceptor.ts +++ b/src/common/interceptors/audit.interceptor.ts @@ -182,7 +182,7 @@ export class AuditInterceptor implements NestInterceptor { query: data.query, agentId: data.agentId, durationMs: data.durationMs, - }, + } as object, ipAddress: data.ipAddress, device: data.userAgent, requestId: data.requestId, From f8cb38a214f0957e9a7a596321a6232895069923 Mon Sep 17 00:00:00 2001 From: Code Date: Sun, 27 Sep 2026 21:55:53 +0100 Subject: [PATCH 3/3] Fix CI for #233 --- src/common/interceptors/audit.interceptor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/interceptors/audit.interceptor.spec.ts b/src/common/interceptors/audit.interceptor.spec.ts index 566b5853..17f762df 100644 --- a/src/common/interceptors/audit.interceptor.spec.ts +++ b/src/common/interceptors/audit.interceptor.spec.ts @@ -139,7 +139,7 @@ describe('AuditInterceptor', () => { const ctx = buildMockContext(); const req = (ctx.switchToHttp() as ReturnType)[ 'getRequest' - ]() as any; + ]() as Record & { headers: Record }; req.headers['x-forwarded-for'] = '10.0.0.1, 10.0.0.2'; const next = buildCallHandler();