mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Audit writes stay best-effort — a LogAudit failure must never fail or abort the request — but a failed write must no longer be silently discarded. Add db.WriteAudit(auditor, actor, action, targetType, targetID, detail), which logs a failed write with actor/action/target context (never the detail string, which may be sensitive) and never propagates the error. The Auditor interface is satisfied structurally by both *db.DB and the service-layer Store, so api/admin/ws/service all reach the helper without an import cycle. Converts all ~26 call sites from `_ = LogAudit(...)` (and the two backup handlers' inline `if err` blocks) to db.WriteAudit. Pinned by db/audit_test.go: failure logged and not propagated, success logs nothing, detail never leaks. Resolves the repo-wide LogAudit policy question flagged by the D8 note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.0 KiB
Go
80 lines
2.0 KiB
Go
package db_test
|
|
|
|
import (
|
|
"errors"
|
|
"log/slog"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/owncord/server/db"
|
|
)
|
|
|
|
// fakeAuditor lets a test drive WriteAudit down either the success or the
|
|
// failure path without a real database.
|
|
type fakeAuditor struct {
|
|
err error
|
|
called bool
|
|
}
|
|
|
|
func (f *fakeAuditor) LogAudit(_ int64, _, _ string, _ int64, _ string) error {
|
|
f.called = true
|
|
return f.err
|
|
}
|
|
|
|
// captureLogs redirects the default slog logger to a buffer for the duration
|
|
// of fn and returns everything it wrote.
|
|
func captureLogs(t *testing.T, fn func()) string {
|
|
t.Helper()
|
|
var buf strings.Builder
|
|
prev := slog.Default()
|
|
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})))
|
|
defer slog.SetDefault(prev)
|
|
fn()
|
|
return buf.String()
|
|
}
|
|
|
|
func TestWriteAudit_LogsFailureButDoesNotPropagate(t *testing.T) {
|
|
a := &fakeAuditor{err: errors.New("disk on fire")}
|
|
|
|
// WriteAudit returns nothing, so "never propagated" is structural — the
|
|
// call simply must not panic and must record the failure.
|
|
out := captureLogs(t, func() {
|
|
db.WriteAudit(a, 7, "user_ban", "user", 42, "spam")
|
|
})
|
|
|
|
if !a.called {
|
|
t.Fatal("WriteAudit did not attempt the underlying LogAudit")
|
|
}
|
|
for _, want := range []string{
|
|
"audit log write failed",
|
|
"action=user_ban",
|
|
"actor_id=7",
|
|
"target_type=user",
|
|
"target_id=42",
|
|
"disk on fire",
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("failure log missing %q; got: %s", want, out)
|
|
}
|
|
}
|
|
// The detail string must not leak into logs.
|
|
if strings.Contains(out, "spam") {
|
|
t.Errorf("detail string leaked into audit failure log: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestWriteAudit_SuccessLogsNothing(t *testing.T) {
|
|
a := &fakeAuditor{err: nil}
|
|
|
|
out := captureLogs(t, func() {
|
|
db.WriteAudit(a, 1, "user_login", "user", 1, "")
|
|
})
|
|
|
|
if !a.called {
|
|
t.Fatal("WriteAudit did not attempt the underlying LogAudit")
|
|
}
|
|
if strings.Contains(out, "audit log write failed") {
|
|
t.Errorf("successful audit write should not log a failure; got: %s", out)
|
|
}
|
|
}
|