# Security Fixes Applied Date: 2026-05-06 ## P0-001 — SEC-FIX-001 : Changement de mot de passe obligatoire au premier login ### Problème Le compte admin par défaut utilisait `admin`/`admin`. N'importe qui ayant accès au port 8080 pouvait se connecter. ### Changements **`backend/models.py`** - Colonne `must_change_password BOOLEAN NOT NULL DEFAULT 0` ajoutée sur `User`. **`backend/main.py`** - Nouvelle migration idempotente `_migrate_users_must_change_password()` — `ALTER TABLE users ADD COLUMN must_change_password BOOLEAN NOT NULL DEFAULT 0` si absente. - `_migrate_users()` : support de la variable d'environnement `INITIAL_ADMIN_PASSWORD`. - Si définie → mot de passe fourni, `must_change_password = 0`. - Sinon → mot de passe `admin`, `must_change_password = 1` (changement forcé au premier login). - Routeurs `vlans`, `devices`, `links`, `discovery` protégés par `require_password_changed` au lieu de `get_current_user`. **`backend/routers/auth.py`** - Dépendance `require_password_changed` : retourne 403 `"Password change required"` si `must_change_password = 1`. - `TokenOut` enrichi du champ `must_change_password: bool`. - `login()` retourne `must_change_password`. - `update_account()` passe `must_change_password` à `False` après changement de mot de passe réussi. - `GET /me` retourne `must_change_password`. **`frontend/src/auth.js`** - Ref `_mustChange` persistée dans `localStorage('auth_mustchange')`. - `mustChangePassword` computed exporté. - `setAuth(token, username, mustChange)` / `clearAuth()` mis à jour. **`frontend/src/App.vue`** - Branche `v-else-if="mustChangePassword"` : affiche `` avant l'application. - `onLogin` et `onAccountUpdated` propagent le flag ; `loadAll` n'est appelé que si `mustChangePassword` est faux. - `onMounted` conditionnel sur `!mustChangePassword.value`. **`frontend/src/components/AccountModal.vue`** - Prop `forced` (Boolean, défaut `false`) : - Masque le bouton ✕ et le bouton Annuler. - Empêche la fermeture par clic sur l'overlay. - Affiche une bannière d'avertissement. - Rend le champ "Nouveau mot de passe" obligatoire. **`frontend/src/i18n.js`** - Clés ajoutées (fr/en/es) : `mustChangePasswordWarning`, `newPasswordRequired`. ### Utilisation ```yaml # docker-compose.yml — optionnel environment: - INITIAL_ADMIN_PASSWORD=MonMotDePasseSecurisé ``` Sans cette variable, le premier login avec `admin`/`admin` force immédiatement le changement de mot de passe. --- ## P1-001 — SEC-FIX-002 : Rate limiting sur POST /api/auth/login ### Problème Aucune limitation du nombre de tentatives de connexion — brute-force possible. ### Changements **`frontend/rate_limit.conf`** (nouveau) - `limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;` **`frontend/Dockerfile`** - `COPY rate_limit.conf /etc/nginx/conf.d/00_rate_limit.conf` — chargé en premier (ordre alphabétique). **`frontend/nginx.conf`** - Location spécifique `= /api/auth/login` avec `limit_req zone=login burst=5 nodelay` et `limit_req_status 429`. **`backend/routers/auth.py`** - Rate limiting in-memory (sans dépendance externe) : - **Par IP** : 20 tentatives / 60 s - **Par username** : 10 tentatives / 900 s (15 min) - Compteurs réinitialisés après login réussi. - `login()` extrait l'IP via `Request.client.host`. - Retourne HTTP 429 `"Too many attempts, try again later"`. **`frontend/src/components/LoginPage.vue`** - Gère le status 429 → affiche `t('tooManyAttempts')`. **`frontend/src/i18n.js`** - Clé ajoutée : `tooManyAttempts`. --- ## P1-002 — SEC-FIX-003 : Validation serveur des entrées sur PUT /api/auth/account ### Problème Aucune validation sur `new_username` et `new_password` — mots de passe vides ou noms d'utilisateurs invalides acceptés. ### Changements **`backend/routers/auth.py`** - `_validate_new_password(password)` : min 8 caractères, au moins une lettre et un chiffre. Lève 400 avec code `password_too_short` ou `password_too_weak`. - `_validate_new_username(username)` : `[a-zA-Z0-9._-]{1,64}`. Lève 400 avec code `username_invalid`. - `update_account()` appelle ces fonctions avant modification. **`frontend/src/components/AccountModal.vue`** - Validation client miroir (évite un aller-retour réseau pour les cas évidents). - Mapping des codes d'erreur backend → clés i18n : `password_too_short`, `password_too_weak`, `username_invalid`. **`frontend/src/i18n.js`** - Clés ajoutées (fr/en/es) : `passwordTooShort`, `passwordTooWeak`, `usernameInvalid`. --- ## Fichiers modifiés | Fichier | Fixes | |---------|-------| | `backend/models.py` | P0-001 | | `backend/main.py` | P0-001 | | `backend/routers/auth.py` | P0-001, P1-001, P1-002 | | `frontend/rate_limit.conf` | P1-001 (nouveau) | | `frontend/Dockerfile` | P1-001 | | `frontend/nginx.conf` | P1-001 | | `frontend/src/auth.js` | P0-001 | | `frontend/src/i18n.js` | P0-001, P1-001, P1-002 | | `frontend/src/App.vue` | P0-001 | | `frontend/src/components/LoginPage.vue` | P0-001, P1-001 | | `frontend/src/components/AccountModal.vue` | P0-001, P1-002 |