forked from Chatbot/v3-api
first commit v3 initialiazed a temporary repository for darmanet client
This commit is contained in:
201
src/common/services/audit-log.service.ts
Normal file
201
src/common/services/audit-log.service.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Model } from 'mongoose';
|
||||
import { AuditLogModel } from 'src/database/model/audit-log.model';
|
||||
import { Socket } from 'socket.io';
|
||||
|
||||
export interface AuditLogData {
|
||||
userId?: string;
|
||||
action: string;
|
||||
resource?: string;
|
||||
resourceId?: string;
|
||||
oldValues?: Record<string, any>;
|
||||
newValues?: Record<string, any>;
|
||||
changes?: Record<string, { from: any; to: any }>;
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
method?: string;
|
||||
endpoint?: string;
|
||||
statusCode?: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuditLogService {
|
||||
constructor(
|
||||
@InjectModel(AuditLogModel.name)
|
||||
private readonly auditLogModel: Model<AuditLogModel>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Extract IP address and user agent from Socket.io client
|
||||
*/
|
||||
private extractClientInfo(client: Socket): { ipAddress?: string; userAgent?: string } {
|
||||
const ipAddress =
|
||||
(client.handshake.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||
|
||||
(client.handshake.headers['x-real-ip'] as string) ||
|
||||
client.handshake.address ||
|
||||
client.conn?.remoteAddress ||
|
||||
undefined;
|
||||
|
||||
const userAgent =
|
||||
(client.handshake.headers['user-agent'] as string) || undefined;
|
||||
|
||||
return { ipAddress, userAgent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate changes between old and new values
|
||||
*/
|
||||
private calculateChanges(
|
||||
oldValues?: Record<string, any>,
|
||||
newValues?: Record<string, any>,
|
||||
): Record<string, { from: any; to: any }> | undefined {
|
||||
if (!oldValues || !newValues) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const changes: Record<string, { from: any; to: any }> = {};
|
||||
const allKeys = new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
|
||||
|
||||
for (const key of allKeys) {
|
||||
const oldVal = oldValues[key];
|
||||
const newVal = newValues[key];
|
||||
|
||||
// Only include if values actually changed
|
||||
if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
|
||||
changes[key] = { from: oldVal, to: newVal };
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(changes).length > 0 ? changes : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an audit event
|
||||
*/
|
||||
async log(data: AuditLogData, client?: Socket): Promise<void> {
|
||||
try {
|
||||
// Extract client info if socket is provided
|
||||
const clientInfo = client ? this.extractClientInfo(client) : {};
|
||||
|
||||
// Calculate changes if both old and new values are provided
|
||||
const changes = data.changes || this.calculateChanges(data.oldValues, data.newValues);
|
||||
|
||||
const auditLog = new this.auditLogModel({
|
||||
userId: data.userId ? (data.userId as any) : undefined,
|
||||
action: data.action,
|
||||
resource: data.resource,
|
||||
resourceId: data.resourceId,
|
||||
oldValues: data.oldValues,
|
||||
newValues: data.newValues,
|
||||
changes,
|
||||
ipAddress: data.ipAddress || clientInfo.ipAddress,
|
||||
userAgent: data.userAgent || clientInfo.userAgent,
|
||||
method: data.method,
|
||||
endpoint: data.endpoint,
|
||||
statusCode: data.statusCode,
|
||||
metadata: data.metadata,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
await auditLog.save();
|
||||
} catch (error) {
|
||||
// Don't throw errors - audit logging should not break the main flow
|
||||
console.error('Failed to save audit log:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a WebSocket event
|
||||
*/
|
||||
async logWebSocketEvent(
|
||||
action: string,
|
||||
client: Socket,
|
||||
options: {
|
||||
userId?: string;
|
||||
resource?: string;
|
||||
resourceId?: string;
|
||||
oldValues?: Record<string, any>;
|
||||
newValues?: Record<string, any>;
|
||||
statusCode?: number;
|
||||
metadata?: Record<string, any>;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const clientInfo = this.extractClientInfo(client);
|
||||
|
||||
await this.log(
|
||||
{
|
||||
action,
|
||||
resource: options.resource,
|
||||
resourceId: options.resourceId,
|
||||
oldValues: options.oldValues,
|
||||
newValues: options.newValues,
|
||||
ipAddress: clientInfo.ipAddress,
|
||||
userAgent: clientInfo.userAgent,
|
||||
method: action, // For WebSocket, method is the event name
|
||||
endpoint: options.resourceId ? `room:${options.resourceId}` : undefined,
|
||||
statusCode: options.statusCode || 200,
|
||||
metadata: options.metadata,
|
||||
userId: options.userId,
|
||||
},
|
||||
client,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an HTTP request (for future use)
|
||||
*/
|
||||
async logHttpRequest(
|
||||
action: string,
|
||||
req: any,
|
||||
options: {
|
||||
userId?: string;
|
||||
resource?: string;
|
||||
resourceId?: string;
|
||||
oldValues?: Record<string, any>;
|
||||
newValues?: Record<string, any>;
|
||||
statusCode?: number;
|
||||
metadata?: Record<string, any>;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const ipAddress =
|
||||
req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
||||
req.headers['x-real-ip'] ||
|
||||
req.ip ||
|
||||
req.connection?.remoteAddress ||
|
||||
undefined;
|
||||
|
||||
const userAgent = req.headers['user-agent'] || undefined;
|
||||
|
||||
await this.log({
|
||||
action,
|
||||
resource: options.resource,
|
||||
resourceId: options.resourceId,
|
||||
oldValues: options.oldValues,
|
||||
newValues: options.newValues,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
method: req.method,
|
||||
endpoint: req.path || req.url,
|
||||
statusCode: options.statusCode || 200,
|
||||
metadata: options.metadata,
|
||||
userId: options.userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user