Audit log - Add audit_log table (migration + schema) with index on created_at. - Add auditLog service (writeAudit, getClientIp) and record events for backups (create, restore, upload-restore, delete, auto-settings), admin actions (users, OIDC, invites, system update, demo baseline, bag tracking, packing template delete, addons), and auth (app settings, MFA enable/disable). - Add GET /api/admin/audit-log with pagination; fix invite insert row id lookup. - Add AuditLogPanel and Admin tab; adminApi.auditLog. - Add admin.tabs.audit and admin.audit.* strings in all locale files. Note: Rebase feature branches so new DB migrations stay after existing ones (e.g. file_links) when merging upstream.
31 lines
1.1 KiB
TypeScript
31 lines
1.1 KiB
TypeScript
import { Request } from 'express';
|
|
import { db } from '../db/database';
|
|
|
|
export function getClientIp(req: Request): string | null {
|
|
const xff = req.headers['x-forwarded-for'];
|
|
if (typeof xff === 'string') {
|
|
const first = xff.split(',')[0]?.trim();
|
|
return first || null;
|
|
}
|
|
if (Array.isArray(xff) && xff[0]) return String(xff[0]).trim() || null;
|
|
return req.socket?.remoteAddress || null;
|
|
}
|
|
|
|
/** Best-effort; never throws — failures are logged only. */
|
|
export function writeAudit(entry: {
|
|
userId: number | null;
|
|
action: string;
|
|
resource?: string | null;
|
|
details?: Record<string, unknown>;
|
|
ip?: string | null;
|
|
}): void {
|
|
try {
|
|
const detailsJson = entry.details && Object.keys(entry.details).length > 0 ? JSON.stringify(entry.details) : null;
|
|
db.prepare(
|
|
`INSERT INTO audit_log (user_id, action, resource, details, ip) VALUES (?, ?, ?, ?, ?)`
|
|
).run(entry.userId, entry.action, entry.resource ?? null, detailsJson, entry.ip ?? null);
|
|
} catch (e) {
|
|
console.error('[audit] write failed:', e instanceof Error ? e.message : e);
|
|
}
|
|
}
|