Files
Homelab-manager/server/src/services/logRetentionScheduler.ts
T
bobbanandClaude Sonnet 5.5 88d9c8e097 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>
2026-10-02 22:40:46 +02:00

57 lines
2.4 KiB
TypeScript

import { getSettings, getInternalFlag, setInternalFlag } from "./settingsStore.js";
import { purgeOldLogs } from "./logRetention.js";
import { recordAudit } from "./audit.js";
const LAST_RUN_FLAG = "logRetentionLastRunAt";
async function runPurge(): Promise<void> {
const { logRetention } = await getSettings();
if (!logRetention.enabled) return;
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`,
);
}
}
let currentTimer: ReturnType<typeof setInterval> | null = null;
/** (Re)arms the periodic purge timer per the current settings. Call again after settings change. */
export async function scheduleLogRetentionPurge(): Promise<void> {
if (currentTimer) {
clearInterval(currentTimer);
currentTimer = null;
}
const { logRetention } = await getSettings();
if (!logRetention.enabled) {
console.log("[logRetention] automatic purge disabled");
return;
}
const intervalMs = logRetention.intervalHours * 60 * 60 * 1000;
currentTimer = setInterval(() => {
runPurge().catch((err) => console.error("[logRetention] purge failed:", err));
}, intervalMs);
console.log(`[logRetention] automatic purge scheduled every ${logRetention.intervalHours}h, keeping ${logRetention.retentionDays} days`);
}
/** Runs immediately at startup if a purge is overdue (e.g. the server was down past the interval), then arms the periodic timer. */
export async function initLogRetentionScheduler(): Promise<void> {
const { logRetention } = await getSettings();
if (logRetention.enabled) {
const lastRun = await getInternalFlag(LAST_RUN_FLAG);
const dueAt = lastRun ? new Date(lastRun).getTime() + logRetention.intervalHours * 60 * 60 * 1000 : 0;
if (Date.now() >= dueAt) {
await runPurge().catch((err) => console.error("[logRetention] purge failed:", err));
}
}
await scheduleLogRetentionPurge();
}