Spaces:
Runtime error
Runtime error
| 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}) | |
| } | |