update
This commit is contained in:
133
docs/README_V7_12.md
Normal file
133
docs/README_V7_12.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# PTA V7.12 — Second facteur pour les comptes Service Enfance et DSI
|
||||
|
||||
## Le problème traité
|
||||
|
||||
La V7.11 a remplacé le sélecteur de rôle par un jeton personnel. C'était un progrès décisif, mais un
|
||||
jeton reste **un secret qui circule** : il est produit par la DSI, transmis par courriel ou sur
|
||||
papier, parfois recopié dans un carnet. Un jeton intercepté suffisait à valider les plannings de
|
||||
toute la collectivité (Service Enfance), voire à distribuer de nouveaux accès (DSI).
|
||||
|
||||
Le risque n'est pas le même selon le compte. Un agent ne peut que lire son propre planning ; un
|
||||
responsable ne touche qu'aux brouillons de son lieu. Ces deux rôles-là restent à un seul facteur —
|
||||
alourdir leur connexion coûterait plus qu'il ne protégerait.
|
||||
|
||||
## Ce qui change
|
||||
|
||||
### 1. Deux facteurs, mais seulement là où c'est justifié
|
||||
|
||||
| Rôle | Jeton | Mot de passe |
|
||||
| --- | --- | --- |
|
||||
| Agent | oui | — |
|
||||
| Responsable de structure | oui | — |
|
||||
| **Service Enfance** | oui | **oui** |
|
||||
| **DSI** | oui | **oui** |
|
||||
|
||||
La règle vit **en base**, dans la vue `v_acces_compte` (`mot_de_passe_requis`), comme le reste des
|
||||
droits depuis la V7.8 : étendre le second facteur à un autre rôle est une modification SQL, pas un
|
||||
redéploiement.
|
||||
|
||||
Les deux secrets sont volontairement de nature différente : le jeton est **remis** par la DSI, le
|
||||
mot de passe est **choisi** par le titulaire et n'est jamais affiché nulle part.
|
||||
|
||||
### 2. Le mot de passe se choisit à la première connexion
|
||||
|
||||
La DSI ne pose aucun mot de passe initial, et ne peut donc en connaître aucun. Le parcours :
|
||||
|
||||
1. la DSI crée le compte et remet le jeton ;
|
||||
2. à la première connexion, le jeton est reconnu et l'écran **impose de choisir un mot de passe** ;
|
||||
3. aux connexions suivantes : jeton, puis mot de passe.
|
||||
|
||||
Entre les deux étapes, **rien n'est ouvert** : la session ne porte qu'une attente horodatée, périmée
|
||||
au bout de cinq minutes. Un jeton validé sans mot de passe ne donne accès à aucune page — vérifié :
|
||||
`dsi.php` renvoie alors vers l'écran de connexion.
|
||||
|
||||
Le compte est **relu en base entre les deux étapes** : si la DSI révoque le compte pendant que
|
||||
l'utilisateur tape son mot de passe, la session ne s'ouvre pas.
|
||||
|
||||
### 3. Politique : la longueur, et rien d'autre
|
||||
|
||||
- **12 caractères minimum**, 72 maximum. Aucune règle de composition : exiger une majuscule et un
|
||||
chiffre produit `Motdepasse1`, pas un secret. Une phrase dont on se souvient vaut mieux.
|
||||
- La borne haute n'est pas arbitraire : `password_hash()` utilise bcrypt, qui **ignore
|
||||
silencieusement** tout ce qui dépasse 72 octets. Mieux vaut refuser que laisser croire.
|
||||
- Le jeton lui-même est refusé comme mot de passe : le recopier ramènerait les deux facteurs à un
|
||||
seul secret.
|
||||
- Hachage par `password_hash()` (bcrypt), avec remise à niveau automatique si PHP change
|
||||
d'algorithme par défaut. C'est un secret choisi par un humain, donc attaquable par dictionnaire :
|
||||
contrairement au jeton, le hachage lent a ici tout son sens.
|
||||
|
||||
### 4. Changement et oubli
|
||||
|
||||
- **Changer son mot de passe** : bouton « Mot de passe » dans l'en-tête, visible des seuls comptes
|
||||
concernés. L'ancien mot de passe est exigé — sans quoi un poste laissé ouvert suffirait à
|
||||
confisquer un compte.
|
||||
- **Oubli** : la DSI **efface** le mot de passe depuis sa page (bouton « Effacer le mot de passe »).
|
||||
Elle ne le remplace pas : le titulaire en choisit un nouveau à sa connexion suivante, avec son
|
||||
jeton inchangé. La DSI ne connaît donc jamais le mot de passe de personne, pas même à l'instant de
|
||||
la réinitialisation.
|
||||
- La colonne « État » de la liste des comptes affiche « Mot de passe défini » ou « Mot de passe à
|
||||
définir ». Le second n'est pas une anomalie : c'est l'état normal d'un compte neuf.
|
||||
|
||||
### 5. Le frein anti-force-brute couvre les deux facteurs
|
||||
|
||||
Le compteur du journal (`JournalModel::echecsRecents()`) additionne désormais `JETON_REFUSE` et
|
||||
`MOT_DE_PASSE_REFUSE` : essayer mille mots de passe derrière un jeton volé bute sur le même mur
|
||||
qu'essayer mille jetons — 10 échecs en 15 minutes par adresse IP, puis plus rien n'est comparé.
|
||||
|
||||
### 6. Ce que le journal enregistre en plus
|
||||
|
||||
| Action | Catégorie | Quand |
|
||||
| --- | --- | --- |
|
||||
| `JETON_ACCEPTE_ATTENTE_MDP` | `CONNEXION` | Jeton reconnu, mot de passe attendu |
|
||||
| `MOT_DE_PASSE_REFUSE` | `CONNEXION` | Mot de passe incorrect derrière un jeton valide |
|
||||
| `MOT_DE_PASSE_DEFINI` | `ECRITURE` | Premier choix par le titulaire |
|
||||
| `MOT_DE_PASSE_MODIFIE` | `ECRITURE` | Changement par le titulaire |
|
||||
| `MOT_DE_PASSE_REINITIALISE` | `ADMINISTRATION` | Effacement par la DSI |
|
||||
|
||||
`CONNEXION_REUSSIE` porte en contexte les facteurs réellement présentés (`["jeton"]` ou
|
||||
`["jeton","mot_de_passe"]`) : le journal distingue donc une connexion à un facteur d'une connexion à
|
||||
deux.
|
||||
|
||||
## Mise en service
|
||||
|
||||
Rien à faire hors la migration `database/migration_v7_9f_mot_de_passe_admin.sql`, appliquée
|
||||
automatiquement au démarrage. **Les comptes Service Enfance et DSI existants n'ont pas de mot de
|
||||
passe** : leur prochaine connexion, jeton en main, leur en fera choisir un. Aucun jeton n'est
|
||||
invalidé, aucun compte n'est bloqué.
|
||||
|
||||
Prévenez les titulaires concernés : ils verront un écran qu'ils n'attendent pas.
|
||||
|
||||
### Si le dernier accès DSI perd son mot de passe
|
||||
|
||||
Le cas ne se résout pas dans l'interface, puisqu'il faudrait être connecté pour le faire. En SQL :
|
||||
|
||||
```sql
|
||||
UPDATE compte_acces SET mot_de_passe_hash = NULL, date_mot_de_passe = NULL WHERE id_compte = 1;
|
||||
```
|
||||
|
||||
La connexion suivante, avec le jeton, redemandera d'en définir un. Si le jeton lui-même est perdu,
|
||||
voir la procédure de `README_V7_11.md`.
|
||||
|
||||
## Fichiers
|
||||
|
||||
| Fichier | Rôle |
|
||||
| --- | --- |
|
||||
| `database/migration_v7_9f_mot_de_passe_admin.sql` | Colonnes `mot_de_passe_hash` / `date_mot_de_passe`, vue `v_acces_compte` enrichie de `mot_de_passe_requis` et `mot_de_passe_defini` |
|
||||
| `app/Controllers/RoleController.php` | Connexion en deux étapes, définition, vérification, changement |
|
||||
| `app/Models/CompteModel.php` | Politique, hachage, vérification, réinitialisation |
|
||||
| `app/Models/JournalModel.php` | Frein étendu au second facteur |
|
||||
| `app/Controllers/DsiController.php`, `api/dsi_compte_mot_de_passe.php` | Effacement par la DSI |
|
||||
| `app/Views/pages/role.php` | Les quatre états de l'écran de connexion |
|
||||
| `app/Views/layouts/header.php` | Lien « Mot de passe », pour les seuls comptes concernés |
|
||||
|
||||
## Ce que la V7.12 ne fait pas
|
||||
|
||||
- **Pas de verrouillage du compte** après N échecs : seul le frein par adresse IP s'applique. Un
|
||||
verrouillage par compte permettrait à quiconque connaît un jeton de bloquer volontairement un
|
||||
administrateur.
|
||||
- **Pas d'expiration ni d'historique des mots de passe** : forcer un changement tous les trois mois
|
||||
produit `Motdepasse-Mars`, `Motdepasse-Juin`. Le mot de passe se change quand il y a une raison.
|
||||
- **Pas de réinitialisation par courriel** : il n'y a pas d'envoi de courriel dans PTA, et la boîte
|
||||
aux lettres deviendrait le vrai facteur d'authentification.
|
||||
- **Pas de TOTP** : le jeton joue déjà le rôle du « quelque chose que l'on détient ». Un
|
||||
authentificateur mobile serait le prolongement naturel si le besoin s'en faisait sentir.
|
||||
Reference in New Issue
Block a user