Files
stupid-simple-network-inven…/SECURITY_FIXES_APPLIED.md
T
olivier 88cf6458d0 Initial commit — Stupid Simple Network Inventory
Application web d'inventaire réseau manuel avec FastAPI, Vue 3 et Docker.
Inclut l'authentification JWT, la découverte ICMP, et la topologie en cards CSS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:19:19 +02:00

129 lines
5.0 KiB
Markdown

# 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 `<AccountModal :forced="true">` 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 |