Adds the full devops-documentation OpenSpec change implementation. Separate from docs/developers/ — serves a different audience (operators, not API consumers). docs/devops/: - README.md — index and system overview - architecture.md — components, ports, data flow, Redis key patterns - environment-variables.md — all 7 env vars (required + optional, formats, .env example) - database.md — 4-table schema, indexes, constraints, migration runner - local-development.md — docker-compose setup, health checks, startup, Dockerfile gap noted - security.md — RSA key generation/rotation, CORS, bcrypt, secret storage guidance - operations.md — startup order, graceful shutdown, log reference, troubleshooting QA gates: 48/48 tasks complete. All env vars verified against source. All table names verified against migrations. All ports verified against docker-compose.yml. All internal links resolve. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
134 lines
5.4 KiB
Markdown
134 lines
5.4 KiB
Markdown
# 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:<jti>` | Token revocation list — checked on every authenticated request | Until token's `exp` |
|
|
| `rate:<client_id>:<window>` | Request count per client per 60-second window | 60 seconds |
|
|
| `monthly:<client_id>:<year>:<month>` | 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:<jti>)
|
|
- Attach decoded payload to req.user
|
|
│
|
|
▼
|
|
Rate Limit Middleware
|
|
- Key: rate:<client_id>:<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.
|