Audit Logs & Auth Trail — Auth

Audit log adalah catatan permanen dari semua aktivitas autentikasi dan otorisasi. Ini kritis untuk keamanan, debugging, dan kepatuhan regulasi (GDPR, SOC2, ISO

Audit log adalah catatan permanen dari semua aktivitas autentikasi dan otorisasi. Ini kritis untuk keamanan, debugging, dan kepatuhan regulasi (GDPR, SOC2, ISO 27001).

Mengapa audit log penting:

Struktur audit log yang baik:

function createAuditLog(params) {
  return {
    id: crypto.randomUUID(),
    timestamp: new Date().toISOString(),
    userId: params.userId,
    action: params.action,       // "login", "logout", "password_change", "resource_access"
    resource: params.resource,   // "/api/users/42", "/api/admin/settings"
    success: params.success,     // true/false
    metadata: {
      ip: params.ip,
      userAgent: params.userAgent,
      ...params.metadata         // data tambahan spesifik action
    }
  };
}

// Contoh penggunaan:
createAuditLog({
  userId: "user_42",
  action: "login",
  resource: "/api/auth/login",
  success: true,
  ip: "1.2.3.4",
  userAgent: "Mozilla/5.0..."
});

createAuditLog({
  userId: "user_42",
  action: "resource_access",
  resource: "/api/admin/users",
  success: false,
  ip: "1.2.3.4",
  metadata: { reason: "insufficient_permissions", requiredRole: "admin" }
});

Tipe event yang harus di-log:

const AUTH_EVENTS = {
  LOGIN_SUCCESS: "auth.login.success",
  LOGIN_FAILED: "auth.login.failed",
  LOGOUT: "auth.logout",
  PASSWORD_CHANGED: "auth.password.changed",
  PASSWORD_RESET_REQUESTED: "auth.password.reset_requested",
  MFA_ENABLED: "auth.mfa.enabled",
  MFA_VERIFIED: "auth.mfa.verified",
  MFA_FAILED: "auth.mfa.failed",
  TOKEN_REFRESHED: "auth.token.refreshed",
  SESSION_REVOKED: "auth.session.revoked",
  ROLE_CHANGED: "auth.role.changed",
  ACCOUNT_LOCKED: "auth.account.locked",
};

Query & filter audit logs:

function queryAuditLogs(logs, filters = {}) {
  return logs.filter((log) => {
    if (filters.userId && log.userId !== filters.userId) return false;
    if (filters.action && log.action !== filters.action) return false;
    if (filters.success !== undefined && log.success !== filters.success) return false;
    if (filters.from && new Date(log.timestamp) < new Date(filters.from)) return false;
    if (filters.to && new Date(log.timestamp) > new Date(filters.to)) return false;
    return true;
  });
}

// Contoh: semua login gagal dalam 1 jam terakhir
const failedLogins = queryAuditLogs(logs, {
  action: "auth.login.failed",
  success: false,
  from: new Date(Date.now() - 60 * 60 * 1000).toISOString()
});

// Deteksi brute force: >5 gagal dari IP yang sama
const byIp = failedLogins.reduce((acc, log) => {
  const ip = log.metadata?.ip;
  acc[ip] = (acc[ip] || 0) + 1;
  return acc;
}, {});
const suspiciousIps = Object.entries(byIp).filter(([, count]) => count > 5);

Tips implementasi: