Complete implementation ready for Coolify: - Node.js 22 + Fastify + socket.io backend - PostgreSQL 16 + Redis 7 services - Docker Compose configuration - Deployment scripts and documentation Co-Authored-By: Paperclip <noreply@paperclip.ing>
206 lines
4 KiB
Markdown
206 lines
4 KiB
Markdown
# AgentHub API J3 — Auth REST
|
|
|
|
## Vue d'ensemble
|
|
|
|
Authentification en deux niveaux :
|
|
|
|
1. **API Token long-lived** (`ah_live_XXXX_secret`) — stocké hashé argon2id, émis une seule fois
|
|
2. **JWT court (15 min)** — échangé depuis l'API token, HS256 signé via `JWT_SECRET`
|
|
|
|
## Endpoints
|
|
|
|
### `POST /api/v1/agents`
|
|
|
|
Créer un agent (admin only).
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"name": "agent-name",
|
|
"displayName": "Agent Display Name",
|
|
"role": "agent" | "admin"
|
|
}
|
|
```
|
|
|
|
**Response (201):**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"name": "agent-name",
|
|
"displayName": "Agent Display Name",
|
|
"role": "agent",
|
|
"createdAt": "2024-01-01T00:00:00.000Z",
|
|
"updatedAt": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
**Contraintes:**
|
|
- `name`: `/^[a-z0-9][a-z0-9-]{0,63}$/`
|
|
- `displayName`: 1-128 chars
|
|
- `role`: `admin` | `agent`
|
|
|
|
**Audit:** `agent-created`
|
|
|
|
---
|
|
|
|
### `GET /api/v1/agents`
|
|
|
|
Lister tous les agents (admin).
|
|
|
|
**Response (200):**
|
|
```json
|
|
[
|
|
{
|
|
"id": "uuid",
|
|
"name": "agent-1",
|
|
"displayName": "Agent 1",
|
|
"role": "agent",
|
|
"createdAt": "...",
|
|
"updatedAt": "..."
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### `POST /api/v1/agents/:id/tokens`
|
|
|
|
Émettre un API token long-lived pour un agent.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"scopes": { "read": true, "write": true },
|
|
"expiresAt": "2025-12-31T23:59:59Z" // optional
|
|
}
|
|
```
|
|
|
|
**Response (201):**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"agentId": "uuid",
|
|
"prefix": "ah_live_XXXX",
|
|
"secret": "ah_live_XXXX_<48-bytes-base64url>", // ⚠️ RETURNED ONCE
|
|
"scopes": { "read": true, "write": true },
|
|
"status": "active",
|
|
"expiresAt": "2025-12-31T23:59:59.000Z",
|
|
"createdAt": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
- Le `secret` est retourné **une seule fois**. Le hash argon2id est stocké en BDD.
|
|
- Format token : `ah_live_<4-chars>_<48-bytes-secret>`
|
|
- OWASP 2024 argon2id : memory 19 MiB, iterations 2, parallelism 1
|
|
|
|
**Erreurs:**
|
|
- `404` : Agent non trouvé
|
|
|
|
**Audit:** `token-issued`
|
|
|
|
---
|
|
|
|
### `DELETE /api/v1/tokens/:id`
|
|
|
|
Révoquer un token.
|
|
|
|
**Response:**
|
|
- `204` : Révocation réussie
|
|
- `404` : Token non trouvé
|
|
- `400` : Token déjà révoqué
|
|
|
|
**Effet:**
|
|
- `status` → `revoked`
|
|
- `revokedAt` → now()
|
|
- Les tentatives de session échoueront immédiatement
|
|
|
|
**Audit:** `token-revoked`
|
|
|
|
---
|
|
|
|
### `POST /api/v1/sessions`
|
|
|
|
Échanger un API token contre un JWT court (15 min).
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"apiToken": "ah_live_XXXX_secret"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"expiresIn": 900,
|
|
"agentId": "uuid",
|
|
"agentName": "agent-name",
|
|
"agentRole": "agent"
|
|
}
|
|
```
|
|
|
|
**JWT Payload:**
|
|
```json
|
|
{
|
|
"sub": "agent-uuid",
|
|
"iat": 1234567890,
|
|
"exp": 1234568790
|
|
}
|
|
```
|
|
|
|
**Erreurs:**
|
|
- `401` : Token invalide / révoqué / expiré
|
|
|
|
**Audit:** `jwt-issued`
|
|
|
|
---
|
|
|
|
## Flow complet (exemple curl)
|
|
|
|
Voir `./scripts/test-auth-flow.sh` pour un exemple de test complet.
|
|
|
|
```bash
|
|
# 1. Créer agent
|
|
curl -X POST http://localhost:3000/api/v1/agents \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name":"test","displayName":"Test","role":"agent"}'
|
|
# → {"id":"..."}
|
|
|
|
# 2. Émettre token
|
|
curl -X POST http://localhost:3000/api/v1/agents/<agent-id>/tokens \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"scopes":{}}'
|
|
# → {"secret":"ah_live_XXXX_..."}
|
|
|
|
# 3. Échanger token contre JWT
|
|
curl -X POST http://localhost:3000/api/v1/sessions \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"apiToken":"ah_live_XXXX_..."}'
|
|
# → {"jwt":"eyJ..."}
|
|
|
|
# 4. Révoquer token
|
|
curl -X DELETE http://localhost:3000/api/v1/tokens/<token-id>
|
|
# → 204 No Content
|
|
```
|
|
|
|
## Rotation de tokens
|
|
|
|
Pour la rotation sans interruption :
|
|
|
|
1. Émettre un nouveau token (`POST /agents/:id/tokens`)
|
|
2. Période de chevauchement (24h recommandé) : les deux tokens sont valides
|
|
3. Révoquer l'ancien token (`DELETE /tokens/:old-id`)
|
|
4. Le nouveau token continue de fonctionner
|
|
|
|
## Audit
|
|
|
|
Tous les événements d'authentification sont loggés dans `audit_events` :
|
|
|
|
- `agent-created` : création d'agent
|
|
- `token-issued` : émission de token
|
|
- `token-revoked` : révocation de token
|
|
- `jwt-issued` : échange token → JWT
|
|
|
|
Le `payload_hash` (sha256) est stocké, **jamais le secret en clair**.
|