feat(audit): admin audit log

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.
This commit is contained in:
fgbona
2026-03-29 19:39:05 -03:00
parent 6444b2b4ce
commit d04629605e
18 changed files with 548 additions and 18 deletions
+12
View File
@@ -13,6 +13,7 @@ import { authenticate, demoUploadBlock } from '../middleware/auth';
import { JWT_SECRET } from '../config';
import { encryptMfaSecret, decryptMfaSecret } from '../services/mfaCrypto';
import { AuthRequest, User } from '../types';
import { writeAudit, getClientIp } from '../services/auditLog';
authenticator.options = { window: 1 };
@@ -518,6 +519,15 @@ router.put('/app-settings', authenticate, (req: Request, res: Response) => {
if (allowed_file_types !== undefined) {
db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allowed_file_types', ?)").run(String(allowed_file_types));
}
writeAudit({
userId: authReq.user.id,
action: 'settings.app_update',
ip: getClientIp(req),
details: {
allow_registration: allow_registration !== undefined ? Boolean(allow_registration) : undefined,
allowed_file_types_changed: allowed_file_types !== undefined,
},
});
res.json({ success: true });
});
@@ -673,6 +683,7 @@ router.post('/mfa/enable', authenticate, (req: Request, res: Response) => {
authReq.user.id
);
mfaSetupPending.delete(authReq.user.id);
writeAudit({ userId: authReq.user.id, action: 'user.mfa_enable', ip: getClientIp(req) });
res.json({ success: true, mfa_enabled: true });
});
@@ -702,6 +713,7 @@ router.post('/mfa/disable', authenticate, rateLimiter(5, RATE_LIMIT_WINDOW), (re
authReq.user.id
);
mfaSetupPending.delete(authReq.user.id);
writeAudit({ userId: authReq.user.id, action: 'user.mfa_disable', ip: getClientIp(req) });
res.json({ success: true, mfa_enabled: false });
});