trading-platform-frontend-v2/src/modules/admin/hooks/useAuditLogs.ts
Adrian Flores Cortes 954da4656c [Sprint-2] feat: Add hooks for Feature Flags, 2FA, and Audit
Created React Query hooks:
- useFeatureFlags.ts: Feature flag evaluation and management
- use2FA.ts: Two-factor authentication setup/verify/disable
- useAuditLogs.ts: Audit logs query with filters and stats

Hooks include:
- useFlagCheck() for simple feature checks
- useFeatures() for multiple flags
- use2FAStatus(), useSetup2FA(), useEnable2FA()
- useAuditLogs(), useAuditStats(), useSecurityEvents()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:38:25 -06:00

307 lines
9.1 KiB
TypeScript

/**
* useAuditLogs Hook
* React Query hooks for Audit Logs management
* @created Sprint 2 - TASK-2026-01-30-ANALISIS-INTEGRACION
*/
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../../../lib/apiClient';
// ============================================================================
// Types
// ============================================================================
export type AuditEventType =
| 'auth.login'
| 'auth.logout'
| 'auth.register'
| 'auth.password_change'
| 'auth.2fa_enabled'
| 'auth.2fa_disabled'
| 'user.profile_update'
| 'user.settings_change'
| 'trading.order_placed'
| 'trading.order_cancelled'
| 'investment.deposit'
| 'investment.withdrawal'
| 'payment.subscription'
| 'payment.refund'
| 'admin.user_suspend'
| 'admin.user_activate'
| 'system.config_change';
export type EventSeverity = 'info' | 'warning' | 'error' | 'critical';
export type EventStatus = 'success' | 'failure' | 'pending';
export interface AuditLog {
id: string;
eventType: AuditEventType;
eventStatus: EventStatus;
severity: EventSeverity;
userId: string | null;
sessionId: string | null;
ipAddress: string | null;
userAgent: string | null;
resourceType: string;
resourceId: string | null;
resourceName: string | null;
action: string;
description: string | null;
oldValues: Record<string, unknown> | null;
newValues: Record<string, unknown> | null;
metadata: Record<string, unknown>;
requestId: string | null;
correlationId: string | null;
serviceName: string | null;
createdAt: Date;
}
export interface AuditLogFilters {
userId?: string;
eventType?: AuditEventType;
resourceType?: string;
resourceId?: string;
severity?: EventSeverity;
dateFrom?: string;
dateTo?: string;
limit?: number;
offset?: number;
}
export interface AuditStats {
totalLogs: number;
byEventType: Partial<Record<AuditEventType, number>>;
bySeverity: Partial<Record<EventSeverity, number>>;
criticalEvents: number;
}
export interface SecurityEvent {
id: string;
category: string;
severity: EventSeverity;
eventStatus: EventStatus;
userId: string | null;
ipAddress: string;
userAgent: string | null;
eventCode: string;
eventName: string;
description: string | null;
isBlocked: boolean;
blockReason: string | null;
requiresReview: boolean;
createdAt: Date;
}
export interface SecurityEventFilters {
userId?: string;
category?: string;
severity?: EventSeverity;
isBlocked?: boolean;
requiresReview?: boolean;
dateFrom?: string;
dateTo?: string;
limit?: number;
offset?: number;
}
// ============================================================================
// API Functions
// ============================================================================
async function getAuditLogs(filters: AuditLogFilters = {}): Promise<AuditLog[]> {
const params = new URLSearchParams();
if (filters.userId) params.append('userId', filters.userId);
if (filters.eventType) params.append('eventType', filters.eventType);
if (filters.resourceType) params.append('resourceType', filters.resourceType);
if (filters.resourceId) params.append('resourceId', filters.resourceId);
if (filters.severity) params.append('severity', filters.severity);
if (filters.dateFrom) params.append('dateFrom', filters.dateFrom);
if (filters.dateTo) params.append('dateTo', filters.dateTo);
if (filters.limit) params.append('limit', filters.limit.toString());
if (filters.offset) params.append('offset', filters.offset.toString());
const response = await apiClient.get(`/audit/logs?${params.toString()}`);
return response.data.data || [];
}
async function getAuditStats(dateFrom?: string, dateTo?: string): Promise<AuditStats> {
const params = new URLSearchParams();
if (dateFrom) params.append('dateFrom', dateFrom);
if (dateTo) params.append('dateTo', dateTo);
const response = await apiClient.get(`/audit/stats?${params.toString()}`);
return response.data.data;
}
async function getSecurityEvents(filters: SecurityEventFilters = {}): Promise<SecurityEvent[]> {
const params = new URLSearchParams();
if (filters.userId) params.append('userId', filters.userId);
if (filters.category) params.append('category', filters.category);
if (filters.severity) params.append('severity', filters.severity);
if (filters.isBlocked !== undefined) params.append('isBlocked', filters.isBlocked.toString());
if (filters.requiresReview !== undefined) params.append('requiresReview', filters.requiresReview.toString());
if (filters.dateFrom) params.append('dateFrom', filters.dateFrom);
if (filters.dateTo) params.append('dateTo', filters.dateTo);
if (filters.limit) params.append('limit', filters.limit.toString());
if (filters.offset) params.append('offset', filters.offset.toString());
const response = await apiClient.get(`/audit/security-events?${params.toString()}`);
return response.data.data || [];
}
async function getUserAuditLogs(userId: string, limit = 50): Promise<AuditLog[]> {
return getAuditLogs({ userId, limit });
}
// ============================================================================
// React Query Hooks
// ============================================================================
/**
* Get audit logs with optional filters
*/
export function useAuditLogs(filters: AuditLogFilters = {}) {
return useQuery({
queryKey: ['audit', 'logs', filters],
queryFn: () => getAuditLogs(filters),
staleTime: 30 * 1000, // 30 seconds
});
}
/**
* Get audit stats for dashboard
*/
export function useAuditStats(dateFrom?: string, dateTo?: string) {
return useQuery({
queryKey: ['audit', 'stats', dateFrom, dateTo],
queryFn: () => getAuditStats(dateFrom, dateTo),
staleTime: 60 * 1000, // 1 minute
});
}
/**
* Get security events with optional filters
*/
export function useSecurityEvents(filters: SecurityEventFilters = {}) {
return useQuery({
queryKey: ['audit', 'security-events', filters],
queryFn: () => getSecurityEvents(filters),
staleTime: 30 * 1000, // 30 seconds
});
}
/**
* Get audit logs for a specific user
*/
export function useUserAuditLogs(userId: string, limit = 50) {
return useQuery({
queryKey: ['audit', 'user', userId, limit],
queryFn: () => getUserAuditLogs(userId, limit),
enabled: !!userId,
staleTime: 30 * 1000,
});
}
// ============================================================================
// Helper Functions
// ============================================================================
/**
* Get user-friendly label for event type
*/
export function getEventTypeLabel(eventType: AuditEventType): string {
const labels: Record<AuditEventType, string> = {
'auth.login': 'Login',
'auth.logout': 'Logout',
'auth.register': 'Registration',
'auth.password_change': 'Password Change',
'auth.2fa_enabled': '2FA Enabled',
'auth.2fa_disabled': '2FA Disabled',
'user.profile_update': 'Profile Update',
'user.settings_change': 'Settings Change',
'trading.order_placed': 'Order Placed',
'trading.order_cancelled': 'Order Cancelled',
'investment.deposit': 'Deposit',
'investment.withdrawal': 'Withdrawal',
'payment.subscription': 'Subscription',
'payment.refund': 'Refund',
'admin.user_suspend': 'User Suspended',
'admin.user_activate': 'User Activated',
'system.config_change': 'System Config Change',
};
return labels[eventType] || eventType;
}
/**
* Get severity color for styling
*/
export function getSeverityColor(severity: EventSeverity): string {
const colors: Record<EventSeverity, string> = {
info: 'text-blue-500',
warning: 'text-yellow-500',
error: 'text-red-500',
critical: 'text-red-700',
};
return colors[severity] || 'text-gray-500';
}
/**
* Get severity badge styles
*/
export function getSeverityBadgeClass(severity: EventSeverity): string {
const classes: Record<EventSeverity, string> = {
info: 'bg-blue-100 text-blue-800',
warning: 'bg-yellow-100 text-yellow-800',
error: 'bg-red-100 text-red-800',
critical: 'bg-red-200 text-red-900 font-bold',
};
return classes[severity] || 'bg-gray-100 text-gray-800';
}
/**
* Get status badge styles
*/
export function getStatusBadgeClass(status: EventStatus): string {
const classes: Record<EventStatus, string> = {
success: 'bg-green-100 text-green-800',
failure: 'bg-red-100 text-red-800',
pending: 'bg-yellow-100 text-yellow-800',
};
return classes[status] || 'bg-gray-100 text-gray-800';
}
/**
* Format date for display
*/
export function formatAuditDate(date: Date | string): string {
const d = typeof date === 'string' ? new Date(date) : date;
return d.toLocaleString('es-MX', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
}
// ============================================================================
// Export
// ============================================================================
export const auditHooks = {
useAuditLogs,
useAuditStats,
useSecurityEvents,
useUserAuditLogs,
getEventTypeLabel,
getSeverityColor,
getSeverityBadgeClass,
getStatusBadgeClass,
formatAuditDate,
};
export default auditHooks;