feat: Implement centralized audit logging admin interface (refs #10)
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 4m42s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Failing after 6s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 6s
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 4m42s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Failing after 6s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 6s
- Add audit_logs table with categories, severities, and indexes - Create AuditLogService and AuditLogRepository - Add REST API endpoints for viewing and exporting logs - Wire audit logging into auth, vehicles, admin, and backup features - Add desktop AdminLogsPage with filters and CSV export - Add mobile AdminLogsMobileScreen with card layout - Implement 90-day retention cleanup job - Remove old AuditLogPanel from AdminCatalogPage Security fixes: - Escape LIKE special characters to prevent pattern injection - Limit CSV export to 5000 records to prevent memory exhaustion - Add truncation warning headers for large exports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import * as path from 'path';
|
||||
import { isValidVIN, isValidPreModernVIN } from '../../../shared-minimal/utils/validators';
|
||||
import { normalizeMakeName, normalizeModelName } from './name-normalizer';
|
||||
import { getVehicleDataService, getPool } from '../../platform';
|
||||
import { auditLogService } from '../../audit-log';
|
||||
|
||||
export class VehiclesService {
|
||||
private readonly cachePrefix = 'vehicles';
|
||||
@@ -61,9 +62,20 @@ export class VehiclesService {
|
||||
// Invalidate user's vehicle list cache
|
||||
await this.invalidateUserCache(userId);
|
||||
|
||||
// Log vehicle creation to unified audit log
|
||||
const vehicleDesc = [vehicle.year, vehicle.make, vehicle.model].filter(Boolean).join(' ');
|
||||
await auditLogService.info(
|
||||
'vehicle',
|
||||
userId,
|
||||
`Vehicle created: ${vehicleDesc || vehicle.id}`,
|
||||
'vehicle',
|
||||
vehicle.id,
|
||||
{ vin: vehicle.vin, make: vehicle.make, model: vehicle.model, year: vehicle.year }
|
||||
).catch(err => logger.error('Failed to log vehicle create audit event', { error: err }));
|
||||
|
||||
return this.toResponse(vehicle);
|
||||
}
|
||||
|
||||
|
||||
async getUserVehicles(userId: string): Promise<VehicleResponse[]> {
|
||||
const cacheKey = `${this.cachePrefix}:user:${userId}`;
|
||||
|
||||
@@ -154,9 +166,20 @@ export class VehiclesService {
|
||||
// Invalidate cache
|
||||
await this.invalidateUserCache(userId);
|
||||
|
||||
// Log vehicle update to unified audit log
|
||||
const vehicleDesc = [updated.year, updated.make, updated.model].filter(Boolean).join(' ');
|
||||
await auditLogService.info(
|
||||
'vehicle',
|
||||
userId,
|
||||
`Vehicle updated: ${vehicleDesc || id}`,
|
||||
'vehicle',
|
||||
id,
|
||||
{ updatedFields: Object.keys(data) }
|
||||
).catch(err => logger.error('Failed to log vehicle update audit event', { error: err }));
|
||||
|
||||
return this.toResponse(updated);
|
||||
}
|
||||
|
||||
|
||||
async deleteVehicle(id: string, userId: string): Promise<void> {
|
||||
// Verify ownership
|
||||
const existing = await this.repository.findById(id);
|
||||
@@ -225,6 +248,17 @@ export class VehiclesService {
|
||||
|
||||
// Invalidate cache
|
||||
await this.invalidateUserCache(userId);
|
||||
|
||||
// Log vehicle deletion to unified audit log
|
||||
const vehicleDesc = [existing.year, existing.make, existing.model].filter(Boolean).join(' ');
|
||||
await auditLogService.info(
|
||||
'vehicle',
|
||||
userId,
|
||||
`Vehicle deleted: ${vehicleDesc || id}`,
|
||||
'vehicle',
|
||||
id,
|
||||
{ vin: existing.vin, make: existing.make, model: existing.model, year: existing.year }
|
||||
).catch(err => logger.error('Failed to log vehicle delete audit event', { error: err }));
|
||||
}
|
||||
|
||||
async getVehicleRaw(id: string, userId: string): Promise<Vehicle | null> {
|
||||
|
||||
Reference in New Issue
Block a user