matrixcloud / api /audit.go
github-actions[bot]
Deploy from 4dfd54f2
857a91b
Raw
History Blame Contribute Delete
1.8 kB
package api
import (
"log"
"net"
"net/http"
"strings"
"github.com/agent-matrix/matrix-runtime/internal/store"
)
// clientIP best-effort extracts the caller IP, honoring common proxy headers
// (the API typically runs behind Cloudflare / an ingress).
func clientIP(r *http.Request) string {
if v := r.Header.Get("CF-Connecting-IP"); v != "" {
return v
}
if v := r.Header.Get("X-Forwarded-For"); v != "" {
return strings.TrimSpace(strings.Split(v, ",")[0])
}
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
// audit records a sensitive action. It is best-effort: a nil store or a write
// error never affects the request outcome (only logged).
func (s *Server) audit(r *http.Request, workspaceID, actor, action, target, status string, meta map[string]any) {
if s.store == nil {
return
}
if err := s.store.RecordAudit(store.AuditEvent{
WorkspaceID: workspaceID,
Actor: actor,
Action: action,
Target: target,
IP: clientIP(r),
Status: status,
Meta: meta,
}); err != nil {
log.Printf("audit: could not record %s: %v", action, err)
}
}
// truncate shortens s to at most n characters for audit targets.
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "…"
}
// handleCloudAudit returns the workspace's recent audit events.
func (s *Server) handleCloudAudit(w http.ResponseWriter, r *http.Request) {
u, ok := s.currentUser(r)
if !ok {
writeError(w, http.StatusUnauthorized, "not authenticated")
return
}
events, err := s.store.ListAudit(u.WorkspaceID, 200)
if err != nil {
writeError(w, http.StatusInternalServerError, "could not load audit log")
return
}
writeJSON(w, http.StatusOK, map[string]any{"events": events})
}