Fix legacy audit monotonic timestamps

Refs #1464
This commit is contained in:
rcourtman 2026-07-06 16:18:38 +01:00
parent 7ddc2fcb47
commit 4a597fa425
3 changed files with 54 additions and 0 deletions

View file

@ -560,6 +560,11 @@ uninitialized audit stores must surface as `audit_store_unavailable`. The
Audit Log settings surface may translate those stable API codes into recovery
copy, but it must not show raw internal server errors or collapse audit-store
state into a generic frontend failure.
The persistent audit reader must also tolerate legacy timestamp encodings that
were previously written into `audit_events.timestamp`, including Unix seconds,
SQLite datetime values, and Go wall-clock strings carrying a monotonic
`m=+...` suffix, so valid historical audit rows cannot make `/api/audit`
return `query_failed`.
That shared token-management boundary now also includes
`frontend-modern/src/utils/apiTokenPresentation.ts`, so API-token load,
generate, and revoke errors stay on one governed customer-facing wording path

View file

@ -465,6 +465,12 @@ func parseAuditTimestampString(raw string) (time.Time, error) {
if unix, err := strconv.ParseInt(raw, 10, 64); err == nil {
return time.Unix(unix, 0), nil
}
if monotonicIndex := strings.LastIndex(raw, " m="); monotonicIndex > 0 && monotonicIndex+3 < len(raw) {
sign := raw[monotonicIndex+3]
if sign == '+' || sign == '-' {
raw = strings.TrimSpace(raw[:monotonicIndex])
}
}
layouts := []string{
time.RFC3339Nano,

View file

@ -310,6 +310,49 @@ func TestSQLiteLoggerQueryHandlesLegacyDatetimeTimestampRows(t *testing.T) {
}
}
func TestSQLiteLoggerQueryHandlesLegacyGoMonotonicTimestampRows(t *testing.T) {
tempDir := t.TempDir()
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
DataDir: tempDir,
CryptoMgr: newMockCryptoManager(),
RetentionDays: 30,
})
if err != nil {
t.Fatalf("NewSQLiteLogger failed: %v", err)
}
defer logger.Close()
const legacyTimestamp = "2026-07-05 08:51:23.65653076 +0000 UTC m=+0.009025344"
want := time.Date(2026, 7, 5, 8, 51, 23, 656530760, time.UTC)
if _, err := logger.db.Exec(`
INSERT INTO audit_events (id, timestamp, event_type, user, ip, path, success, details, signature)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
"legacy-go-monotonic",
legacyTimestamp,
"startup",
sql.NullString{},
sql.NullString{},
"/api/audit",
1,
"legacy Go monotonic timestamp",
"legacy-signature",
); err != nil {
t.Fatalf("insert legacy Go monotonic row: %v", err)
}
events, err := logger.Query(QueryFilter{ID: "legacy-go-monotonic", Limit: 1})
if err != nil {
t.Fatalf("Query failed for legacy Go monotonic timestamp row: %v", err)
}
if len(events) != 1 {
t.Fatalf("Expected 1 event, got %d", len(events))
}
if !events[0].Timestamp.Equal(want) {
t.Fatalf("timestamp = %s, want %s", events[0].Timestamp, want)
}
}
func TestSQLiteLoggerCount(t *testing.T) {
tempDir := t.TempDir()