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:
@@ -7,6 +7,7 @@ import { AgentRepository } from '../repositories/AgentRepository.js';
|
||||
import { CredentialRepository } from '../repositories/CredentialRepository.js';
|
||||
import { AuditService } from './AuditService.js';
|
||||
import { DIDService } from './DIDService.js';
|
||||
import { EventPublisher } from './EventPublisher.js';
|
||||
import {
|
||||
IAgent,
|
||||
ICreateAgentRequest,
|
||||
@@ -36,12 +37,15 @@ export class AgentService {
|
||||
* @param didService - Optional DIDService. When provided, a W3C DID is generated for each
|
||||
* newly registered agent. When null/undefined, DID generation is skipped
|
||||
* (backward-compatible default).
|
||||
* @param eventPublisher - Optional EventPublisher. When provided, lifecycle events are
|
||||
* published as webhooks and Kafka messages (fire-and-forget).
|
||||
*/
|
||||
constructor(
|
||||
private readonly agentRepository: AgentRepository,
|
||||
private readonly credentialRepository: CredentialRepository,
|
||||
private readonly auditService: AuditService,
|
||||
private readonly didService: DIDService | null = null,
|
||||
private readonly eventPublisher: EventPublisher | null = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -96,6 +100,13 @@ export class AgentService {
|
||||
// Instrument: count successful agent registrations
|
||||
agentsRegisteredTotal.inc({ deployment_env: data.deploymentEnv });
|
||||
|
||||
// Publish event (fire-and-forget)
|
||||
void this.eventPublisher?.publishEvent(
|
||||
agent.organizationId,
|
||||
'agent.created',
|
||||
{ agentId: agent.agentId, email: agent.email, name: agent.owner },
|
||||
);
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
@@ -184,6 +195,33 @@ export class AgentService {
|
||||
{ updatedFields: Object.keys(data) },
|
||||
);
|
||||
|
||||
// Publish lifecycle event (fire-and-forget)
|
||||
if (auditAction === 'agent.updated') {
|
||||
void this.eventPublisher?.publishEvent(
|
||||
updated.organizationId,
|
||||
'agent.updated',
|
||||
{ agentId, changes: data },
|
||||
);
|
||||
} else if (auditAction === 'agent.suspended') {
|
||||
void this.eventPublisher?.publishEvent(
|
||||
updated.organizationId,
|
||||
'agent.suspended',
|
||||
{ agentId },
|
||||
);
|
||||
} else if (auditAction === 'agent.reactivated') {
|
||||
void this.eventPublisher?.publishEvent(
|
||||
updated.organizationId,
|
||||
'agent.reactivated',
|
||||
{ agentId },
|
||||
);
|
||||
} else if (auditAction === 'agent.decommissioned') {
|
||||
void this.eventPublisher?.publishEvent(
|
||||
updated.organizationId,
|
||||
'agent.decommissioned',
|
||||
{ agentId },
|
||||
);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -224,5 +262,12 @@ export class AgentService {
|
||||
userAgent,
|
||||
{},
|
||||
);
|
||||
|
||||
// Publish event (fire-and-forget)
|
||||
void this.eventPublisher?.publishEvent(
|
||||
agent.organizationId,
|
||||
'agent.decommissioned',
|
||||
{ agentId },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user