feat(phase-3): workstream 4 — AGNTCY Federation

Implements cross-IdP token verification for the AGNTCY ecosystem:

- Migration 015: federation_partners table (issuer, jwks_uri,
  allowed_organizations JSONB, status, expires_at)
- FederationService: registerPartner (JWKS validation at registration),
  listPartners, getPartner, updatePartner, deletePartner,
  verifyFederatedToken (alg:none rejected, RS256/ES256 only,
  allowedOrganizations filter, expiry enforcement)
- JWKS caching in Redis (TTL: FEDERATION_JWKS_CACHE_TTL_SECONDS);
  cache invalidated on partner delete and jwks_uri change
- FederationController + routes: 5 admin:orgs endpoints +
  POST /federation/verify (agents:read)
- OPA policy: 5 federation admin endpoint → admin:orgs mappings
- 499 unit tests passing; 94.69% statement coverage on FederationService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SentryAgent.ai Developer
2026-03-30 10:13:49 +00:00
parent 5e465e596a
commit 03b5de300c
12 changed files with 2092 additions and 13 deletions

View File

@@ -26,6 +26,7 @@ import { OrgService } from './services/OrgService.js';
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 { AgentController } from './controllers/AgentController.js';
import { TokenController } from './controllers/TokenController.js';
@@ -34,6 +35,7 @@ import { AuditController } from './controllers/AuditController.js';
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 { createAgentsRouter } from './routes/agents.js';
import { createTokenRouter } from './routes/token.js';
@@ -44,6 +46,7 @@ import { createMetricsRouter } from './routes/metrics.js';
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 { errorHandler } from './middleware/errorHandler.js';
import { createOpaMiddleware } from './middleware/opa.js';
@@ -167,6 +170,8 @@ export async function createApp(): Promise<Application> {
const orgController = new OrgController(orgService);
const didController = new DIDController(didService, agentRepo);
const oidcController = new OIDCController(oidcKeyService, agentRepo);
const federationService = new FederationService(pool, redis as RedisClientType);
const federationController = new FederationController(federationService);
// ────────────────────────────────────────────────────────────────
// Org context middleware — sets PostgreSQL session variable app.organization_id
@@ -203,6 +208,7 @@ export async function createApp(): Promise<Application> {
app.use(`${API_BASE}/token`, createTokenRouter(tokenController, opaMiddleware));
app.use(`${API_BASE}/audit`, createAuditRouter(auditController, opaMiddleware));
app.use(`${API_BASE}/organizations`, createOrgsRouter(orgController, opaMiddleware));
app.use(`${API_BASE}`, createFederationRouter(federationController, authMiddleware, opaMiddleware));
// ────────────────────────────────────────────────────────────────
// Dashboard static assets (served from dashboard/dist/)