WS3 — Advanced Analytics Dashboard: - DB migration: analytics_events table (tenant_id, date, metric_type, count) - AnalyticsService: recordEvent (fire-and-forget), getTokenTrend, getAgentActivity, getAgentUsageSummary - Analytics hooks in OAuth2Service (token_issued) and AgentService (agent_registered/deactivated) - AnalyticsController + routes/analytics.ts (gated by ANALYTICS_ENABLED flag) - Portal: TokenTrendChart (recharts LineChart), AgentHeatmap (recharts heatmap), /analytics page WS4 — API Gateway Tiers: - DB migration: tenant_tiers table; src/config/tiers.ts (free/pro/enterprise limits) - TierService: getStatus, initiateUpgrade (Stripe), applyUpgrade; TierLimitError in errors.ts - tierEnforcement middleware (Redis-backed daily call/token counters; TIER_ENFORCEMENT flag) - Agent count enforcement in AgentService.create() - Stripe webhook updated to call TierService.applyUpgrade() on checkout.session.completed - TierController + routes/tiers.ts; Portal: /settings/tier page with upgrade flow WS6 — AGNTCY Compliance Certification: - ComplianceService: generateReport() (Redis-cached 5 min), exportAgentCards() - Compliance sections: agent-identity (DID + credential expiry checks), audit-trail (Merkle chain) - ComplianceController updated with getComplianceReport, exportAgentCards handlers - routes/compliance.ts: new AGNTCY routes (gated by COMPLIANCE_ENABLED flag); SOC2 routes unaffected QA: - 28 new unit tests: AnalyticsService (8), TierService (9), ComplianceService (11) — all pass - 673 total unit tests passing; 0 TypeScript errors across API and portal - AGNTCY conformance test suite at tests/agntcy-conformance/ (4 protocol tests) - Portal builds cleanly: 9 routes including /analytics and /settings/tier - Feature flags verified: ANALYTICS_ENABLED, TIER_ENFORCEMENT, COMPLIANCE_ENABLED Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
193 lines
7.2 KiB
TypeScript
193 lines
7.2 KiB
TypeScript
/**
|
|
* ComplianceController — SOC 2 Type II and AGNTCY compliance endpoints.
|
|
*
|
|
* Handles endpoints defined in docs/openapi/compliance.yaml:
|
|
* GET /api/v1/audit/verify — Audit chain integrity verification (auth required)
|
|
* GET /api/v1/compliance/controls — SOC 2 control status summary (public)
|
|
* GET /api/v1/compliance/report — AGNTCY compliance report (auth required)
|
|
* GET /api/v1/compliance/agent-cards — AGNTCY agent card export (auth required)
|
|
*/
|
|
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import { AuditVerificationService } from '../services/AuditVerificationService.js';
|
|
import { ComplianceService } from '../services/ComplianceService.js';
|
|
import { getAllControlStatuses } from '../services/ComplianceStatusStore.js';
|
|
import { ValidationError } from '../utils/errors.js';
|
|
import { ITokenPayload } from '../types/index.js';
|
|
|
|
// ============================================================================
|
|
// Helpers
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Returns `true` if the given string is a valid ISO 8601 date-time string.
|
|
* Uses `Date.parse` — valid ISO 8601 strings produce a finite number;
|
|
* invalid strings produce `NaN`.
|
|
*
|
|
* @param value - The string to validate.
|
|
* @returns `true` if valid ISO 8601 date-time; `false` otherwise.
|
|
*/
|
|
function isValidIsoDateTime(value: string): boolean {
|
|
const parsed = Date.parse(value);
|
|
return !isNaN(parsed);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Controller
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Controller for SOC 2 Type II and AGNTCY compliance API endpoints.
|
|
* Exposes audit chain verification, live control status reporting,
|
|
* AGNTCY compliance report generation, and agent card export.
|
|
*/
|
|
export class ComplianceController {
|
|
/**
|
|
* @param auditVerificationService - Service for cryptographic audit chain verification.
|
|
* @param complianceService - Service for AGNTCY compliance report and agent card generation.
|
|
*/
|
|
constructor(
|
|
private readonly auditVerificationService: AuditVerificationService,
|
|
private readonly complianceService: ComplianceService,
|
|
) {}
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
// Handlers
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* GET /api/v1/audit/verify
|
|
*
|
|
* Verifies the cryptographic integrity of the audit event hash chain.
|
|
* Accepts optional `fromDate` and `toDate` ISO 8601 query parameters to restrict
|
|
* the verification window. Returns 200 regardless of whether the chain is intact —
|
|
* check `verified` in the response body.
|
|
*
|
|
* Requires Bearer token with `audit:read` scope (enforced by route middleware).
|
|
*
|
|
* @param req - Express request; optional `fromDate` and `toDate` query params.
|
|
* @param res - Express response.
|
|
* @param next - Express next function.
|
|
*/
|
|
async verifyAuditChain(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const { fromDate, toDate } = req.query as Record<string, string | undefined>;
|
|
|
|
// Validate fromDate if provided
|
|
if (fromDate !== undefined && !isValidIsoDateTime(fromDate)) {
|
|
throw new ValidationError('Invalid query parameter value.', {
|
|
field: 'fromDate',
|
|
reason: 'Must be a valid ISO 8601 date-time string (e.g. 2026-03-01T00:00:00.000Z).',
|
|
});
|
|
}
|
|
|
|
// Validate toDate if provided
|
|
if (toDate !== undefined && !isValidIsoDateTime(toDate)) {
|
|
throw new ValidationError('Invalid query parameter value.', {
|
|
field: 'toDate',
|
|
reason: 'Must be a valid ISO 8601 date-time string (e.g. 2026-03-31T23:59:59.999Z).',
|
|
});
|
|
}
|
|
|
|
// Validate date range ordering
|
|
if (fromDate !== undefined && toDate !== undefined) {
|
|
if (new Date(fromDate) > new Date(toDate)) {
|
|
throw new ValidationError('Invalid date range.', {
|
|
reason: 'fromDate must be before or equal to toDate.',
|
|
});
|
|
}
|
|
}
|
|
|
|
const result = await this.auditVerificationService.verifyChain(fromDate, toDate);
|
|
|
|
res.status(200).json(result);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/compliance/controls
|
|
*
|
|
* Returns a live status snapshot for all five in-scope SOC 2 Trust Services
|
|
* Criteria controls. Status values are maintained by background jobs
|
|
* (SecretsRotationJob, AuditChainVerificationJob) via ComplianceStatusStore.
|
|
*
|
|
* No authentication required — this is a public health-style endpoint.
|
|
* Sets `Cache-Control: public, max-age=60` to permit 60-second downstream caching.
|
|
*
|
|
* @param _req - Express request (unused).
|
|
* @param res - Express response.
|
|
* @param next - Express next function.
|
|
*/
|
|
async getComplianceControls(
|
|
_req: Request,
|
|
res: Response,
|
|
next: NextFunction,
|
|
): Promise<void> {
|
|
try {
|
|
const controls = getAllControlStatuses();
|
|
|
|
res.setHeader('Cache-Control', 'public, max-age=60');
|
|
res.status(200).json({ controls });
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/compliance/report
|
|
*
|
|
* Generates and returns an AGNTCY compliance report for the authenticated tenant.
|
|
* The report covers agent-identity verification and audit-trail integrity.
|
|
* Reports are cached in Redis for 5 minutes; sets `X-Cache: HIT` when served from cache.
|
|
*
|
|
* Requires Bearer token authentication (tenant extracted from req.user.sub).
|
|
*
|
|
* @param req - Express request; tenant derived from authenticated user context.
|
|
* @param res - Express response.
|
|
* @param next - Express next function.
|
|
*/
|
|
async getComplianceReport(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const user = req.user as ITokenPayload | undefined;
|
|
const tenantId = user?.organization_id ?? user?.sub ?? '';
|
|
|
|
const report = await this.complianceService.generateReport(tenantId);
|
|
|
|
if (report.from_cache === true) {
|
|
res.setHeader('X-Cache', 'HIT');
|
|
}
|
|
|
|
res.status(200).json(report);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/compliance/agent-cards
|
|
*
|
|
* Exports all active agents for the authenticated tenant as AGNTCY-standard
|
|
* agent card JSON objects.
|
|
*
|
|
* Requires Bearer token authentication (tenant extracted from req.user.sub).
|
|
*
|
|
* @param req - Express request; tenant derived from authenticated user context.
|
|
* @param res - Express response.
|
|
* @param next - Express next function.
|
|
*/
|
|
async exportAgentCards(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const user = req.user as ITokenPayload | undefined;
|
|
const tenantId = user?.organization_id ?? user?.sub ?? '';
|
|
|
|
const cards = await this.complianceService.exportAgentCards(tenantId);
|
|
|
|
res.status(200).json(cards);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
}
|