# 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.
