package settings import ( "encoding/json" "errors" "fmt" "os" "path/filepath" ) // MaxAuthAuditEvents is the retention cap for auth-audit.json. const MaxAuthAuditEvents = 1000 // LoadAuthAudit reads and parses auth-audit.json from dir. func LoadAuthAudit(dir string) (AuthAuditStore, error) { path := filepath.Join(dir, AuthAuditFileName) payload, err := os.ReadFile(path) if err != nil { return AuthAuditStore{}, err } var store AuthAuditStore if err := json.Unmarshal(payload, &store); err != nil { return AuthAuditStore{}, fmt.Errorf("parse auth audit: %w", err) } if store.Events == nil { store.Events = []AuthAuditEvent{} } return store, nil } // LoadAuthAuditOrEmpty returns an empty store when auth-audit.json is missing. func LoadAuthAuditOrEmpty(dir string) (AuthAuditStore, error) { store, err := LoadAuthAudit(dir) if err != nil { if errors.Is(err, os.ErrNotExist) { return AuthAuditStore{Events: []AuthAuditEvent{}}, nil } return AuthAuditStore{}, err } return store, nil } // SaveAuthAudit writes auth-audit.json to dir with mode 0640. func SaveAuthAudit(dir string, store AuthAuditStore) error { if err := ensureConfigDir(dir); err != nil { return err } if store.Events == nil { store.Events = []AuthAuditEvent{} } payload, err := json.MarshalIndent(store, "", " ") if err != nil { return fmt.Errorf("encode auth audit: %w", err) } payload = append(payload, '\n') path := filepath.Join(dir, AuthAuditFileName) if err := os.WriteFile(path, payload, 0o640); err != nil { return fmt.Errorf("write auth audit: %w", err) } return nil } // AppendAuthAuditEvent appends event and trims to MaxAuthAuditEvents (newest kept). func AppendAuthAuditEvent(dir string, event AuthAuditEvent) error { store, err := LoadAuthAuditOrEmpty(dir) if err != nil { return err } store.Events = append(store.Events, event) if len(store.Events) > MaxAuthAuditEvents { store.Events = store.Events[len(store.Events)-MaxAuthAuditEvents:] } return SaveAuthAudit(dir, store) }