Files
mimic/tasks/todo.md
Knacky bd9c06e31b chore: bootstrap project (sprint 0)
Lay down the project foundation before Sprint 1 implementation:

- SPEC.md enriched with a "Décisions techniques" section that pins
  down 3-role auth (admin super-user / redteam / soc), JWT bearer,
  single-container Flask+React topology, minimal Engagement model,
  local MITRE STIX bundle, and the Makefile target list.
- .claude/agents/ defines the 6 sub-agents per SPEC.md § Team:
  backend-builder, frontend-builder, spec-reviewer (project override
  covering plan-vs-spec + code-vs-spec), code-reviewer, test-verifier,
  devil-advocate.
- tasks/todo.md holds the full Sprint 1 plan (Auth + CRUD Engagement)
  validated by spec-reviewer on 2026-05-26 after one round of fixes.
- CHANGELOG.md and tasks/lessons.md scaffolded.
- .gitignore covers Python, Node, Playwright, secrets, build artifacts
  and Claude Code worktrees.

No application code is shipped in this commit — Sprint 1 will be a
separate branch and PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 08:01:13 +02:00

316 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sprint 1 — Auth + CRUD Engagement
**Branche** : `sprint/1-auth-engagements`
**Statut** : 🟢 PLAN APPROUVÉ (spec-reviewer 2026-05-26) — prêt pour dispatch backend-builder
**Base** : `main`
**Objectif** : poser l'infrastructure (Flask + SQLite + React + Docker + Makefile + tests) ET livrer une première feature de bout en bout testable sur l'UI — login + admin gère les comptes + tout utilisateur authentifié peut créer/lister/éditer/supprimer des engagements.
---
## 1. User stories
### US-1 — En tant qu'admin, je bootstrap le premier compte admin
**Pourquoi** : sinon impossible d'utiliser l'application au premier démarrage.
**Critères d'acceptation**
- [ ] AC-1.1 : la commande `make create-admin USER=alice PASS=p4ssw0rd` crée un user `alice` avec le rôle `admin` et le password hashé (argon2).
- [ ] AC-1.2 : la commande échoue proprement (exit ≠ 0, message clair) si le username existe déjà.
- [ ] AC-1.3 : la commande échoue si le password fait moins de 8 caractères.
- [ ] AC-1.4 : la commande s'exécute via `docker exec mimic flask create-admin …` (le Makefile encapsule cet appel).
### US-2 — En tant qu'utilisateur, je me connecte et me déconnecte
**Pourquoi** : porte d'entrée de l'application.
**Critères d'acceptation**
- [ ] AC-2.1 : `POST /api/auth/login {username, password}` retourne `{access_token, user: {id, username, role}}` (200) si credentials valides.
- [ ] AC-2.2 : 401 si credentials invalides, avec un message générique ("Invalid credentials") — pas de fuite username vs password.
- [ ] AC-2.3 : `POST /api/auth/logout` invalide le token côté client (UI supprime le token). Côté serveur : optionnel V1, on accepte un logout client-side.
- [ ] AC-2.4 : page `/login` affiche le formulaire ; soumission OK → redirection `/engagements`. Soumission KO → message d'erreur visible.
- [ ] AC-2.5 : navigation vers `/engagements` sans token → redirection `/login`.
- [ ] AC-2.6 : si une requête API retourne 401 (token expiré ou invalide), l'intercepteur axios purge le token et redirige vers `/login` avec un toast "Session expirée".
### US-3 — En tant qu'admin, je gère les comptes utilisateurs
**Pourquoi** : créer redteam/soc accounts depuis l'UI.
**Critères d'acceptation**
- [ ] AC-3.1 : `GET /api/users` (admin only) → liste `[{id, username, role, created_at}]`.
- [ ] AC-3.2 : `POST /api/users {username, password, role}` (admin only) → 201 + objet user (sans password_hash). 400 si username existe ou password < 8 chars.
- [ ] AC-3.3 : `PATCH /api/users/<id> {role?, password?}` (admin only) → 200, modifie role et/ou password.
- [ ] AC-3.4 : `DELETE /api/users/<id>` (admin only) → 204. Refuse de supprimer le dernier admin (409).
- [ ] AC-3.5 : tout autre rôle (redteam/soc) appelant ces endpoints reçoit 403.
- [ ] AC-3.6 : page `/admin/users` (admin only) liste les users avec actions "Créer", "Modifier rôle", "Reset password", "Supprimer".
- [ ] AC-3.7 : un user redteam/soc qui visite `/admin/users` est redirigé vers `/engagements` avec un toast "Accès refusé".
### US-4 — En tant qu'utilisateur authentifié, je gère les engagements
**Pourquoi** : la feature métier centrale du Sprint 1.
**Critères d'acceptation**
- [ ] AC-4.1 : `GET /api/engagements` (auth) → `[{id, name, description, start_date, end_date, status, created_at, created_by}]`.
- [ ] AC-4.2 : `POST /api/engagements {name, description?, start_date, end_date?, status?}` (auth) → 201. Valide : `name` non vide, `start_date` parseable, `end_date >= start_date` si fournie, `status ∈ {planned, active, closed}` (défaut `planned`).
- [ ] AC-4.3 : `GET /api/engagements/<id>` (auth) → 200 + objet, 404 si inconnu.
- [ ] AC-4.4 : `PATCH /api/engagements/<id>` (auth, redteam ou admin) → 200, modifie les champs fournis.
- [ ] AC-4.5 : `DELETE /api/engagements/<id>` (admin ou redteam) → 204.
- [ ] AC-4.6 : un user `soc` peut lire (GET) mais pas créer/modifier/supprimer (403).
- [ ] AC-4.7 : page `/engagements` liste les engagements avec colonnes (name, status badge, dates, created_by). Boutons "Nouveau", "Voir", "Éditer", "Supprimer" selon rôle.
- [ ] AC-4.8 : page `/engagements/new` et `/engagements/<id>/edit` (formulaire avec validation côté client + erreurs API affichées).
- [ ] AC-4.9 : page `/engagements/<id>` (détail), placeholder "Simulations à venir au Sprint 2".
### US-5 — En tant qu'utilisateur, l'UI respecte DESIGN.md
**Pourquoi** : non négociable selon SPEC.md.
**Critères d'acceptation**
- [ ] AC-5.1 : la palette, typographie, espacements, composants (boutons, inputs, badges) suivent strictement `DESIGN.md`.
- [ ] AC-5.2 : layout responsive desktop-first (≥ 1024px), pas de breakage visible jusqu'à 1280×720 minimum.
- [ ] AC-5.3 : états loading / error / empty implémentés pour la liste d'engagements et la liste d'users.
### US-6 — Le livrable se déploie via Docker + Makefile
**Pourquoi** : exigence SPEC.md.
**Critères d'acceptation**
- [ ] AC-6.1 : `make build` produit l'image docker `mimic:latest` (Dockerfile multistage : Node build → Python runtime).
- [ ] AC-6.2 : `make start` lance le container, l'app est accessible sur `http://localhost:5000` (front + API).
- [ ] AC-6.3 : `make stop`, `make restart`, `make logs` fonctionnent.
- [ ] AC-6.4 : SQLite persisté via volume nommé `mimic-data` (la DB survit à `make restart`).
- [ ] AC-6.5 : `make test-backend`, `make test-frontend`, `make test-e2e` exécutent les suites respectives.
---
## 2. Brief technique — Backend Builder
**Scope strict** : `backend/`, `Dockerfile`, `Makefile` (en collab avec ce sprint uniquement).
### Livrables
1. **Structure** :
```
backend/
__init__.py # ⚠️ requis pour que `backend.app:create_app` soit importable depuis /app dans le Dockerfile
app/
__init__.py # create_app() factory
config.py # DevConfig / ProdConfig (SECRET_KEY, JWT_SECRET, SQLALCHEMY_DATABASE_URI)
extensions.py # db = SQLAlchemy(), migrate = Migrate()
cli.py # @app.cli.command("create-admin")
models/
__init__.py
user.py # User(id, username, password_hash, role, created_at)
engagement.py # Engagement(id, name, description, start_date, end_date, status, created_at, created_by)
auth/
__init__.py
jwt.py # encode_token / decode_token
hashing.py # argon2 hash & verify
decorators.py # @login_required, @role_required("admin")
api/
__init__.py
auth.py # /login, /logout, /me
users.py # CRUD users (admin)
engagements.py # CRUD engagements
serializers.py # to_dict() helpers — engagement renvoie created_by={id, username}, jamais l'objet User brut
errors.py # uniform JSON error handler
migrations/ # alembic init + 0001 initial schema
tests/
conftest.py # app fixture, db fixture, auth helpers
test_auth.py # login OK + 401 invalid + 401 token expiré
test_users.py # CRUD admin only, 403 redteam/soc, last-admin protection (AC-3.4)
test_engagements.py # CRUD redteam/admin, 403 soc en write, serializer created_by
test_cli_create_admin.py # success + duplicate username (AC-1.2) + password < 8 chars (AC-1.3)
pyproject.toml
requirements.txt # flask, flask-sqlalchemy, flask-migrate, pyjwt, argon2-cffi, ruff, mypy, pytest
```
2. **Endpoints** : voir critères AC-2 / AC-3 / AC-4.
3. **Modèles** : voir SPEC.md § Modèle de données.
4. **Config** :
- `SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.environ.get('MIMIC_DB_PATH', '/data/mimic.sqlite')}"` — l'env var `MIMIC_DB_PATH` du Dockerfile surcharge le chemin si présent (utile pour tests ou changement de mount).
- `JWT_SECRET` lu depuis env var `MIMIC_JWT_SECRET`, requis (raise si absent en Prod).
- `JWT_EXP_MINUTES = 60`.
5. **CLI** : `flask create-admin <user> <pass>` (avec validations AC-1.2/1.3).
6. **Serializer engagement** : la réponse JSON expose `created_by` sous forme `{"id": <int>, "username": <str>}` — pas l'objet User complet, pas seulement l'id.
7. **Tests** : couverture success / failure / edge sur chaque endpoint et CLI. Les AC-1.2 et AC-1.3 doivent avoir leur propre test dans `test_cli_create_admin.py`.
8. **Lint / typing** : ruff clean, mypy clean sur `app/`.
### Règles
- Pas de touche au frontend.
- Pas d'invention de dépendances hors de la liste ci-dessus sans escalade au team-lead.
- Renvoyer le summary attendu (cf. `.claude/agents/backend-builder.md`).
---
## 3. Brief technique — Frontend Builder
**Scope strict** : `frontend/` UNIQUEMENT. Le dossier `e2e/` est **interdit** au frontend-builder — il est sous la responsabilité exclusive du test-verifier (scaffolding Playwright + tests).
### Livrables
1. **Structure** :
```
frontend/
package.json # react, react-dom, react-router-dom, @tanstack/react-query, axios, tailwindcss, vite, typescript, vitest, @testing-library/react
vite.config.ts # proxy /api -> http://localhost:5000 en dev
tailwind.config.ts # tokens issus de DESIGN.md, font-family principale = "Inter"
tsconfig.json
index.html
src/
main.tsx
App.tsx # router, QueryClientProvider
api/
client.ts # axios + interceptor (Bearer + 401 purge → /login)
auth.ts # login, me
users.ts # CRUD users
engagements.ts # CRUD engagements
types.ts
hooks/
useAuth.ts # token in memory + localStorage, role helpers (isAdmin, isRedteam, isSoc)
useEngagements.ts # TanStack Query hooks
useUsers.ts
useToast.ts # provider + hook pour notifications éphémères
pages/
LoginPage.tsx
EngagementsListPage.tsx
EngagementFormPage.tsx # new + edit
EngagementDetailPage.tsx
UsersAdminPage.tsx
components/
Layout.tsx # nav, topbar, role-aware menu
ProtectedRoute.tsx # redirect to /login if no token, role gate
StatusBadge.tsx
FormField.tsx
EmptyState.tsx
ErrorState.tsx
LoadingState.tsx
Toast.tsx # composant + ToastProvider (utilisé par AC-2.6 + AC-3.7)
styles/
index.css # tailwind base + DESIGN.md tokens
fonts.css # @font-face Inter (bundlée localement dans public/fonts/, AUCUN CDN)
public/
fonts/ # fichiers Inter .woff2 (déposés via npm install + copie post-install, ou commit direct)
tests/
components/*.test.tsx # Vitest
```
2. **Police** : `Inter` (substitut Forma DJR Micro choisi par défaut parmi les 3 options DESIGN.md §86-89). Bundlée localement en `.woff2`, jamais via Google Fonts/CDN. Configurée dans `tailwind.config.ts` comme `font-sans` et chargée via `@font-face` dans `styles/fonts.css`.
3. **Routing** :
- `/login`
- `/engagements` (auth, all roles)
- `/engagements/new` (auth, redteam|admin)
- `/engagements/:id` (auth, all roles)
- `/engagements/:id/edit` (auth, redteam|admin)
- `/admin/users` (auth, admin only)
- `/` → redirige vers `/engagements` ou `/login`
4. **Auth** : token JWT en mémoire + `localStorage` ; intercepteur axios ajoute `Authorization: Bearer <token>` ; 401 → purge token + redirect `/login` + toast "Session expirée" (AC-2.6).
5. **Tests Vitest** : 1 test par composant non trivial (états loading/error/empty, comportement des rôles dans `ProtectedRoute`, Toast déclenché par 401).
### Règles
- Lit le summary du backend-builder EN PREMIER.
- Pas d'invention d'endpoints. Mismatch → escalade au team-lead.
- Respect strict de `DESIGN.md` pour palette/typo/composants.
- Pas de CDN remote au runtime — bundle local (police Inter incluse).
- **Interdiction absolue de toucher `e2e/`** (responsabilité test-verifier).
---
## 4. Brief — Docker / Makefile (réalisé par le backend-builder)
### `docker/Dockerfile` (multistage)
```dockerfile
# Stage 1: build front
FROM node:20-alpine AS frontend-build
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
# Stage 2: python runtime
FROM python:3.12-slim
WORKDIR /app
COPY backend/requirements.txt ./backend/
RUN pip install --no-cache-dir -r backend/requirements.txt
COPY backend/ ./backend/
COPY --from=frontend-build /app/frontend/dist ./backend/app/static
ENV FLASK_APP=backend.app:create_app
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app
# Variables surchargeables au `docker run` :
ENV MIMIC_PORT=5000
ENV MIMIC_DB_PATH=/data/mimic.sqlite
VOLUME ["/data"]
EXPOSE 5000
# Entrypoint : applique les migrations Alembic puis lance Flask
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
**`docker/entrypoint.sh`** :
```bash
#!/bin/sh
set -e
flask db upgrade
exec flask run --host=0.0.0.0 --port="${MIMIC_PORT:-5000}"
```
Flask sert `backend/app/static` en racine `/` ET expose les blueprints sous `/api/*`. La DB SQLite vit dans `/data/mimic.sqlite` (volume nommé `mimic-data`) — survit à `make restart` (AC-6.4).
### `Makefile`
Targets requis et leur sémantique :
| Target | Action |
|---|---|
| `build` | `docker build -f docker/Dockerfile -t mimic:latest .` |
| `start` | `docker run -d --name mimic -p $(PORT):5000 -v mimic-data:/data --env-file .env mimic:latest` (`PORT ?= 5000`) |
| `stop` | `docker stop mimic && docker rm mimic` |
| `restart` | `$(MAKE) stop && $(MAKE) start` |
| `update` | `git pull && $(MAKE) build && $(MAKE) restart` |
| `logs` | `docker logs -f mimic` |
| `create-admin` | `docker exec mimic flask create-admin $(USER) $(PASS)` (requiert `USER=` et `PASS=`) |
| `update-mitre` | placeholder no-op Sprint 1 (`@echo "MITRE update: Sprint 2+"`) |
| `test-backend` | `docker exec mimic pytest -q backend/tests/` (ou run local en venv) |
| `test-frontend` | `cd frontend && npm run test -- --run` |
| `test-e2e` | `cd e2e && npx playwright test` (container doit être up) |
| `clean` | `docker rm -f mimic 2>/dev/null; docker volume rm mimic-data 2>/dev/null; rm -rf backend/__pycache__ frontend/node_modules frontend/dist` |
---
## 5. Definition of Done — Sprint 1
- [ ] Tous les critères d'acceptation des US 1→6 passent.
- [ ] `pytest`, `ruff`, `mypy` clean côté backend.
- [ ] `npm run typecheck`, `lint`, `test` clean côté frontend.
- [ ] Playwright suite verte côté `e2e/`.
- [ ] Image docker se build (`make build`), démarre (`make start`), répond sur `:5000`.
- [ ] Code review (Opus) sans BLOCKER ouvert.
- [ ] `SPEC.md`, `README.md`, `CHANGELOG.md` à jour.
- [ ] PR ouverte sur la branche sprint, validée par l'utilisateur après récap synthétique.
---
## 6. Risques & questions à clarifier avec l'utilisateur AVANT de coder
1. **DESIGN.md** : faut-il que je relise DESIGN.md (27 Ko, déjà présent dans le repo) avant que le frontend-builder l'utilise ? OUI
2. **Persistance SQLite** : DB stockée dans `/data/mimic.sqlite` montée comme volume nommé `mimic-data`. OK ? OK
3. **Port** : `5000` (Flask défaut). Conflit possible avec macOS AirPlay si jamais — on s'en fiche en Linux. Surcharge du port possible via dockerfile et container.
4. **CSRF** : on est en API JWT pure, pas de cookie session, donc pas de CSRF protection nécessaire côté serveur. OK ? OK
5. **Refresh token** : exclu V1. JWT court (60 min) ; user devra se reconnecter. OK ? OK
6. **Logout** : V1 = client-side uniquement (purge du token). Pas de blacklist. OK ? OK
7. **CI** : pas mentionné dans la spec. Skip pour Sprint 1 ? (à confirmer) On verra plus tard.
8. **README.md** : actuellement absent. Le team-lead le crée en fin de Sprint 1 avec instructions `make build / start / create-admin`. OK.
---
## 7. Plan d'exécution (séquence)
**Branche unique pour ce sprint** : `sprint/1-auth-engagements` (pas de sous-branches builders, le sprint est séquentiel).
1. ✅ **Spec-reviewer** (`.claude/agents/spec-reviewer.md`, override projet du built-in) valide ce plan vs SPEC.md.
2. 🔵 **Backend-builder** implémente `backend/` + `docker/Dockerfile` + `docker/entrypoint.sh` + `Makefile`, livre son summary (incluant le contrat API).
3. 🔵 **Frontend-builder** lit le summary backend puis implémente le front (`frontend/` UNIQUEMENT, jamais `e2e/`).
4. 🔵 **Code-reviewer** relit le diff du sprint (LSP-first, focus bugs/qualité/scope).
5. 🔵 **Test-verifier** **scaffolds** `e2e/` (Playwright config, package.json, fixtures auth) **puis écrit** les acceptance tests Playwright et exécute la suite contre le container démarré via `make start`.
6. 🟢 **Team-lead** (moi) prépare la PR + récap synthétique → user valide.
**Responsabilité `e2e/`** : exclusivement le test-verifier. Backend-builder et frontend-builder n'y touchent jamais.