# @zola_do/audit

[![npm version](https://img.shields.io/npm/v/@zola_do/audit.svg)](https://www.npmjs.com/package/@zola_do/audit)
[![npm downloads](https://img.shields.io/npm/dm/@zola_do/audit.svg)](https://www.npmjs.com/package/@zola_do/audit)
[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)

RabbitMQ-based audit logging for NestJS applications with entity change tracking and request auditing.

## Overview

`@zola_do/audit` provides comprehensive audit logging capabilities:

- **Request Auditing** — Automatic logging of HTTP requests
- **Event Auditing** — Mark RPC events with `@AuditRmqEvent()`
- **Entity Auditing** — TypeORM subscriber for INSERT/UPDATE/DELETE
- **Conditional Loading** — Gracefully degrades without RabbitMQ
- **User Context** — Captures user and organization from request
- **Request correlation** — Optional `RequestIdInterceptor` for `x-request-id` / `req.requestId`

## Installation

```bash
# Install individually
npm install @zola_do/audit

# Or via meta package
npm install @zola_do/nestjs-shared
```

### Required Dependencies

For full audit functionality:

```bash
npm install @nestjs/microservices amqplib amqp-connection-manager
```

Set `RMQ_URL` in the environment. Without it (or without the packages above), `AuditModule` loads safely but RabbitMQ audit is disabled.

**Note:** The module loads successfully without these dependencies but audit features are disabled with a development warning.

## Quick Start

### 1. Configure Environment

```bash
# .env
RMQ_URL=amqp://user:pass@localhost:5672
APPLICATION_NAME=my-app
```

### 2. Register Module

```typescript
import { Module } from '@nestjs/common';
import { AuditModule } from '@zola_do/audit';

@Module({
  imports: [AuditModule.forRoot()],
})
export class AppModule {}
```

### Request correlation (optional)

```typescript
import { RequestIdInterceptor } from '@zola_do/audit';

app.useGlobalInterceptors(new RequestIdInterceptor());
```

Preserves incoming `x-request-id` or generates one; sets `req.requestId` for structured logs and audit correlation.

### Optional: extra sinks and redaction

```typescript
AuditModule.forRoot({
  extraSinks: [
    async (log) => {
      // e.g. stdout in local dev, secondary queue, or observability bridge
      console.log(JSON.stringify(log));
    },
  ],
  sensitiveBodyFields: ['password', 'token', 'refresh_token'],
  sensitiveColumns: ['passwordHash', 'ssn'],
  logSqlParameters: false, // default — omit bind params from SQL audit events
})
```

By default, audit payloads redact `authorization`, `x-api-key`, cookies (names only), and common secret body fields before RabbitMQ emit.

### 3. Mark Routes for Audit

```typescript
import { Controller, Post, Body } from '@nestjs/common';
import { AuditRmqEvent } from '@zola_do/audit';

@Controller('orders')
export class OrdersController {
  @Post()
  @AuditRmqEvent()
  createOrder(@Body() dto: CreateOrderDto) {
    // This request is logged to RabbitMQ
    return this.orderService.create(dto);
  }
}
```

## Audit Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                         Audit Flow                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  HTTP Request                                                        │
│       │                                                              │
│       ▼                                                              │
│  ┌─────────────┐                                                    │
│  │  JwtGuard   │──► request.user                                    │
│  └─────────────┘                                                    │
│       │                                                              │
│       ▼                                                              │
│  ┌─────────────────────────────────────────┐                        │
│  │  AuditLoggerInterceptor (Global)       │                        │
│  │                                         │                        │
│  │  Checks @AuditRmqEvent() metadata       │                        │
│  └─────────────────────────────────────────┘                        │
│       │                                                              │
│       │ @AuditRmqEvent() found                                     │
│       ▼                                                              │
│  ┌─────────────┐                                                    │
│  │  Publish to │                                                    │
│  │  RabbitMQ   │─────► Exchange: audit.events                      │
│  └─────────────┘       Queue: audit.logs                            │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
```

## Decorators

### @AuditRmqEvent()

Marks a controller method for audit logging:

```typescript
@Controller('orders')
export class OrdersController {
  @Post()
  @AuditRmqEvent()
  createOrder(@Body() dto: CreateOrderDto) {
    // Logs: { userId, organization, method, path, body, timestamp }
  }

  @Put(':id')
  @AuditRmqEvent({ action: 'UPDATE_ORDER' })
  updateOrder(@Param('id') id: string, @Body() dto: UpdateOrderDto) {
    // Logs with custom action name
  }
}
```

### @IgnoreLogger()

Excludes specific routes from audit logging:

```typescript
@Controller()
export class HealthController {
  @Get('health')
  @IgnoreLogger()
  healthCheck() {
    return { status: 'ok' };
  }

  @Get('metrics')
  @IgnoreLogger()
  metrics() {
    return { requests: 1000 };
  }
}
```

## Entity Auditing

The `AuditSubscriber` automatically tracks entity changes:

```typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuditSubscriber } from '@zola_do/audit';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      subscribers: [AuditSubscriber],
    }),
  ],
})
export class AppModule {}
```

### Tracked Events

| Event    | Description     |
| -------- | --------------- |
| `INSERT` | Entity created  |
| `UPDATE` | Entity modified |
| `DELETE` | Entity deleted  |

### Audit Data Structure

```typescript
interface AuditLog {
  id: string;
  userId?: string;
  organizationId?: string;
  action: string;
  entity: string;
  entityId: string;
  oldValues?: Record<string, any>;
  newValues?: Record<string, any>;
  timestamp: Date;
  requestPath?: string;
  requestMethod?: string;
}
```

## Configuration

### RabbitMQ Config

```typescript
import { auditLoggerConfig } from '@zola_do/audit';

// Uses RMQ_URL from environment
export const rmqConfig = auditLoggerConfig;
```

### Manual Audit Publishing

```typescript
import { Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Inject } from '@nestjs/common';
import { AUDIT_RMQ_EVENT } from '@zola_do/audit';

@Injectable()
export class OrderService {
  constructor(
    @Inject('AUDIT_CLIENT') private readonly auditClient: ClientProxy,
  ) {}

  async createOrder(dto: CreateOrderDto, user: any) {
    const order = await this.orderRepo.save(dto);

    // Publish audit event
    this.auditClient.emit(AUDIT_RMQ_EVENT, {
      userId: user.id,
      organizationId: user.organizationId,
      action: 'ORDER_CREATED',
      entity: 'Order',
      entityId: order.id,
      newValues: order,
      timestamp: new Date(),
    });

    return order;
  }
}
```

## Environment Variables

| Variable           | Description             | Required                 |
| ------------------ | ----------------------- | ------------------------ |
| `RMQ_URL`          | RabbitMQ connection URL | Yes (for audit features) |
| `APPLICATION_NAME` | App name for audit logs | No                       |

## Conditional Loading

The module gracefully handles missing dependencies:

```typescript
// If amqplib or @nestjs/microservices not installed:
@Module({
  providers: [AuditModule],
  exports: [AuditModule],
})
export class AuditModule {
  // AuditLoggerInterceptor NOT registered
  // Warning in development: "Audit logging disabled - missing dependencies"
}

// If dependencies installed:
@Module({
  providers: [AuditLoggerInterceptor, AuditSubscriber],
  exports: [AuditModule],
})
export class AuditModule {
  // Full audit functionality enabled
}
```

## DTOs

### CreateAuditLogDto

```typescript
interface CreateAuditLogDto {
  userId?: string;
  organizationId?: string;
  action: string;
  entity: string;
  entityId: string;
  oldValues?: Record<string, any>;
  newValues?: Record<string, any>;
  requestPath?: string;
  requestMethod?: string;
  metadata?: Record<string, any>;
}
```

### CreateEventDTO

```typescript
interface CreateEventDTO {
  userId?: string;
  organizationId?: string;
  eventName: string;
  eventData: Record<string, any>;
  timestamp?: Date;
}
```

## API Reference

### Module

```typescript
@Module({
  imports: [AuditModule],
  // Or register subscriber globally
})
export class AppModule {}
```

### Decorators

| Decorator                    | Description            |
| ---------------------------- | ---------------------- |
| `@AuditRmqEvent()`           | Mark for audit logging |
| `@AuditRmqEvent({ action })` | Custom action name     |
| `@IgnoreLogger()`            | Exclude from audit     |

### Exports

```typescript
// All exports from index.ts
import {
  AuditModule,
  AuditRmqEvent,
  AUDIT_RMQ_EVENT,
  IgnoreLogger,
  IGNORE_AUDIT_LOGGER,
  AuditLoggerInterceptor,
  AuditSubscriber,
  auditLoggerConfig,
} from '@zola_do/audit';
```

## Best Practices

### 1. Audit Sensitive Operations

```typescript
@Post(':id/approve')
@AuditRmqEvent({ action: 'ORDER_APPROVED' })
async approveOrder(@Param('id') id: string) {
  return this.orderService.approve(id);
}

@Post(':id/reject')
@AuditRmqEvent({ action: 'ORDER_REJECTED' })
async rejectOrder(@Param('id') id: string) {
  return this.orderService.reject(id);
}
```

### 2. Exclude Health Checks

```typescript
@Controller()
export class HealthController {
  @Get()
  @IgnoreLogger() // Don't audit health checks
  health() {
    return { status: 'ok' };
  }
}
```

### 3. Use Custom User Extraction

The interceptor extracts user from `request.user` (populated by JwtGuard):

```typescript
// Ensure your JwtStrategy sets user object
class JwtStrategy extends PassportStrategy(Strategy) {
  async validate(payload: any) {
    return {
      id: payload.sub,
      email: payload.email,
      organizationId: payload.organization?.organizationId,
    };
  }
}
```

## Troubleshooting

### Q: Audit events not being published?

1. Check `RMQ_URL` is configured
2. Verify RabbitMQ is running
3. Check for `Audit logging disabled` warning in logs

### Q: UserId is undefined in audit?

Ensure `JwtGuard` or `AuthorizationModule` is enabled and populates `request.user`.

### Q: Module loading fails?

Install optional dependencies or ignore the warning - the module still loads without audit functionality.

## Related Packages

- [@zola_do/authorization](../authorization) — User context for audit

## License

ISC

## Community

- [Contributing](../../CONTRIBUTING.md)
- [Code of Conduct](../../CODE_OF_CONDUCT.md)
- [Security Policy](../../SECURITY.md)

## Configuration Validation

Validate RabbitMQ audit configuration before bootstrap when audit logging is required:

```typescript
import { validateAuditEnv } from '@zola_do/audit';

validateAuditEnv();
```

Use `validateAuditEnv({ required: false })` when audit transport is intentionally optional.
