package store
import (
"encoding/binary"
"encoding/json"
"time"
bolt "go.etcd.io/bbolt"
)
// AuditEvent is one entry in the instance's audit trail, per DESIGN.md §9
// Phase 3 ("Audit log des accès cross-instance").
type AuditEvent struct {
Time time.Time `json:"time"`
Action string `json:"action"` // e.g. "ssh-auth", "git-upload-pack", "git-receive-pack", "trust-discover", "trust-approve"
Principal string `json:"principal,omitempty"`
Repo string `json:"repo,omitempty"`
Domain string `json:"domain,omitempty"`
Allowed bool `json:"allowed"`
Detail string `json:"detail,omitempty"`
}
// AppendAudit records an event. Entries are stored in insertion order under
// a monotonically increasing sequence key.
func (s *Store) AppendAudit(e AuditEvent) error {
e.Time = time.Now().UTC()
return s.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketAudit)
seq, err := b.NextSequence()
if err != nil {
return err
}
key := make([]byte, 8)
binary.BigEndian.PutUint64(key, seq)
data, err := json.Marshal(e)
if err != nil {
return err
}
return b.Put(key, data)
})
}
// ListAudit returns up to limit of the most recent events, newest first.
func (s *Store) ListAudit(limit int) ([]AuditEvent, error) {
var out []AuditEvent
err := s.db.View(func(tx *bolt.Tx) error {
c := tx.Bucket(bucketAudit).Cursor()
for k, v := c.Last(); k != nil && len(out) < limit; k, v = c.Prev() {
var e AuditEvent
if err := json.Unmarshal(v, &e); err != nil {
return err
}
out = append(out, e)
}
return nil
})
return out, err
}