Files
goclaw/internal/http/delegations.go
T
Goon 75c570e951 feat(security): credentialed exec + HTTP RBAC + API key cache (#197)
- Secure CLI credential injection via AES-256-GCM encrypted env vars
- API key management with fine-grained RBAC scopes
- resolveAuth/requireAuth middleware across all 25+ HTTP handlers
- In-memory API key cache with TTL, negative caching, pubsub invalidation
- Sandbox-first execution (fails if unavailable, no silent fallback)
- Credential scrubbing, constant-time token comparison, Admin-only CLI creds
- SQL migration 000020: secure_cli_binaries + api_keys tables
- 14 unit tests for cache and RBAC with race detector

Closes #197
2026-03-15 20:13:18 +07:00

99 lines
2.6 KiB
Go

package http
import (
"net/http"
"strconv"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/i18n"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
// DelegationsHandler handles delegation history HTTP endpoints.
type DelegationsHandler struct {
teamStore store.TeamStore
token string
}
func NewDelegationsHandler(teamStore store.TeamStore, token string) *DelegationsHandler {
return &DelegationsHandler{teamStore: teamStore, token: token}
}
func (h *DelegationsHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /v1/delegations", h.authMiddleware(h.handleList))
mux.HandleFunc("GET /v1/delegations/{id}", h.authMiddleware(h.handleGet))
}
func (h *DelegationsHandler) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return requireAuth(h.token, "", next)
}
func (h *DelegationsHandler) handleList(w http.ResponseWriter, r *http.Request) {
opts := store.DelegationHistoryListOpts{
Limit: 50,
Offset: 0,
}
if v := r.URL.Query().Get("source_agent_id"); v != "" {
if id, err := uuid.Parse(v); err == nil {
opts.SourceAgentID = &id
}
}
if v := r.URL.Query().Get("target_agent_id"); v != "" {
if id, err := uuid.Parse(v); err == nil {
opts.TargetAgentID = &id
}
}
if v := r.URL.Query().Get("team_id"); v != "" {
if id, err := uuid.Parse(v); err == nil {
opts.TeamID = &id
}
}
if v := r.URL.Query().Get("user_id"); v != "" {
opts.UserID = v
}
if v := r.URL.Query().Get("status"); v != "" {
opts.Status = v
}
if v := r.URL.Query().Get("limit"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 {
opts.Limit = n
}
}
if v := r.URL.Query().Get("offset"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n >= 0 {
opts.Offset = n
}
}
records, total, err := h.teamStore.ListDelegationHistory(r.Context(), opts)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]any{
"records": records,
"total": total,
})
}
func (h *DelegationsHandler) handleGet(w http.ResponseWriter, r *http.Request) {
locale := store.LocaleFromContext(r.Context())
idStr := r.PathValue("id")
id, err := uuid.Parse(idStr)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "delegation")})
return
}
record, err := h.teamStore.GetDelegationHistory(r.Context(), id)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, record)
}