Pulse/internal/api/agent_command_authorization.go
2026-07-11 11:54:56 +01:00

46 lines
1.6 KiB
Go

package api
import (
"fmt"
"strings"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/agentexec"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/approval"
)
// verifyAndConsumeCommandAuthorization is the final server-owned authority
// boundary for approval-gated arbitrary agent commands. It validates the
// tenant and action identity before atomically consuming the exact
// command/target approval. Grant signing and WebSocket dispatch happen only
// after this function succeeds.
func verifyAndConsumeCommandAuthorization(req agentexec.CommandAuthorizationRequest) error {
store := approval.GetStore()
if store == nil {
return fmt.Errorf("approval store is unavailable")
}
approvalReq, found := store.GetApproval(strings.TrimSpace(req.ApprovalID))
if !found || approvalReq == nil {
return fmt.Errorf("approval not found")
}
if !approval.BelongsToOrg(approvalReq, req.OrgID) {
return fmt.Errorf("approval belongs to another org")
}
if approvalReq.Plan == nil || strings.TrimSpace(approvalReq.Plan.ActionID) == "" ||
strings.TrimSpace(approvalReq.Plan.ActionID) != strings.TrimSpace(req.ActionID) {
return fmt.Errorf("approval action does not match")
}
if approvalReq.Status != approval.StatusApproved {
return fmt.Errorf("approval is not approved (status: %s)", approvalReq.Status)
}
if approvalReq.Consumed {
return fmt.Errorf("approval has already been consumed")
}
if time.Now().After(approvalReq.ExpiresAt) {
return fmt.Errorf("approval has expired")
}
if _, err := store.ConsumeApproval(req.ApprovalID, req.Command, req.TargetType, req.TargetID); err != nil {
return err
}
return nil
}