Record sign-ins, new accounts, automatic log trimming and what settings changed
A check of every write path found gaps in what the audit log captured: - Sign-ins and sign-outs are now recorded with the IP they came from (sign-out is recorded first and can't block signing out). - A new account is recorded when it's created on first sign-in, including when the very first user becomes admin - so the log shows who gained access, not only who changed things. - The automatic log purge, which deletes audit entries, now records itself, attributed to "system". recordAudit() takes an optional actor for this. It only records when something was actually deleted. - Settings updates record what changed (before and after) instead of only which sections were touched. The notification channels (Gotify, ntfy, SMTP, webhook) record field names only: they hold credentials, and webhook URLs and public ntfy topics act as secrets, while the audit log is readable by operators and Settings is admin-only. - Integration edits record renames, enabling/disabling, whether credentials were replaced (never the credentials), and which settings fields changed (names only). The Privacy page and README now say sign-ins store an IP in the audit log. Verified through the real routes against a scratch database: user creation, the logout route, the automatic purge, settings and integration edits - including that a secret token and a webhook URL appear nowhere in the stored entries. The sign-in callback itself needs a real identity provider and wasn't run. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
22cfdbede0
commit
88d9c8e097
9 files changed
+129
-12
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { getSettings, getInternalFlag, setInternalFlag } from "./settingsStore.js";
|
||||
import { purgeOldLogs } from "./logRetention.js";
|
||||
import { recordAudit } from "./audit.js";
|
||||
|
||||
const LAST_RUN_FLAG = "logRetentionLastRunAt";
|
||||
|
||||
@@ -9,6 +10,12 @@ async function runPurge(): Promise<void> {
|
||||
const result = await purgeOldLogs(logRetention.retentionDays);
|
||||
await setInternalFlag(LAST_RUN_FLAG, new Date().toISOString());
|
||||
if (result.diagDeleted || result.auditDeleted) {
|
||||
// Trimming the audit log is itself something to be able to look back on — attributed to the system, since no one asked for it.
|
||||
await recordAudit({
|
||||
category: "settings",
|
||||
action: "purge_logs",
|
||||
detail: { automatic: true, retentionDays: logRetention.retentionDays, ...result },
|
||||
});
|
||||
console.log(
|
||||
`[logRetention] purged ${result.diagDeleted} diagnostic log and ${result.auditDeleted} audit log entries older than ${logRetention.retentionDays} days`,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user