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>
17 KiB
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=p4ssw0rdcrée un useraliceavec le rôleadminet 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/logoutinvalide le token côté client (UI supprime le token). Côté serveur : optionnel V1, on accepte un logout client-side. - AC-2.4 : page
/loginaffiche le formulaire ; soumission OK → redirection/engagements. Soumission KO → message d'erreur visible. - AC-2.5 : navigation vers
/engagementssans 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
/loginavec 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/usersest redirigé vers/engagementsavec 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 :namenon vide,start_dateparseable,end_date >= start_datesi fournie,status ∈ {planned, active, closed}(défautplanned). - 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
socpeut lire (GET) mais pas créer/modifier/supprimer (403). - AC-4.7 : page
/engagementsliste les engagements avec colonnes (name, status badge, dates, created_by). Boutons "Nouveau", "Voir", "Éditer", "Supprimer" selon rôle. - AC-4.8 : page
/engagements/newet/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 buildproduit l'image dockermimic:latest(Dockerfile multistage : Node build → Python runtime). - AC-6.2 :
make startlance le container, l'app est accessible surhttp://localhost:5000(front + API). - AC-6.3 :
make stop,make restart,make logsfonctionnent. - 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-e2eexécutent les suites respectives.
2. Brief technique — Backend Builder
Scope strict : backend/, Dockerfile, Makefile (en collab avec ce sprint uniquement).
Livrables
-
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 -
Endpoints : voir critères AC-2 / AC-3 / AC-4.
-
Modèles : voir SPEC.md § Modèle de données.
-
Config :
SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.environ.get('MIMIC_DB_PATH', '/data/mimic.sqlite')}"— l'env varMIMIC_DB_PATHdu Dockerfile surcharge le chemin si présent (utile pour tests ou changement de mount).JWT_SECRETlu depuis env varMIMIC_JWT_SECRET, requis (raise si absent en Prod).JWT_EXP_MINUTES = 60.
-
CLI :
flask create-admin <user> <pass>(avec validations AC-1.2/1.3). -
Serializer engagement : la réponse JSON expose
created_bysous forme{"id": <int>, "username": <str>}— pas l'objet User complet, pas seulement l'id. -
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. -
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
-
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 -
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 danstailwind.config.tscommefont-sanset chargée via@font-facedansstyles/fonts.css. -
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/engagementsou/login
-
Auth : token JWT en mémoire +
localStorage; intercepteur axios ajouteAuthorization: Bearer <token>; 401 → purge token + redirect/login+ toast "Session expirée" (AC-2.6). -
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.mdpour 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)
# 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 :
#!/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,mypyclean côté backend.npm run typecheck,lint,testclean 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
- 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
- Persistance SQLite : DB stockée dans
/data/mimic.sqlitemontée comme volume nommémimic-data. OK ? OK - 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. - CSRF : on est en API JWT pure, pas de cookie session, donc pas de CSRF protection nécessaire côté serveur. OK ? OK
- Refresh token : exclu V1. JWT court (60 min) ; user devra se reconnecter. OK ? OK
- Logout : V1 = client-side uniquement (purge du token). Pas de blacklist. OK ? OK
- CI : pas mentionné dans la spec. Skip pour Sprint 1 ? (à confirmer) On verra plus tard.
- 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).
- ✅ Spec-reviewer (
.claude/agents/spec-reviewer.md, override projet du built-in) valide ce plan vs SPEC.md. - 🔵 Backend-builder implémente
backend/+docker/Dockerfile+docker/entrypoint.sh+Makefile, livre son summary (incluant le contrat API). - 🔵 Frontend-builder lit le summary backend puis implémente le front (
frontend/UNIQUEMENT, jamaise2e/). - 🔵 Code-reviewer relit le diff du sprint (LSP-first, focus bugs/qualité/scope).
- 🔵 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é viamake start. - 🟢 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.