Skip to content

fix(api): enforce AuditAction enum and structured metadata on audit logs - #474

Open
irefavor15-dotcom wants to merge 1 commit into
XStreamRollz:mainfrom
irefavor15-dotcom:fix/326-audit-action-enum
Open

fix(api): enforce AuditAction enum and structured metadata on audit logs#474
irefavor15-dotcom wants to merge 1 commit into
XStreamRollz:mainfrom
irefavor15-dotcom:fix/326-audit-action-enum

Conversation

@irefavor15-dotcom

Copy link
Copy Markdown

Summary

Closes #326

AuditService.log() previously accepted a free-form action: string, which callers used to embed dynamic data (email addresses, reasons) directly into the action value. This made the action column impossible to index or aggregate meaningfully, and prevented querying for specific event types.

Changes

api/src/audit/audit-action.enum.ts (new)

Introduces an AuditAction enum covering every action string used across the codebase:

export enum AuditAction {
  AUTH_LOGIN_SUCCESS = 'AUTH_LOGIN_SUCCESS',
  AUTH_LOGIN_FAILURE = 'AUTH_LOGIN_FAILURE',
  AUTH_REGISTER_SUCCESS = 'AUTH_REGISTER_SUCCESS',
  AUTH_REGISTER_FAILURE = 'AUTH_REGISTER_FAILURE',
  LOGIN = 'login',
  PASSWORD_CHANGE = 'password_change',
  STREAM_DELETE = 'stream_delete',
  ROLE_CHANGE = 'role_change',
  PROFILE_UPDATE = 'profile_update',
}

api/src/audit/audit.service.ts

Updated log() signature:

// Before
async log(userId: number | null, action: string, ip: string)

// After
async log(userId: number | null, action: AuditAction, metadata: Record<string, unknown>, ip: string)

Variable data (email, reason, username) now goes into a separate metadata JSONB column instead of being embedded in the action string.

api/src/auth/auth.service.ts

All 6 auditService.log() calls updated to use enum values with structured metadata, e.g.:

// Before
auditService.log(null, `AUTH_LOGIN_FAILURE: user_not_found (${dto.email})`, ip)

// After
auditService.log(null, AuditAction.AUTH_LOGIN_FAILURE, { reason: 'user_not_found', email: dto.email }, ip)

api/src/audit/audit.interceptor.ts

SENSITIVE_ACTIONS map typed as Record<string, AuditAction>; interceptor passes {} as metadata (no body context available at interceptor level).

Database migration

database/migrations/2026072801_add_audit_log_metadata_and_index.up.sql:

  • Widens action column to VARCHAR(255)
  • Adds metadata JSONB NOT NULL DEFAULT '{}'
  • Creates idx_audit_logs_action via CREATE INDEX CONCURRENTLY

Rollback: 2026072801_add_audit_log_metadata_and_index.down.sql

Testing

  • TypeScript types catch any caller passing a free-form string at compile time
  • All existing tests pass; no behaviour change for the running application

…ogs (XStreamRollz#326)

- Add AuditAction enum (api/src/audit/audit-action.enum.ts) covering all
  existing action strings used across auth and interceptor callers
- Update AuditService.log() signature to accept AuditAction + metadata
  Record so variable data (email, reason) is stored in a separate JSONB
  column instead of embedded in the action string
- Update all callers in AuthService and AuditInterceptor to pass enum
  values and structured metadata objects
- Add migration: widen action column to VARCHAR(255), add metadata JSONB
  column (NOT NULL DEFAULT '{}'), and CREATE INDEX on action for efficient
  filtering and aggregation

Closes XStreamRollz#326
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api: Audit log action field is a free-form string — no schema enforcement or query indexing

1 participant