mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 16:01:28 +00:00
261 lines
8.1 KiB
Go
261 lines
8.1 KiB
Go
package aitools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/warmbly/warmbly/internal/app/webhook"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
"github.com/warmbly/warmbly/internal/pkg/generation"
|
|
)
|
|
|
|
// Webhook endpoint tools, gated on PermManageSettings / APIPermWebhooks. New
|
|
// endpoints stay HMAC-signed and HTTPS-validated by the service exactly as the
|
|
// HTTP route does.
|
|
func (d Deps) registerWebhookTools(r *Registry) {
|
|
if d.Webhooks == nil {
|
|
return
|
|
}
|
|
|
|
r.Register(Tool{
|
|
Name: "list_webhooks",
|
|
Description: "List the workspace's webhook endpoints.",
|
|
InputSchema: objectSchema(map[string]any{}),
|
|
Risk: generation.RiskRead,
|
|
RequiredOrgPerm: models.PermManageSettings,
|
|
RequiredAPIPerm: models.APIPermWebhooks,
|
|
Handler: d.listWebhooks,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "create_webhook",
|
|
Description: "Create a webhook endpoint subscribed to event types. Must be an HTTPS URL. Returns the signing secret once.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"url": strProp("HTTPS endpoint URL (required)."),
|
|
"description": strProp("Optional description."),
|
|
"event_types": arrProp("Event types to subscribe to.", strProp("Event type, e.g. campaign.reply_received.")),
|
|
"enabled": boolProp("Whether the endpoint is active (default true)."),
|
|
}, "url"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageSettings,
|
|
RequiredAPIPerm: models.APIPermWebhooks,
|
|
Handler: d.createWebhook,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "update_webhook",
|
|
Description: "Update a webhook endpoint's URL, description, event subscriptions, and enabled state.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"webhook_id": strProp("The endpoint's UUID."),
|
|
"url": strProp("HTTPS endpoint URL (required)."),
|
|
"description": strProp("Description."),
|
|
"event_types": arrProp("Event types to subscribe to.", strProp("Event type.")),
|
|
"enabled": boolProp("Whether the endpoint is active."),
|
|
}, "webhook_id", "url"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageSettings,
|
|
RequiredAPIPerm: models.APIPermWebhooks,
|
|
Handler: d.updateWebhook,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "delete_webhook",
|
|
Description: "Delete a webhook endpoint. Destructive; requires user approval.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"webhook_id": strProp("The endpoint's UUID."),
|
|
}, "webhook_id"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageSettings,
|
|
RequiredAPIPerm: models.APIPermWebhooks,
|
|
Handler: d.deleteWebhook,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "rotate_webhook_secret",
|
|
Description: "Rotate a webhook endpoint's signing secret. Returns the new secret once.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"webhook_id": strProp("The endpoint's UUID."),
|
|
}, "webhook_id"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageSettings,
|
|
RequiredAPIPerm: models.APIPermWebhooks,
|
|
Handler: d.rotateWebhookSecret,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "verify_webhook",
|
|
Description: "Send a signed verification/test event to a webhook endpoint.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"webhook_id": strProp("The endpoint's UUID."),
|
|
}, "webhook_id"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageSettings,
|
|
RequiredAPIPerm: models.APIPermWebhooks,
|
|
Handler: d.verifyWebhook,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "list_webhook_deliveries",
|
|
Description: "List recent delivery attempts, optionally for one endpoint.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"webhook_id": strProp("Optional endpoint UUID filter."),
|
|
"limit": intProp("Max rows (1-100, default 50)."),
|
|
}),
|
|
Risk: generation.RiskRead,
|
|
RequiredOrgPerm: models.PermManageSettings,
|
|
RequiredAPIPerm: models.APIPermWebhooks,
|
|
Handler: d.listWebhookDeliveries,
|
|
})
|
|
}
|
|
|
|
func (d Deps) listWebhooks(ctx context.Context, inv Invocation, _ json.RawMessage) (string, error) {
|
|
endpoints, err := d.Webhooks.ListEndpoints(ctx, inv.OrgID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return jsonResult(map[string]any{"webhooks": endpoints, "count": len(endpoints)})
|
|
}
|
|
|
|
func (d Deps) createWebhook(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
URL string `json:"url"`
|
|
Description string `json:"description"`
|
|
EventTypes []string `json:"event_types"`
|
|
Enabled *bool `json:"enabled"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if in.URL == "" {
|
|
return "", ErrInvalidArgs
|
|
}
|
|
enabled := true
|
|
if in.Enabled != nil {
|
|
enabled = *in.Enabled
|
|
}
|
|
userID := inv.UserID
|
|
endpoint, cerr := d.Webhooks.CreateEndpoint(ctx, inv.OrgID, webhook.EndpointInput{
|
|
URL: in.URL,
|
|
Description: in.Description,
|
|
EventTypes: in.EventTypes,
|
|
Enabled: enabled,
|
|
CreatedBy: &userID,
|
|
})
|
|
if cerr != nil {
|
|
return "", cerr
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionCreate, models.AuditEntityWebhook, &endpoint.ID, nil)
|
|
return jsonResult(endpoint)
|
|
}
|
|
|
|
func (d Deps) updateWebhook(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
WebhookID string `json:"webhook_id"`
|
|
URL string `json:"url"`
|
|
Description string `json:"description"`
|
|
EventTypes []string `json:"event_types"`
|
|
Enabled *bool `json:"enabled"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
wid, err := parseUUIDArg(in.WebhookID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if in.URL == "" {
|
|
return "", ErrInvalidArgs
|
|
}
|
|
enabled := true
|
|
if in.Enabled != nil {
|
|
enabled = *in.Enabled
|
|
}
|
|
endpoint, uerr := d.Webhooks.UpdateEndpoint(ctx, inv.OrgID, wid, in.URL, in.Description, in.EventTypes, enabled)
|
|
if uerr != nil {
|
|
return "", uerr
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionUpdate, models.AuditEntityWebhook, &wid, nil)
|
|
return jsonResult(endpoint)
|
|
}
|
|
|
|
func (d Deps) deleteWebhook(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
WebhookID string `json:"webhook_id"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
wid, err := parseUUIDArg(in.WebhookID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if derr := d.Webhooks.DeleteEndpoint(ctx, inv.OrgID, wid); derr != nil {
|
|
return "", derr
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionDelete, models.AuditEntityWebhook, &wid, nil)
|
|
return jsonResult(map[string]any{"ok": true, "webhook_id": wid.String()})
|
|
}
|
|
|
|
func (d Deps) rotateWebhookSecret(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
WebhookID string `json:"webhook_id"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
wid, err := parseUUIDArg(in.WebhookID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
secret, rerr := d.Webhooks.RotateSecret(ctx, inv.OrgID, wid)
|
|
if rerr != nil {
|
|
return "", rerr
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionRotate, models.AuditEntityWebhook, &wid, nil)
|
|
return jsonResult(map[string]any{"ok": true, "webhook_id": wid.String(), "secret": secret})
|
|
}
|
|
|
|
func (d Deps) verifyWebhook(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
WebhookID string `json:"webhook_id"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
wid, err := parseUUIDArg(in.WebhookID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if verr := d.Webhooks.VerifyEndpoint(ctx, inv.OrgID, wid); verr != nil {
|
|
return "", verr
|
|
}
|
|
return jsonResult(map[string]any{"ok": true, "webhook_id": wid.String()})
|
|
}
|
|
|
|
func (d Deps) listWebhookDeliveries(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
WebhookID string `json:"webhook_id"`
|
|
Limit int `json:"limit"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
limit := in.Limit
|
|
if limit <= 0 || limit > 100 {
|
|
limit = 50
|
|
}
|
|
filter := models.WebhookDeliveryFilter{Limit: limit}
|
|
if in.WebhookID != "" {
|
|
wid, perr := parseUUIDArg(in.WebhookID)
|
|
if perr != nil {
|
|
return "", perr
|
|
}
|
|
filter.EndpointID = &wid
|
|
}
|
|
res, derr := d.Webhooks.ListDeliveries(ctx, inv.OrgID, filter)
|
|
if derr != nil {
|
|
return "", derr
|
|
}
|
|
return jsonResult(res)
|
|
}
|