- DB migrations 016/017: webhook_subscriptions and webhook_deliveries tables - WebhookService: CRUD for subscriptions, Vault-backed secret storage, delivery history - WebhookDeliveryWorker: Bull queue, HMAC-SHA256 signatures, exponential backoff, SSRF protection (RFC 1918 + loopback + link-local rejection), dead-letter handling - EventPublisher: publishes 10 event types (agent/credential/token lifecycle); optional Kafka adapter activated via KAFKA_BROKERS env var - AgentService, CredentialService, OAuth2Service: wired to EventPublisher - WebhookController + routes: 6 endpoints with webhooks:read / webhooks:write scope guards - KafkaAdapter: optional Kafka producer (kafkajs), no-op when KAFKA_BROKERS unset - OAuthScope extended: webhooks:read, webhooks:write - AuditAction extended: webhook.created, webhook.updated, webhook.deleted - Metrics: agentidp_webhook_dead_letters_total counter added to registry - 523 unit tests passing; TypeScript strict throughout, zero `any` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
20 lines
1.4 KiB
SQL
20 lines
1.4 KiB
SQL
-- webhook_subscriptions: per-organization webhook delivery targets.
|
|
-- Each subscription registers a URL to receive HTTP POST callbacks for specified event types.
|
|
-- The HMAC signing secret is stored in Vault (vault_secret_path) when Vault is configured,
|
|
-- or bcrypt-hashed into secret_hash in local mode. The raw secret is never stored in PostgreSQL.
|
|
CREATE TABLE IF NOT EXISTS webhook_subscriptions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
url VARCHAR(2048) NOT NULL, -- HTTPS delivery target
|
|
events JSONB NOT NULL DEFAULT '[]', -- array of WebhookEventType strings
|
|
secret_hash VARCHAR(255) NOT NULL, -- bcrypt hash (local mode) or 'vault' sentinel
|
|
vault_secret_path VARCHAR(512) NOT NULL, -- Vault KV path for HMAC signing secret, or 'local'
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_webhook_subscriptions_organization_id ON webhook_subscriptions(organization_id);
|
|
CREATE INDEX IF NOT EXISTS idx_webhook_subscriptions_active ON webhook_subscriptions(active);
|