Support Systems

Support systems, checklists, and audit logging

P0: Implementation Checklist

P0 Implementation Checklist — Drop Support Systems

Date: 2026-02-22 Status: Ready for Implementation Total Effort: ~21 hours (2-3 days) Owner: John (AI Director)


Overview

This checklist tracks the 6 production-blocking (P0) items that must be completed before Drop can launch to production. Each item addresses a critical gap in monitoring, compliance, or incident response.


P0 Items

1. Server-Side Error Tracking ⏱️ 2 hours (revised)

Problem: All server errors are invisible after Sentry removed CORRECTED: sentry-server.ts already exists with lightweight Envelope API (no @sentry/node dep, Turbopack compatible). However, only 5/25+ routes have captureServerError integrated.

Status: 🟡 Partially Complete (library done, coverage gaps)

Tasks:

Deliverables:

Acceptance Criteria:


2. Audit Logging System ⏱️ 0 hours (ALREADY COMPLETE)

Problem: PSD2 requires immutable audit trail CORRECTED: Audit logging is FULLY IMPLEMENTED.

Status: ✅ Complete

What exists:

No action needed. This was incorrectly flagged as missing in the initial analysis.


3. WAF Deployment ⏱️ 2 hours

Problem: WAF rules defined but not enforced (requires reverse proxy).

Status: ⬜ Not Started

Tasks:

Deliverables:

Acceptance Criteria:


4. Log Aggregation & Retention ⏱️ 2 hours

Problem: Structured logs write to stdout but aren't retained or searchable.

Status: ⬜ Not Started

Tasks:

Deliverables:

Acceptance Criteria:


5. External Uptime Monitoring ⏱️ 1 hour

Problem: BetterStack documented but not deployed.

Status: ⬜ Not Started

Tasks:

Deliverables:

Acceptance Criteria:


6. Payment/Banking Failure Runbooks ⏱️ 4 hours

Problem: DR runbook covers infrastructure but not fintech-specific failures.

Status: ✅ Partially Complete

Tasks:

Deliverables:

Acceptance Criteria:


Progress Tracking

Completion Status

Item Status Progress Blocker
1. Server-side error tracking 🟡 Expanding 80% (lib done, expanding to all routes) None
2. Audit logging ✅ COMPLETE 100% (was already built) None
3. WAF deployment 🟡 Ready 90% (Terraform written, needs apply) terraform apply
4. Log aggregation 🔨 Building 50% (CloudWatch alarms being added) None
5. External monitoring ⬜ Not Started 0% BetterStack account signup
6. Runbooks 🔨 Building 33% → 100% (4 remaining being written) None

Overall Progress: ~70% (revised — audit logging was already 100%)


Priority Order

Week 1 (High Impact, Low Effort):

  1. ✅ External monitoring (1h) — Immediate visibility into outages
  2. ✅ CloudWatch retention (30min) — Logs already flowing, just set policy
  3. ⬜ CloudWatch alarms (1.5h) — Automated alerting

Week 2 (Critical Compliance): 4. ⬜ Audit logging schema (2h) — Create table and library 5. ⬜ Audit logging integration (6h) — Wire into endpoints

Week 3 (Security & Error Tracking): 6. ⬜ Server-side error tracking (4h) — Sentry edge setup 7. ⬜ WAF deployment (2h) — Security hardening

Week 4 (Runbooks): 8. ⬜ Remaining runbooks (2h) — AISP, Swan, Sumsub, Neonomics


Dependencies

External Dependencies

Internal Dependencies

Blocked Items


Testing Plan

Test 1: Error Tracking

# Trigger server error
curl -X POST http://localhost:3000/api/test/error \
  -H "Content-Type: application/json" \
  -d '{"trigger":"server_error"}'

# Verify in Sentry:
# - Event appears within 30s
# - Stack trace includes source file/line
# - User context present (if logged in)

Test 2: Audit Logging

# Perform audit-worthy action
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"wrong"}'

# Check database (PostgreSQL 16):
psql "$DATABASE_URL" -c "SELECT * FROM audit_log ORDER BY timestamp DESC LIMIT 1;"

# Expected:
# audit_xxx|2026-02-22T10:00:00Z|usr_123|login_failure|...|1.2.3.4|Mozilla...

Test 3: WAF

# Test SQLi blocking
curl "https://getdrop.no/api/test?id=1' OR '1'='1" -v

# Expected: HTTP 403 Forbidden

# Test legitimate request
curl "https://getdrop.no/api/health" -v

# Expected: HTTP 200 OK

Test 4: CloudWatch Alarms

# Trigger error spike (loop 15 errors)
for i in {1..15}; do
  curl http://localhost:3000/api/test/error
  sleep 2
done

# Expected:
# - CloudWatch alarm fires after 2 minutes (2 x 1min periods)
# - Slack alert received in #drop-ops
# - Email sent to alem@alai.no

Test 5: BetterStack

# Stop app
docker stop drop-app

# Wait 3-5 minutes

# Expected:
# - BetterStack detects downtime
# - Slack alert in #drop-ops
# - Email to alem@alai.no

# Restart app
docker start drop-app

# Expected:
# - BetterStack detects recovery
# - "UP" notification sent

Rollout Plan

Phase 1: Non-Intrusive (Day 1)

Risk: None. These are read-only additions.

Phase 2: Database Changes (Day 2)

Risk: Low. New table, no app changes. Test migration in dev first.

Phase 3: Code Integration (Day 3-4)

Risk: Medium. Requires code changes + deployment. Deploy to staging first, test 24h, then production.

Phase 4: Runbooks (Day 5)

Risk: None. Documentation only, no production changes.


Success Metrics

After P0 completion, we should achieve:


Approvals

Required Approvals

Sign-Off


Next Steps

  1. Review this analysis with Alem
  2. Get approvals for costs and schema changes
  3. Create Mission Control tasks for each P0 item
  4. Begin implementation (priority order above)
  5. Test thoroughly in staging before production
  6. Document completion in this checklist


Status: Ready for approval and implementation Next Review: After P0 completion (before Phase 2 launch)

Support Overview

Customer Support

Customer support resources for Drop project: FAQs, guides, feedback.

Support Systems Analysis

Drop Support Systems Analysis

Date: 2026-02-22 Author: John (AI Director) Status: MVP Hardening Phase (0.5) Purpose: Comprehensive analysis of support systems for production-ready fintech deployment


Executive Summary

Drop currently has foundational support systems in place but requires critical enhancements before production launch. The application has health checks, CI/CD, error tracking (client-side), and basic alerting, but lacks enterprise-grade observability, audit logging, and incident response procedures required for a PSD2-compliant fintech service.

Key Findings:

Recommendation: Implement P0 systems immediately (est. 2-3 days), defer P1 to Phase 2 (banking integration), and P2 to post-launch optimization.


Current State

1. Monitoring — Uptime & Health Checks

What Exists

What's Missing

Assessment

Status: Adequate for MVP, requires enhancement for production. Gap: External monitoring configured but not deployed. Synthetic checks needed.


2. Logging — Centralized Log Aggregation

What Exists

What's Missing

Assessment

Status: Foundation exists, but logs are ephemeral (lost on container restart). Gap: Critical for incident investigation and compliance audits. Need CloudWatch Logs or similar.


3. Error Tracking — Error Capture & Alerting

What Exists

What's Missing

Assessment

Status: Client errors tracked, server errors blind. Gap: CRITICAL — server-side errors (API, DB, integrations) are invisible. P0 fix required.


4. Alerting — On-Call & Escalation

What Exists

What's Missing

Assessment

Status: Basic alerting works for small team, inadequate for 24/7 production. Gap: Need on-call schedule, escalation policy, and multi-channel delivery.


5. Security Monitoring — WAF, DDoS, Anomaly Detection, Audit Logs

What Exists

What's Missing

Assessment

Status: Security-aware codebase, but monitoring/audit infrastructure missing. Gap: CRITICAL — audit logs are PSD2/GDPR compliance requirement. P0 fix.


6. Performance — APM, Latency Tracking, Resource Utilization

What Exists

What's Missing

Assessment

Status: Minimal. Can detect total outage but not performance degradation. Gap: Need before production to identify bottlenecks and capacity issues.


7. Database — Backups, Replication, Monitoring

What Exists

What's Missing

Assessment

Status: Basic backup/restore exists, monitoring gaps. Gap: Backup testing and proactive monitoring needed before production.


8. Incident Response — Runbooks, Status Page, Communication Plan

What Exists

What's Missing

Assessment

Status: Basic DR runbook exists, lacks fintech-specific scenarios. Gap: Need payment/banking integration runbooks before Phase 2.


9. CI/CD — Build Pipeline, Deployment, Rollback

What Exists

What's Missing

Assessment

Status: Strong quality gate, weak deployment safety. Gap: Add post-deployment health checks and rollback automation.


10. Compliance — Audit Trails, Data Retention, GDPR/PSD2 Logging

What Exists

What's Missing

Assessment

Status: CRITICAL GAP. Audit logs are PSD2 legal requirement. Gap: P0 — must implement before production launch.


Gap Analysis

P0 — Production Blockers (Must Fix Before Go-Live)

# Category Gap Impact Effort
1 Error Tracking No server-side error monitoring Can't detect/debug API failures 4h
2 Compliance No audit logs (auth, data access, admin actions) PSD2 non-compliance, legal risk 8h
3 Security WAF rules defined but not deployed Vulnerable to SQLi, XSS, DDoS 2h (config)
4 Logging No log aggregation/retention Can't investigate incidents 2h (CloudWatch setup)
5 Monitoring BetterStack configured but not deployed No external incident detection 1h (account setup)
6 Incident Response No payment/banking failure runbooks Can't recover from PISP/BankID outages 4h

Total P0 effort: ~21 hours (2-3 days)


P1 — Needed Soon (Before Phase 2: Banking Integration)

# Category Gap Impact Effort
7 Alerting No on-call rotation or escalation policy Incidents may go unnoticed outside work hours 2h
8 Performance No APM for distributed tracing Can't diagnose slow transactions 4h
9 Database No backup testing or monitoring Backups may be corrupt, undetected 3h
10 Security No penetration testing Unknown vulnerabilities 16h (external)
11 CI/CD No automated rollback on deployment failure Bad deploys cause extended outages 6h
12 Compliance No STR submission workflow Can't fulfill AML obligations 8h

Total P1 effort: ~39 hours (5 days)


P2 — Nice to Have (Post-Launch Optimization)

# Category Gap Impact Effort
13 Monitoring No synthetic transaction monitoring Can't detect broken user flows 8h
14 Performance No Core Web Vitals tracking Poor user experience undetected 4h
15 Alerting No SMS/phone alerts for critical incidents Slack outage = missed alerts 2h
16 Database No slow query alerts Performance degradation undetected 6h
17 Security No IDS/IPS for intrusion detection Advanced attacks undetected 16h
18 Incident Response No public status page Customers unaware of outages 4h

Total P2 effort: ~40 hours (5 days)


Implementation Plan

Phase 1: P0 Production Blockers (NOW — before Phase 1 demo)

Goal: Address legal/compliance requirements and critical observability gaps.

1.1 Server-Side Error Tracking (4h)

Problem: All server errors invisible after Sentry removed (Next.js 16 Turbopack incompatibility).

Solution:

Deliverable:

Files: infrastructure/error-tracking-setup.md


1.2 Audit Logging System (8h)

Problem: PSD2 requires immutable audit trail for auth, data access, admin actions.

Solution:

Deliverable:

Files: support/audit-logging-setup.md


1.3 WAF Deployment (2h)

Problem: WAF rules defined but not enforced (requires reverse proxy).

Solution:

Deliverable:

Files: infrastructure/cloudflare-waf-setup.md


1.4 Log Aggregation (2h)

Problem: Structured logs write to stdout but aren't retained or searchable.

Solution:

Deliverable:

Files: infrastructure/cloudwatch-logs-setup.md


1.5 External Uptime Monitoring (1h)

Problem: BetterStack documented but not deployed.

Solution:

Deliverable:

Files: support/betterstack-deployment.md


1.6 Payment/Banking Failure Runbooks (4h)

Problem: DR runbook covers infrastructure but not fintech-specific failures.

Solution:

Deliverable:

Files: Created in /Users/makinja/ALAI/products/Drop/support/runbooks/


Phase 2: P1 Items (Phase 2: Banking Integration)

Defer to Phase 2 when real banking integrations are live and need production-grade support.

Priority order:

  1. Penetration testing (external security audit)
  2. APM for transaction tracing (identify slow payments)
  3. On-call rotation and escalation policy
  4. Automated rollback on failed deployments
  5. Backup testing and monitoring
  6. STR submission workflow (AML compliance)

Phase 3: P2 Items (Post-Launch)

Optimize after initial production deployment and user feedback.

Priority order:

  1. Synthetic transaction monitoring (test critical user flows)
  2. Public status page (customer transparency)
  3. Core Web Vitals tracking (frontend performance)
  4. SMS/phone alerts (redundancy)
  5. Slow query monitoring (database optimization)
  6. IDS/IPS (advanced threat detection)

Architecture

Support Systems Connectivity

┌─────────────────────────────────────────────────────────────────┐
│                         Drop Application                        │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────────┐  │
│  │  drop-app   │  │  drop-api    │  │  drop-mobile (Expo)  │  │
│  │  (Next.js)  │  │  (Hono)      │  │  (React Native)      │  │
│  └─────────────┘  └──────────────┘  └──────────────────────┘  │
│         │                │                      │               │
│         └────────────────┴──────────────────────┘               │
│                          │                                      │
└──────────────────────────┼──────────────────────────────────────┘
                           │
        ┌──────────────────┼──────────────────────────────┐
        │                  │                              │
        ▼                  ▼                              ▼
┌───────────────┐  ┌──────────────┐           ┌──────────────────┐
│ Structured    │  │ Health Check │           │ Audit Logs       │
│ Logging       │  │ Endpoint     │           │ (audit_logs      │
│ (JSON stdout) │  │ /api/health  │           │  table)          │
└───────┬───────┘  └──────┬───────┘           └─────────┬────────┘
        │                 │                             │
        │                 │                             │
        ▼                 │                             │
┌────────────────┐        │                             │
│ CloudWatch     │        │                             │
│ Logs           │        │                             │
│ (30d retention)│        │                             │
└────────────────┘        │                             │
        │                 │                             │
        │                 ▼                             │
        │         ┌───────────────┐                     │
        │         │ BetterStack   │                     │
        │         │ (external     │                     │
        │         │  monitoring)  │                     │
        │         └───────┬───────┘                     │
        │                 │                             │
        └─────────────────┼─────────────────────────────┘
                          │
                          ▼
                 ┌────────────────┐
                 │ Alerting Layer │
                 │ (alerts.ts)    │
                 └────────┬───────┘
                          │
        ┌─────────────────┼─────────────────┐
        │                 │                 │
        ▼                 ▼                 ▼
┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│ Slack       │  │ Sentry      │  │ Email       │
│ Webhook     │  │ (client +   │  │ (SMTP)      │
│ (#drop-ops) │  │  edge)      │  │             │
└─────────────┘  └─────────────┘  └─────────────┘

Data Flows

  1. Error Flow:

    • Client error → Sentry browser → Slack alert (if spike)
    • Server error → Sentry edge → CloudWatch Logs → Slack alert
    • API 5xx → trackError() → Spike detection → Slack
  2. Monitoring Flow:

    • App → stdout → CloudWatch Logs
    • App → /api/health → BetterStack → Slack/Email/SMS
    • Container → Docker health check → Auto-restart
  3. Audit Flow:

    • User action → auditLog()audit_logs table
    • Compliance query → SQL export → Regulator submission
  4. Incident Flow:

    • Alert → Slack #drop-ops
    • Unacknowledged (5 min) → Email to Alem
    • Unresolved (15 min) → SMS (BetterStack escalation)
    • Incident → Runbook → Recovery → Post-mortem

Cost Estimate

Free Tier (MVP)

Total MVP cost: $0/month

Paid Services (Production)

Total production cost: ~$50-100/month (without APM)


Recommendations

Immediate (This Week)

  1. Deploy BetterStack (1h) — External monitoring is fast win
  2. Configure CloudWatch retention (30 min) — Logs already flow, just set policy
  3. Create audit log schema (2h) — Start with table, integrate incrementally

Before Phase 1 Demo (Next 2 Weeks)

  1. Implement server-side error tracking (4h) — Sentry edge or custom
  2. Write payment failure runbooks (4h) — Prepare for demo questions
  3. Deploy Cloudflare WAF (2h) — Security hygiene

Before Phase 2 Go-Live (Next 2-3 Months)

  1. 🔲 External penetration test (hire security firm, ~$5K budget)
  2. 🔲 APM implementation (Datadog or Sentry Performance)
  3. 🔲 On-call rotation (define schedule, test escalation)
  4. 🔲 Backup testing (restore from snapshot, verify data integrity)

Post-Launch Optimization

  1. 🔲 Synthetic monitoring (Checkly or custom Playwright tests)
  2. 🔲 Public status page (BetterStack included, just enable)
  3. 🔲 Core Web Vitals (Google Lighthouse CI integration)

Success Metrics

Before Go-Live (P0 Checklist)

Production KPIs


Appendices

B. External Services

C. Change History


Next Actions:

  1. Review this analysis with Alem
  2. Approve P0 implementation plan
  3. Begin P0 work (estimated 21 hours / 2-3 days)
  4. Track progress in Mission Control tasks

Audit Logging Setup

Audit Logging Setup — Drop Fintech

Date: 2026-02-22 Priority: P0 (Production Blocker) Compliance: PSD2, GDPR Effort: 8 hours


Overview

Audit logging provides an immutable record of all authentication, authorization, data access, and administrative actions. This is a legal requirement for PSD2-regulated payment services and GDPR data protection compliance.


Requirements

PSD2 Audit Trail Requirements

GDPR Right of Access


Database Schema

Migration: 003_audit_logs.sql

-- Audit Logs Table (PostgreSQL 16 — ADR-014)
-- Schema managed via Drizzle ORM (src/shared/db/schema.ts)
-- Apply with: make db-push

CREATE TABLE IF NOT EXISTS audit_log (
  id TEXT PRIMARY KEY,
  timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  user_id TEXT,
  action TEXT NOT NULL,
  resource_type TEXT,
  resource_id TEXT,
  details JSONB,
  ip_address TEXT,
  user_agent TEXT,
  request_id TEXT,
  result TEXT NOT NULL DEFAULT 'success', -- 'success', 'failure', 'denied'
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_audit_user_time ON audit_log(user_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_audit_action_time ON audit_log(action, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_audit_resource ON audit_log(resource_type, resource_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_audit_request ON audit_log(request_id);
CREATE INDEX IF NOT EXISTS idx_audit_result ON audit_log(result, timestamp DESC);

-- Partitioning by month (production)
CREATE TABLE audit_log_2026_02 PARTITION OF audit_log FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

Migration steps (PostgreSQL 16 via Drizzle ORM):

  1. Schema is defined in src/shared/db/schema.ts
  2. Apply with:
    make db-push
    # or: cd src/shared && npx drizzle-kit push
    
  3. Verify table exists:
    psql "$DATABASE_URL" -c "SELECT table_name FROM information_schema.tables WHERE table_name='audit_log';"
    

Implementation

Audit Log Library: src/lib/audit-log.ts

import { db } from '@drop/shared/db';
import { randomId } from './utils-server';
import { logger } from './logger';

export type AuditAction =
  // Authentication
  | 'login_success'
  | 'login_failure'
  | 'logout'
  | 'password_change'
  | 'session_revoked'
  // Authorization
  | 'access_granted'
  | 'access_denied'
  // Data Access
  | 'data_view'
  | 'data_export'
  | 'data_delete'
  // Transactions
  | 'transaction_created'
  | 'transaction_completed'
  | 'transaction_failed'
  // KYC/AML
  | 'kyc_approved'
  | 'kyc_rejected'
  | 'aml_alert_created'
  | 'aml_alert_reviewed'
  // Admin
  | 'user_created'
  | 'user_updated'
  | 'user_deleted'
  | 'role_changed';

export type AuditResult = 'success' | 'failure' | 'denied';

export interface AuditLogEntry {
  userId?: string;
  action: AuditAction;
  resourceType?: string;
  resourceId?: string;
  metadata?: Record<string, unknown>;
  ip?: string;
  userAgent?: string;
  requestId?: string;
  result?: AuditResult;
}

/**
 * Create an audit log entry
 *
 * IMPORTANT: This function must NEVER throw errors.
 * Audit failures should not block user actions.
 */
export async function auditLog(entry: AuditLogEntry): Promise<void> {
  try {
    const id = randomId('audit');
    const timestamp = new Date().toISOString();

    await run(
      `INSERT INTO audit_logs (
        id, timestamp, user_id, action, resource_type, resource_id,
        metadata, ip_address, user_agent, request_id, result
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      [
        id,
        timestamp,
        entry.userId || null,
        entry.action,
        entry.resourceType || null,
        entry.resourceId || null,
        entry.metadata ? JSON.stringify(entry.metadata) : null,
        entry.ip || null,
        entry.userAgent || null,
        entry.requestId || null,
        entry.result || 'success',
      ]
    );

    logger.debug('Audit log created', { auditId: id, action: entry.action });
  } catch (error) {
    // Log error but do NOT throw (audit failures should not block operations)
    logger.error('Failed to create audit log', {
      error: error instanceof Error ? error.message : String(error),
      action: entry.action,
    });
  }
}

/**
 * Retrieve audit logs for a user (GDPR Right of Access)
 */
export async function getUserAuditLogs(
  userId: string,
  options?: { limit?: number; offset?: number; startDate?: string; endDate?: string }
): Promise<unknown[]> {
  const { limit = 100, offset = 0, startDate, endDate } = options || {};

  let sql = 'SELECT * FROM audit_logs WHERE user_id = ?';
  const params: unknown[] = [userId];

  if (startDate) {
    sql += ' AND timestamp >= ?';
    params.push(startDate);
  }

  if (endDate) {
    sql += ' AND timestamp <= ?';
    params.push(endDate);
  }

  sql += ' ORDER BY timestamp DESC LIMIT ? OFFSET ?';
  params.push(limit, offset);

  const { query } = await import('./db');
  return query(sql, params);
}

/**
 * Export audit logs as CSV (for compliance reporting)
 */
export async function exportAuditLogsCSV(
  filters?: {
    userId?: string;
    action?: AuditAction;
    startDate?: string;
    endDate?: string;
  }
): Promise<string> {
  let sql = 'SELECT * FROM audit_logs WHERE 1=1';
  const params: unknown[] = [];

  if (filters?.userId) {
    sql += ' AND user_id = ?';
    params.push(filters.userId);
  }

  if (filters?.action) {
    sql += ' AND action = ?';
    params.push(filters.action);
  }

  if (filters?.startDate) {
    sql += ' AND timestamp >= ?';
    params.push(filters.startDate);
  }

  if (filters?.endDate) {
    sql += ' AND timestamp <= ?';
    params.push(filters.endDate);
  }

  sql += ' ORDER BY timestamp DESC';

  const { query } = await import('./db');
  const rows = await query(sql, params);

  // Convert to CSV
  const headers = [
    'id',
    'timestamp',
    'user_id',
    'action',
    'resource_type',
    'resource_id',
    'metadata',
    'ip_address',
    'user_agent',
    'request_id',
    'result',
  ];

  const csvRows = [headers.join(',')];

  for (const row of rows as Record<string, unknown>[]) {
    const values = headers.map((h) => {
      const val = row[h];
      if (val === null || val === undefined) return '';
      return String(val).replace(/"/g, '""'); // Escape quotes
    });
    csvRows.push(values.map((v) => `"${v}"`).join(','));
  }

  return csvRows.join('\n');
}

Integration Points

1. Authentication (src/app/api/auth/login/route.ts)

import { auditLog } from '@/lib/audit-log';

export async function POST(request: NextRequest) {
  const { email, password } = await request.json();
  const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip');
  const userAgent = request.headers.get('user-agent');
  const requestId = getRequestId(request.headers);

  try {
    const user = await getUserByEmail(email);
    if (!user || !await verifyPassword(password, user.password_hash)) {
      // Audit failed login attempt
      await auditLog({
        userId: user?.id,
        action: 'login_failure',
        metadata: { email, reason: 'invalid_credentials' },
        ip,
        userAgent,
        requestId,
        result: 'failure',
      });

      return jsonError('Invalid credentials', 401);
    }

    // Audit successful login
    await auditLog({
      userId: user.id,
      action: 'login_success',
      metadata: { email },
      ip,
      userAgent,
      requestId,
      result: 'success',
    });

    // ... rest of login logic
  } catch (error) {
    // ... error handling
  }
}

2. Logout (src/app/api/auth/logout/route.ts)

await auditLog({
  userId: session.userId,
  action: 'logout',
  metadata: { sessionId: session.id },
  ip,
  userAgent,
  requestId,
});

3. Data Access (src/app/api/users/[id]/route.ts)

export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
  const session = await requireAuth(request);
  const userId = params.id;

  // Check authorization
  if (session.userId !== userId && session.role !== 'admin') {
    await auditLog({
      userId: session.userId,
      action: 'access_denied',
      resourceType: 'user',
      resourceId: userId,
      metadata: { reason: 'insufficient_permissions' },
      ip: request.headers.get('x-forwarded-for'),
      userAgent: request.headers.get('user-agent'),
      requestId: getRequestId(request.headers),
      result: 'denied',
    });

    return jsonError('Access denied', 403);
  }

  // Audit successful data access
  await auditLog({
    userId: session.userId,
    action: 'data_view',
    resourceType: 'user',
    resourceId: userId,
    ip: request.headers.get('x-forwarded-for'),
    userAgent: request.headers.get('user-agent'),
    requestId: getRequestId(request.headers),
  });

  const user = await getUserById(userId);
  return jsonSuccess(user);
}

4. KYC Approval (src/app/api/admin/kyc/route.ts)

await auditLog({
  userId: adminSession.userId,
  action: 'kyc_approved',
  resourceType: 'user',
  resourceId: targetUserId,
  metadata: { reason: kycApprovalReason },
  ip: request.headers.get('x-forwarded-for'),
  userAgent: request.headers.get('user-agent'),
  requestId: getRequestId(request.headers),
});

5. Transaction Creation (src/app/api/transactions/route.ts)

await auditLog({
  userId: session.userId,
  action: 'transaction_created',
  resourceType: 'transaction',
  resourceId: transactionId,
  metadata: {
    type: transactionType,
    amount: amount,
    currency: currency,
  },
  ip: request.headers.get('x-forwarded-for'),
  userAgent: request.headers.get('user-agent'),
  requestId: getRequestId(request.headers),
});

Compliance Reporting

GDPR Right of Access (User Data Export)

// src/app/api/users/[id]/audit-logs/route.ts
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
  const session = await requireAuth(request);

  // Users can only access their own audit logs
  if (session.userId !== params.id && session.role !== 'admin') {
    return jsonError('Access denied', 403);
  }

  const logs = await getUserAuditLogs(params.id, {
    limit: 1000, // GDPR requires "all data"
    startDate: request.nextUrl.searchParams.get('start') || undefined,
    endDate: request.nextUrl.searchParams.get('end') || undefined,
  });

  return jsonSuccess({ logs });
}

PSD2 Audit Trail Export (Admin)

// src/app/api/admin/audit/export/route.ts
export async function GET(request: NextRequest) {
  const session = await requireAuth(request);

  if (session.role !== 'admin') {
    return jsonError('Admin access required', 403);
  }

  const startDate = request.nextUrl.searchParams.get('start');
  const endDate = request.nextUrl.searchParams.get('end');
  const action = request.nextUrl.searchParams.get('action');
  const userId = request.nextUrl.searchParams.get('userId');

  const csv = await exportAuditLogsCSV({
    userId: userId || undefined,
    action: action as AuditAction | undefined,
    startDate: startDate || undefined,
    endDate: endDate || undefined,
  });

  return new Response(csv, {
    headers: {
      'Content-Type': 'text/csv',
      'Content-Disposition': `attachment; filename="audit-logs-${new Date().toISOString()}.csv"`,
    },
  });
}

Retention Policy

PSD2 Requirement: 5 Years

PostgreSQL 16 (all environments — ADR-014):


Testing

Test Audit Logging

# 1. Create audit log entry
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"wrong"}'

# 2. Check audit log table (PostgreSQL 16)
psql "$DATABASE_URL" -c "SELECT * FROM audit_log ORDER BY timestamp DESC LIMIT 5;"

# Expected output:
# audit_123 | 2026-02-22T10:00:00.000Z | usr_456 | login_failure | ... | {"email":"test@example.com","reason":"invalid_credentials"} | 1.2.3.4 | Mozilla/5.0... | req_789 | failure

# 3. Export audit logs (admin)
curl -X GET "http://localhost:3000/api/admin/audit/export?start=2026-02-01&end=2026-02-28" \
  -H "Cookie: auth-token=<admin-jwt>" \
  > audit-logs.csv

# 4. Verify CSV format
head -n 5 audit-logs.csv

Monitoring

Alert on Audit Failures

Add to src/lib/audit-log.ts:

import { sendAlert } from './alerts';

export async function auditLog(entry: AuditLogEntry): Promise<void> {
  try {
    // ... insert logic
  } catch (error) {
    logger.error('Failed to create audit log', { error, action: entry.action });

    // CRITICAL: Alert if audit logging fails (compliance risk)
    await sendAlert({
      severity: 'critical',
      title: 'Audit logging failure',
      message: `Failed to record ${entry.action} for user ${entry.userId}`,
    });
  }
}

Metrics to Track


Security Considerations

Immutability

Access Control

Data Redaction


Checklist


Next Steps:

  1. Create migration file
  2. Implement audit-log.ts library
  3. Integrate into auth routes (high priority)
  4. Add to remaining endpoints incrementally
  5. Test with real login/logout flows
  6. Deploy to staging for verification