All files / src/redaction audit-logger.ts

100% Statements 39/39
100% Branches 12/12
100% Functions 5/5
100% Lines 38/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 948x 8x   8x   8x       8x 19x         19x 19x     19x 19x   1x 1x                   16x 3x     13x 13x     13x       13x     13x       13x   13x       26x 26x 26x   26x   4x         48x 19x 19x   15x       29x 3x     27x 24x 24x 19x   24x     3x        
import * as fs from 'fs';
import * as path from 'path';
 
import { v4 as uuidv4 } from 'uuid';
 
import { Logger } from '@nestjs/common';
 
import { RedactionOptions } from '../config/types';
 
export class AuditLogger {
  private readonly logger = new Logger(AuditLogger.name);
  private baseDir: string;
 
  constructor(clientName: string) {
    // Get executable directory or use current working directory
    const execDir = process.cwd();
    this.baseDir = path.join(execDir, 'redaction_audit', clientName);
 
    // Ensure directory exists
    try {
      fs.mkdirSync(this.baseDir, { recursive: true, mode: 0o755 });
    } catch (error) {
      this.logger.error(`Failed to create audit directory: ${ error }`);
      throw error;
    }
  }
 
  logOperation(
    config: RedactionOptions | null | undefined,
    operation: string,
    preData: unknown,
    postData: unknown
  ): string {
    if (!config || !config.verboseAudit) {
      return '';
    }
 
    const opID = uuidv4();
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
 
    // Write pre-redaction data
    const preFile = path.join(
      this.baseDir,
      `${ timestamp }-${ opID }-${ operation }-pre.json`
    );
    this.writeJSONFile(preFile, preData);
 
    // Write post-redaction data
    const postFile = path.join(
      this.baseDir,
      `${ timestamp }-${ opID }-${ operation }-post.json`
    );
    this.writeJSONFile(postFile, postData);
 
    return opID;
  }
 
  private writeJSONFile(filepath: string, data: unknown): void {
    try {
      const enhancedData = this.enhanceDataForReadability(data);
      const jsonData = JSON.stringify(enhancedData, null, 2);
 
      fs.writeFileSync(filepath, jsonData, { mode: 0o644 });
    } catch (error) {
      this.logger.error(`Failed to write audit file ${ filepath }: ${ error }`);
    }
  }
 
  private enhanceDataForReadability(data: unknown): unknown {
    if (typeof data === 'string') {
      try {
        return JSON.parse(data);
      } catch {
        return data;
      }
    }
 
    if (Array.isArray(data)) {
      return data.map((item) => this.enhanceDataForReadability(item));
    }
 
    if (data && typeof data === 'object') {
      const result: Record<string, unknown> = {};
      for (const [ key, value ] of Object.entries(data as Record<string, unknown>)) {
        result[key] = this.enhanceDataForReadability(value);
      }
      return result;
    }
 
    return data;
  }
}