mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-02 21:19:05 +00:00
308 lines
7.0 KiB
TypeScript
308 lines
7.0 KiB
TypeScript
/**
|
|
* Audit Logging Helper
|
|
*
|
|
* Provides easy-to-use functions for logging audit events from API endpoints.
|
|
* This is an Enterprise-only feature.
|
|
*/
|
|
|
|
import type { RequestEvent } from '@sveltejs/kit';
|
|
import { isEnterprise } from './license';
|
|
import { logAuditEvent, type AuditAction, type AuditEntityType, type AuditLogCreateData } from './db';
|
|
import { authorize } from './authorize';
|
|
|
|
export interface AuditContext {
|
|
userId?: number | null;
|
|
username: string;
|
|
ipAddress?: string | null;
|
|
userAgent?: string | null;
|
|
}
|
|
|
|
/**
|
|
* Extract audit context from a request event
|
|
*/
|
|
export async function getAuditContext(event: RequestEvent): Promise<AuditContext> {
|
|
const auth = await authorize(event.cookies);
|
|
|
|
// Get IP address from various headers (proxied requests)
|
|
const forwardedFor = event.request.headers.get('x-forwarded-for');
|
|
const realIp = event.request.headers.get('x-real-ip');
|
|
let ipAddress = forwardedFor?.split(',')[0]?.trim() || realIp || event.getClientAddress?.() || null;
|
|
|
|
// Convert IPv6 loopback to more readable format
|
|
if (ipAddress === '::1' || ipAddress === '::ffff:127.0.0.1') {
|
|
ipAddress = '127.0.0.1';
|
|
} else if (ipAddress?.startsWith('::ffff:')) {
|
|
// Strip IPv6 prefix from IPv4-mapped addresses
|
|
ipAddress = ipAddress.substring(7);
|
|
}
|
|
|
|
// Get user agent
|
|
const userAgent = event.request.headers.get('user-agent') || null;
|
|
|
|
return {
|
|
userId: auth.user?.id ?? null,
|
|
username: auth.user?.username ?? 'anonymous',
|
|
ipAddress,
|
|
userAgent
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Log an audit event (only logs if Enterprise license is active)
|
|
*/
|
|
export async function audit(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
entityType: AuditEntityType,
|
|
options: {
|
|
entityId?: string | null;
|
|
entityName?: string | null;
|
|
environmentId?: number | null;
|
|
description?: string | null;
|
|
details?: any | null;
|
|
} = {}
|
|
): Promise<void> {
|
|
// Only log if enterprise
|
|
if (!(await isEnterprise())) return;
|
|
|
|
const ctx = await getAuditContext(event);
|
|
|
|
const data: AuditLogCreateData = {
|
|
userId: ctx.userId,
|
|
username: ctx.username,
|
|
action,
|
|
entityType: entityType,
|
|
entityId: options.entityId ?? null,
|
|
entityName: options.entityName ?? null,
|
|
environmentId: options.environmentId ?? null,
|
|
description: options.description ?? null,
|
|
details: options.details ?? null,
|
|
ipAddress: ctx.ipAddress ?? null,
|
|
userAgent: ctx.userAgent ?? null
|
|
};
|
|
|
|
try {
|
|
await logAuditEvent(data);
|
|
} catch (error) {
|
|
// Don't let audit logging errors break the main operation
|
|
console.error('Failed to log audit event:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper for container actions
|
|
*/
|
|
export async function auditContainer(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
containerId: string,
|
|
containerName: string,
|
|
environmentId?: number | null,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'container', {
|
|
entityId: containerId,
|
|
entityName: containerName,
|
|
environmentId,
|
|
description: `Container ${containerName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for image actions
|
|
*/
|
|
export async function auditImage(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
imageId: string,
|
|
imageName: string,
|
|
environmentId?: number | null,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'image', {
|
|
entityId: imageId,
|
|
entityName: imageName,
|
|
environmentId,
|
|
description: `Image ${imageName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for stack actions
|
|
*/
|
|
export async function auditStack(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
stackName: string,
|
|
environmentId?: number | null,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'stack', {
|
|
entityId: stackName,
|
|
entityName: stackName,
|
|
environmentId,
|
|
description: `Stack ${stackName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for volume actions
|
|
*/
|
|
export async function auditVolume(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
volumeId: string,
|
|
volumeName: string,
|
|
environmentId?: number | null,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'volume', {
|
|
entityId: volumeId,
|
|
entityName: volumeName,
|
|
environmentId,
|
|
description: `Volume ${volumeName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for network actions
|
|
*/
|
|
export async function auditNetwork(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
networkId: string,
|
|
networkName: string,
|
|
environmentId?: number | null,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'network', {
|
|
entityId: networkId,
|
|
entityName: networkName,
|
|
environmentId,
|
|
description: `Network ${networkName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for user actions
|
|
*/
|
|
export async function auditUser(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
userId: number,
|
|
username: string,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'user', {
|
|
entityId: String(userId),
|
|
entityName: username,
|
|
description: `User ${username} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for settings actions
|
|
*/
|
|
export async function auditSettings(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
settingName: string,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'settings', {
|
|
entityId: settingName,
|
|
entityName: settingName,
|
|
description: `Settings ${settingName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for environment actions
|
|
*/
|
|
export async function auditEnvironment(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
environmentId: number,
|
|
environmentName: string,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'environment', {
|
|
entityId: String(environmentId),
|
|
entityName: environmentName,
|
|
environmentId,
|
|
description: `Environment ${environmentName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for registry actions
|
|
*/
|
|
export async function auditRegistry(
|
|
event: RequestEvent,
|
|
action: AuditAction,
|
|
registryId: number,
|
|
registryName: string,
|
|
details?: any
|
|
): Promise<void> {
|
|
await audit(event, action, 'registry', {
|
|
entityId: String(registryId),
|
|
entityName: registryName,
|
|
description: `Registry ${registryName} ${action}`,
|
|
details
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper for auth actions (login/logout)
|
|
*/
|
|
export async function auditAuth(
|
|
event: RequestEvent,
|
|
action: 'login' | 'logout',
|
|
username: string,
|
|
details?: any
|
|
): Promise<void> {
|
|
// For login/logout, we want to log even without a session
|
|
if (!(await isEnterprise())) return;
|
|
|
|
const forwardedFor = event.request.headers.get('x-forwarded-for');
|
|
const realIp = event.request.headers.get('x-real-ip');
|
|
let ipAddress = forwardedFor?.split(',')[0]?.trim() || realIp || event.getClientAddress?.() || null;
|
|
|
|
// Convert IPv6 loopback to more readable format
|
|
if (ipAddress === '::1' || ipAddress === '::ffff:127.0.0.1') {
|
|
ipAddress = '127.0.0.1';
|
|
} else if (ipAddress?.startsWith('::ffff:')) {
|
|
ipAddress = ipAddress.substring(7);
|
|
}
|
|
|
|
const userAgent = event.request.headers.get('user-agent') || null;
|
|
|
|
const data: AuditLogCreateData = {
|
|
userId: null, // Will be set from details if available
|
|
username,
|
|
action,
|
|
entityType: 'user',
|
|
entityId: null,
|
|
entityName: username,
|
|
environmentId: null,
|
|
description: `User ${username} ${action}`,
|
|
details,
|
|
ipAddress: ipAddress,
|
|
userAgent: userAgent
|
|
};
|
|
|
|
try {
|
|
await logAuditEvent(data);
|
|
} catch (error) {
|
|
console.error('Failed to log audit event:', error);
|
|
}
|
|
}
|