Files
objectstorage/ts/classes/auditlogger.ts
T

64 lines
1.7 KiB
TypeScript
Raw Normal View History

import type * as interfaces from '../../ts_interfaces/index.ts';
export interface IAuditLogEntry {
timestamp: number;
actorUserId: string;
action: string;
targetType: string;
targetId?: string;
success: boolean;
message?: string;
metadata?: Record<string, string | number | boolean>;
}
export class AuditLogger {
constructor(private storageDirectory: string) {}
public get auditLogPath(): string {
return `${this.storageDirectory}/.objectstorage/audit.log`;
}
public async log(entry: Omit<IAuditLogEntry, 'timestamp'>): Promise<void> {
const logEntry: IAuditLogEntry = {
timestamp: Date.now(),
...entry,
};
const dirPath = this.auditLogPath.substring(0, this.auditLogPath.lastIndexOf('/'));
await Deno.mkdir(dirPath, { recursive: true });
await Deno.writeTextFile(this.auditLogPath, `${JSON.stringify(logEntry)}\n`, {
append: true,
create: true,
mode: 0o600,
});
await this.restrictAuditLogPermissions();
}
public async listRecent(limit = 100): Promise<interfaces.data.IAuditEntry[]> {
let content = '';
try {
content = await Deno.readTextFile(this.auditLogPath);
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
return [];
}
throw error;
}
return content
.trim()
.split('\n')
.filter(Boolean)
.slice(-limit)
.map((line) => JSON.parse(line) as interfaces.data.IAuditEntry)
.reverse();
}
private async restrictAuditLogPermissions(): Promise<void> {
try {
await Deno.chmod(this.auditLogPath, 0o600);
} catch {
// chmod is not available on every platform Deno supports.
}
}
}