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>
This commit is contained in:
315
tasks/todo.md
Normal file
315
tasks/todo.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user