# Architecture ## Component Overview ``` ┌─────────────────────────────────────┐ │ AgentIdP Application │ │ Node.js / Express │ │ Port 3000 │ │ │ │ Auth MW → RateLimit MW → Routes │ │ ↓ ↓ │ │ Controllers → Services → Repos │ └──────────────┬──────────────┬────────┘ │ │ ┌──────────────▼──┐ ┌───────▼────────┐ │ PostgreSQL 14 │ │ Redis 7 │ │ Port 5432 │ │ Port 6379 │ │ │ │ │ │ agents │ │ Token revoke │ │ credentials │ │ Rate limits │ │ audit_events │ │ Monthly counts │ │ token_revocati- │ │ │ │ ons │ │ │ └──────────────────┘ └─────────────────┘ ``` ## Components ### AgentIdP Application A stateless Express HTTP server. Every request is handled independently — no in-process shared state. This means it can be horizontally scaled (multiple instances) as long as all instances share the same PostgreSQL and Redis. **Internal layers:** | Layer | Responsibility | |-------|---------------| | Routes | Wire HTTP methods and paths to controllers | | Auth middleware | Validate Bearer JWT (RS256 + Redis revocation check) | | Rate limit middleware | Redis sliding-window counter per `client_id` | | Controllers | Parse and validate request, call service, return response | | Services | Business logic — no direct DB access | | Repositories | All SQL queries — no business logic | | Utils | JWT sign/verify, bcrypt, error types, async handler | ### PostgreSQL 14+ Primary durable data store. All agent identities, credentials, audit events, and token revocation records live here. See [database.md](database.md) for schema details. The application connects via a connection pool (`pg.Pool`) initialised from `DATABASE_URL`. The pool is a singleton shared across all request handlers. ### Redis 7+ Ephemeral store for three use cases: | Key pattern | Purpose | TTL | |------------|---------|-----| | `revoked:` | Token revocation list — checked on every authenticated request | Until token's `exp` | | `rate::` | Request count per client per 60-second window | 60 seconds | | `monthly:::` | Token issuance count for free tier limit enforcement | End of month | **Redis is supplementary, not the source of truth.** Token revocations are also written to the `token_revocations` PostgreSQL table for durability across Redis restarts. On Redis restart, the revocation list is cold — previously revoked tokens will pass auth until the PostgreSQL-backed warm-up is implemented (Phase 2). ## Request Data Flow ``` HTTP Request │ ▼ Express Router (matches path + method) │ ▼ Auth Middleware - Extract Bearer token from Authorization header - Verify RS256 signature using JWT_PUBLIC_KEY - Check Redis for revocation (key: revoked:) - Attach decoded payload to req.user │ ▼ Rate Limit Middleware - Key: rate::<60s-window> - Increment counter in Redis (INCR + EXPIRE) - Set X-RateLimit-* headers - Reject with 429 if count > 100 │ ▼ Controller - Validate request body / query params (Joi schemas) - Call service method - Return HTTP response │ ▼ Service - Business logic and orchestration - Calls one or more repositories - Fires audit log writes (async, fire-and-forget) │ ▼ Repository - Executes parameterised SQL queries - Maps DB rows to typed interfaces - Returns typed results to service │ ▼ PostgreSQL / Redis ``` ## Service Map | Route prefix | Service | Repository | |-------------|---------|-----------| | `/api/v1/agents` | `AgentService` | `AgentRepository` | | `/api/v1/agents/:id/credentials` | `CredentialService` | `CredentialRepository` | | `/api/v1/token` | `OAuth2Service` | `TokenRepository`, `CredentialRepository`, `AgentRepository` | | `/api/v1/audit` | `AuditService` | `AuditRepository` | ## Ports | Service | Internal port | Exposed port (local dev) | |---------|--------------|--------------------------| | AgentIdP app | 3000 | 3000 | | PostgreSQL | 5432 | 5432 | | Redis | 6379 | 6379 | ## Graceful Shutdown The server listens for `SIGTERM` and `SIGINT`. On receipt: 1. `server.close()` is called — stops accepting new connections 2. In-flight requests complete 3. `process.exit(0)` is called The PostgreSQL pool and Redis client are not explicitly closed in the current shutdown path. This is safe for single-instance deployments; connection cleanup is handled by the OS.