feat(phase-3): workstream 5 — Webhooks & Event Streaming
- 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>
This commit is contained in:
30
src/app.ts
30
src/app.ts
@@ -27,6 +27,10 @@ import { DIDService } from './services/DIDService.js';
|
||||
import { OIDCKeyService } from './services/OIDCKeyService.js';
|
||||
import { IDTokenService } from './services/IDTokenService.js';
|
||||
import { FederationService } from './services/FederationService.js';
|
||||
import { WebhookService } from './services/WebhookService.js';
|
||||
import { EventPublisher } from './services/EventPublisher.js';
|
||||
import { WebhookDeliveryWorker } from './workers/WebhookDeliveryWorker.js';
|
||||
import { createKafkaProducer } from './adapters/KafkaAdapter.js';
|
||||
|
||||
import { AgentController } from './controllers/AgentController.js';
|
||||
import { TokenController } from './controllers/TokenController.js';
|
||||
@@ -36,6 +40,7 @@ import { OrgController } from './controllers/OrgController.js';
|
||||
import { DIDController } from './controllers/DIDController.js';
|
||||
import { OIDCController } from './controllers/OIDCController.js';
|
||||
import { FederationController } from './controllers/FederationController.js';
|
||||
import { WebhookController } from './controllers/WebhookController.js';
|
||||
|
||||
import { createAgentsRouter } from './routes/agents.js';
|
||||
import { createTokenRouter } from './routes/token.js';
|
||||
@@ -47,6 +52,7 @@ import { createOrgsRouter } from './routes/organizations.js';
|
||||
import { createDIDRouter } from './routes/did.js';
|
||||
import { createOIDCRouter } from './routes/oidc.js';
|
||||
import { createFederationRouter } from './routes/federation.js';
|
||||
import { createWebhooksRouter } from './routes/webhooks.js';
|
||||
|
||||
import { errorHandler } from './middleware/errorHandler.js';
|
||||
import { createOpaMiddleware } from './middleware/opa.js';
|
||||
@@ -124,13 +130,30 @@ export async function createApp(): Promise<Application> {
|
||||
console.log('[AgentIdP] Vault integration enabled — new credentials will use Vault KV v2');
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Kafka (optional) — activated when KAFKA_BROKERS env var is set
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const kafkaProducer = await createKafkaProducer();
|
||||
if (kafkaProducer !== null) {
|
||||
console.log('[AgentIdP] Kafka integration enabled — events will be produced to agentidp-events');
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Webhook infrastructure
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const redisUrl = process.env['REDIS_URL'] ?? 'redis://localhost:6379';
|
||||
const webhookService = new WebhookService(pool, vaultClient, redis as RedisClientType);
|
||||
const webhookWorker = new WebhookDeliveryWorker(pool, vaultClient, redis as RedisClientType, redisUrl);
|
||||
webhookWorker.start();
|
||||
const eventPublisher = new EventPublisher(webhookWorker, pool, kafkaProducer);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Service layer
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const auditService = new AuditService(auditRepo);
|
||||
const didService = new DIDService(pool, vaultClient, redis as RedisClientType);
|
||||
const agentService = new AgentService(agentRepo, credentialRepo, auditService, didService);
|
||||
const credentialService = new CredentialService(credentialRepo, agentRepo, auditService, vaultClient);
|
||||
const agentService = new AgentService(agentRepo, credentialRepo, auditService, didService, eventPublisher);
|
||||
const credentialService = new CredentialService(credentialRepo, agentRepo, auditService, vaultClient, eventPublisher);
|
||||
const orgService = new OrgService(orgRepo, agentRepo);
|
||||
|
||||
const privateKey = process.env['JWT_PRIVATE_KEY'];
|
||||
@@ -153,6 +176,7 @@ export async function createApp(): Promise<Application> {
|
||||
publicKey,
|
||||
vaultClient,
|
||||
idTokenService,
|
||||
eventPublisher,
|
||||
);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
@@ -172,6 +196,7 @@ export async function createApp(): Promise<Application> {
|
||||
const oidcController = new OIDCController(oidcKeyService, agentRepo);
|
||||
const federationService = new FederationService(pool, redis as RedisClientType);
|
||||
const federationController = new FederationController(federationService);
|
||||
const webhookController = new WebhookController(webhookService);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Org context middleware — sets PostgreSQL session variable app.organization_id
|
||||
@@ -209,6 +234,7 @@ export async function createApp(): Promise<Application> {
|
||||
app.use(`${API_BASE}/audit`, createAuditRouter(auditController, opaMiddleware));
|
||||
app.use(`${API_BASE}/organizations`, createOrgsRouter(orgController, opaMiddleware));
|
||||
app.use(`${API_BASE}`, createFederationRouter(federationController, authMiddleware, opaMiddleware));
|
||||
app.use(`${API_BASE}/webhooks`, createWebhooksRouter(webhookController, authMiddleware, opaMiddleware));
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Dashboard static assets (served from dashboard/dist/)
|
||||
|
||||
Reference in New Issue
Block a user