matrixcloud / internal /store /audit.go
github-actions[bot]
Deploy from 4dfd54f2
857a91b
Raw
History Blame Contribute Delete
2.02 kB
package store
import (
"encoding/json"
"time"
"github.com/agent-matrix/matrix-runtime/internal/auth"
)
// AuditEvent is one row in the append-only audit log.
type AuditEvent struct {
ID string `json:"id"`
WorkspaceID string `json:"workspace_id"`
Actor string `json:"actor"`
Action string `json:"action"`
Target string `json:"target"`
IP string `json:"ip"`
Status string `json:"status"`
Meta map[string]any `json:"meta"`
CreatedAt time.Time `json:"created_at"`
}
// RecordAudit appends an audit event. Failures are non-fatal to callers (audit
// is best-effort) but the error is returned so it can be logged.
func (s *Store) RecordAudit(e AuditEvent) error {
if e.Status == "" {
e.Status = "success"
}
if e.ID == "" {
e.ID = auth.NewID("au_")
}
metaB, _ := json.Marshal(e.Meta)
_, err := s.exec(`INSERT INTO audit_events (id,workspace_id,actor,action,target,ip,status,meta_json,created_at)
VALUES (?,?,?,?,?,?,?,?,?)`,
e.ID, e.WorkspaceID, e.Actor, e.Action, e.Target, e.IP, e.Status, string(metaB), nowRFC())
return err
}
// ListAudit returns a workspace's most recent audit events (newest first).
func (s *Store) ListAudit(workspaceID string, limit int) ([]AuditEvent, error) {
if limit <= 0 || limit > 500 {
limit = 100
}
rows, err := s.query(`SELECT id,workspace_id,actor,action,target,ip,status,meta_json,created_at
FROM audit_events WHERE workspace_id=? ORDER BY created_at DESC LIMIT ?`, workspaceID, limit)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
out := []AuditEvent{}
for rows.Next() {
var e AuditEvent
var meta, created string
if err := rows.Scan(&e.ID, &e.WorkspaceID, &e.Actor, &e.Action, &e.Target, &e.IP, &e.Status, &meta, &created); err != nil {
return nil, err
}
_ = json.Unmarshal([]byte(meta), &e.Meta)
e.CreatedAt, _ = time.Parse(time.RFC3339, created)
out = append(out, e)
}
return out, rows.Err()
}