mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-29 01:27:34 +00:00
46 lines
1.6 KiB
Go
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
|
|
}
|