eduverse-backend / src /modules /security /interceptors /audit-log.interceptor.ts
amirhamdi's picture
before testing endpoints
990c0e8
Raw
History Blame Contribute Delete
2.21 kB
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
import { AuditService } from '../services/audit.service';
@Injectable()
export class AuditLogInterceptor implements NestInterceptor {
private readonly logger = new Logger(AuditLogInterceptor.name);
constructor(private readonly auditService: AuditService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const method = request.method;
// Only log write operations
if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
return next.handle();
}
const user = request.user;
const url = request.url;
const routePath = request.route?.path || url;
// Derive entity type from URL path
const pathParts = url.replace('/api/', '').split('/').filter(Boolean);
const entityType = pathParts[0] || 'unknown';
// Derive action type from HTTP method
const actionMap: Record<string, string> = {
POST: 'CREATE',
PUT: 'UPDATE',
PATCH: 'UPDATE',
DELETE: 'DELETE',
};
const actionType = actionMap[method] || method;
// Try to extract entity ID from URL params
const paramId = request.params?.id || request.params?.courseId || request.params?.userId || null;
return next.handle().pipe(
tap({
next: () => {
// Fire and forget — don't block the response
this.auditService
.createAuditLog({
userId: user?.userId || user?.id || null,
actionType,
entityType,
entityId: paramId ? Number(paramId) : undefined,
newValues: method !== 'DELETE' ? JSON.stringify(request.body) : undefined,
ipAddress: request.ip || request.connection?.remoteAddress,
userAgent: request.headers?.['user-agent'],
description: `${actionType} ${entityType} via ${method} ${url}`,
})
.catch((err) => {
this.logger.warn(`Failed to create audit log: ${err.message}`);
});
},
}),
);
}
}