Commit 6143e6f4 authored by Jordan Grossemy's avatar Jordan Grossemy
Browse files

Sécurité : correctifs d'audit, et second facteur TOTP sur les comptes

Deux sujets dans un seul commit, et il faut le dire : les correctifs de l'audit
et le second facteur se recouvrent sur six fichiers — le garde de session, les
types de jeton, le schéma, la documentation. Le TOTP s'appuie directement sur la
révocation de session introduite par le correctif M3. Les séparer aurait demandé
un découpage par section, avec un commit intermédiaire dans un état que je ne
pouvais pas compiler pour le vérifier.

═══════════════════════════════════════════════════════════════════
PARTIE 1 — Audit de sécurité : 4 constats hauts, 6 moyens, 6 faibles
═══════════════════════════════════════════════════════════════════

H1 — XSS stocké via la GED

Le type MIME stocké était celui DÉCLARÉ PAR LE CLIENT, et il était renvoyé tel
quel en affichage direct. Déposer un fichier HTML suffisait à faire exécuter du
script sur l'origine de l'application chez quiconque ouvrait le document. Le
cookie est httpOnly, donc illisible par ce script — mais il part tout seul sur
chaque requête : le script agissait au nom de sa victime sans jamais voir son
jeton. Un concepteur obtenait ainsi d'un administrateur les actions que son
propre rôle lui refuse.

Trois barrières cumulatives (common/file-serving.ts), parce qu'aucune ne suffit
seule : liste blanche à l'entrée, téléchargement forcé sauf types sûrs, et
`nosniff` qui empêche le navigateur de redeviner le type — sans quoi la première
serait contournable. La politique `sandbox` s'y ajoute pour neutraliser ce qui est
DÉJÀ en base : les fichiers déposés avant ce durcissement n'ont pas de type fiable.

H2 — XSS via logo SVG, atteignable sans authentification

`image/svg+xml` figurait dans la liste blanche des logos sous la mention « rien
d'exécutable ». C'est faux : un SVG accepte <script>, <foreignObject> et des
gestionnaires d'événements. La liste des images d'exercice l'excluait déjà pour
cette raison exacte — les deux listes du même dépôt se disaient « rien
d'exécutable » et une seule le respectait. Servi par une route volontairement
ouverte, il donnait un XSS stocké sans session.

H3 — Aucun plafond sur /auth/login

Le garde de plafonnement existait, testé, posé sur le bac à sable et les routes
de compte. Pas sur l'authentification. Deux conséquences : force brute libre, et
déni de service par le processeur — chaque essai coûtant un bcrypt à 12 tours,
quelques dizaines de requêtes concurrentes saturaient une API mono-processus.

10 essais par quart d'heure et par adresse, 5 par compte visé. Les deux plafonds
répondent à deux attaques distinctes et aucun ne remplace l'autre ; le second ne
peut pas servir à bloquer autrui puisqu'il s'ajoute au premier.

H4 — Le scénario partait chez les joueurs

`exerciseRoom` est partagée : joueurs et écrans de salle y sont aussi. Le rappel
d'appel, l'avancement des injects et le journal d'événements y étaient diffusés.
Le code portait pourtant « rappel côté animateur : aucune diffusion aux joueurs »
à la ligne PRÉCÉDANT la diffusion, et le type partagé disait « non transmis aux
joueurs ».

Les clients joueurs ne s'y abonnaient pas, donc rien ne s'affichait. Ne pas
afficher n'est pas ne pas transmettre : les trames arrivaient et se lisaient dans
les outils de développement. Pour un public d'exercices CYBER, supposer que
personne ne les ouvrira n'était pas raisonnable — et un joueur qui lit le scénario
à l'avance annule l'exercice.

Salle `ex:{id}:staff`, que les sockets d'animation rejoignent EN PLUS de la salle
commune : aucune double émission, et ce qui est légitimement commun (horloge,
presse, réseaux sociaux, main courante — vérifié composant par composant) continue
d'y passer.

Moyens

M1  Aucun en-tête de sécurité. Écrits à la main plutôt qu'avec `helmet` : quatre
    en-têtes ne justifiaient pas une dépendance de plus dans un dépôt qui assume
    sa frugalité. L'empreinte du script en ligne d'index.html est CALCULÉE au
    démarrage depuis le fichier servi — une constante en dur aurait dérivé au
    premier changement de page, et une empreinte fausse casse l'application en
    silence. Fichier introuvable : la directive est omise plutôt que fausse.
M2  `trust proxy` inactif. Le défaut se payait à l'envers : derrière un proxy,
    tous les visiteurs partageaient un seau, soit trois bacs à sable par heure
    pour la planète. TRUSTED_PROXIES, avec les deux erreurs symétriques
    documentées au README.
M3  Aucune révocation de session. Réinitialiser un mot de passe — le geste qu'on
    fait quand on soupçonne une compromission — laissait la session de l'attaquant
    vivre huit heures. Compteur `sessionEpoch` dans le jeton. Les jetons émis
    avant ne le portent pas et restent valides : un correctif de sécurité ne doit
    pas déconnecter tout le monde d'un coup.
M4  SMTP sans TLS imposé. STARTTLS opportuniste : un attaquant réseau retirait la
    capacité de la réponse EHLO et obtenait en clair les identifiants SMTP et les
    liens de réinitialisation. `requireTLS` + TLS 1.2 minimum.
M5  Objets des messages joueur→animation diffusés à tous les joueurs. Même
    correctif que H4.
M6  Journal d'audit d'instance et demandes d'inscription conservés sans limite de
    durée, données personnelles comprises — y compris les demandes REJETÉES, qui
    n'ont ouvert aucun compte. Durées définies et documentées.

Faibles

L1  Confinement de chemin par préfixe : un répertoire frère partageant le préfixe
    passait. Non exploitable (clés UUID, chemins de kit venant du dépôt), mais
    c'était une traversée en attente d'une entrée utilisateur.
L2  Secrets de 16 caractères acceptés pour du HS256 et de l'AES-256. Portés à 32.
L3  Troncature silencieuse à 72 octets par bcrypt, alors que 200 caractères
    étaient acceptés.
L4  Listes blanches de routes comparées par `endsWith`, donc par sous-chaîne.
    Rien d'exploitable — un garde s'exécute après le routage — mais toute route
    ajoutée plus tard et terminant par le même suffixe héritait de l'exemption en
    silence. Comparaison exacte : la classe disparaît.
L5  NON TRAITÉ. multer 1.x et bcryptjs 2.4.3 : montées majeures impossibles à
    éprouver sans exécuter les tests. Consigné dans DEPENDENCIES.md avec la
    décision à prendre.
L6  APP_URL retombait sur localhost, produisant des liens morts sans le dire.

Recommandation appliquée : la passerelle temps réel ne vérifiait pas la
suspension d'organisation. Une organisation suspendue gardait ses connexions, et
une nouvelle réussissait.

Trouvés en corrigeant

- `LoginDto.password` sans borne supérieure.
- `CreateUserDto` acceptait 8 caractères là où le libre-service en exigeait 10 :
  un compte créé par un administrateur pouvait être plus faible que celui que
  l'intéressé se serait choisi. D'où la politique centralisée, minimum partagé
  avec l'interface pour qu'un écran n'annonce plus une règle différente de celle
  qui est appliquée.
- SIX dépendances déclarées mais absentes de DEPENDENCIES.md, contrairement à la
  politique du dépôt.
- La GED n'avait AUCUNE section de sécurité. Ce n'est probablement pas étranger
  au fait qu'elle portait le constat le plus grave.

Confirmé sain, sans modification

Zéro injection SQL possible (une seule requête brute, `SELECT 1`). Zéro puits XSS
sur 16 800 lignes de front — pas un `dangerouslySetInnerHTML`, pas un `eval`.
Cloisonnement verrouillé par construction : les 33 modèles portant `tenantId`
sont tous couverts, aux deux exceptions vérifiées comme délibérées. Jetons à usage
unique corrects de bout en bout. Chiffrement au repos bien fait.

═══════════════════════════════════════════════════════════════════
PARTIE 2 — Second facteur TOTP (RFC 6238)
═══════════════════════════════════════════════════════════════════

Optionnel pour tous, OBLIGATOIRE pour l'administration : INSTANCE_ADMIN dans
l'organisation système, TENANT_ADMIN dans une organisation cliente. Ce sont les
deux rôles qui créent des comptes, changent des rôles et effacent en cascade.

L'obligation se lit sur TOUTES les appartenances

Le point important. Le jeton ne désigne qu'une organisation, et un compte peut en
avoir plusieurs : ne regarder que l'active aurait laissé un administrateur
d'organisation se soustraire à l'obligation en basculant vers une organisation où
il n'est qu'animateur. Un test épingle ce cas.

Deux niveaux d'exigence

Immédiat pour l'administration d'instance : peu nombreuse, elle ne conduit pas
d'exercice, rien ne justifie de différer. Délai de grâce de 14 jours (réglable en
base) pour l'administration d'organisation : un administrateur peut être en pleine
conduite d'exercice, téléphone hors de portée, et le bloquer à cet instant serait
un défaut, pas une protection.

Le mot de passe seul n'ouvre rien

`POST /auth/login` ne pose AUCUN cookie de session quand un second facteur est
actif : seulement un jeton d'attente de cinq minutes, dans un cookie DISTINCT.
Trois barrières le séparent d'une session, plutôt qu'un contrôle unique qu'on
pourrait oublier : ce n'est pas le cookie que le garde lit, le jeton porte un
marqueur `stage` que le garde refuse explicitement, et il expire vite.

L'échéance voyage dans le JETON, pas relue à chaque requête. C'est correct parce
que toute promotion de rôle incrémente le compteur de révocation, donc force une
nouvelle signature : la valeur ne peut pas se périmer dans le sens permissif.

Anti-rejeu

La tolérance de ± 1 pas laisse un code valable jusqu'à 90 s — assez pour le
rejouer. Le dernier pas accepté est enregistré et tout pas antérieur ou égal
refusé, même si le code est mathématiquement correct.

Trois voies de secours

Rendre une protection obligatoire sans prévoir sa reprise en main, c'est préparer
une panne.

1. Codes de secours : 8 codes à usage unique, empreinte seule stockée, alphabet
   sans caractères ambigus (ils se recopient à la main). Consommation ATOMIQUE
   par écriture conditionnelle : deux requêtes simultanées avec le même code ne
   peuvent pas réussir toutes les deux.
2. Réarmement par la console d'instance, MOTIF OBLIGATOIRE, tracé au journal
   persistant. Le délai de grâce repart à zéro — délibérément : sinon une échéance
   déjà dépassée rebloquait la personne dans la seconde et le geste ne servait à
   rien.
3. Commande d'exploitation pour l'administrateur d'instance, qui n'a personne
   au-dessus de lui. Elle demande un accès serveur et base, précisément ce qui en
   fait un privilège acceptable — cet accès permet déjà tout. Elle avertit et
   exige --confirm avant d'écrire.

Chacune révoque toutes les sessions du compte : on vient de baisser son niveau
d'authentification.

Le second facteur ne peut pas être retiré quand il est imposé. Sinon l'obligation
ne vaudrait rien — il suffirait de l'activer une fois puis de le retirer.

Dépendances

otplib 12.0.1 et qrcode 1.5.4, épinglées, licences et niveau de maintenance
documentés. otplib n'a pas été publiée depuis 2021 : la norme TOTP est figée, donc
ce n'est pas alarmant, mais c'est consigné avec la porte de sortie (otpauth, la
primitive étant isolée dans un seul fichier testé). Le QR est rendu dans le
NAVIGATEUR, en data-URI dans une <img> : rien ne sort de l'instance, et le dépôt
ne gagne pas son premier dangerouslySetInnerHTML.

Trois défauts trouvés en écrivant, et corrigés

- Dans mon propre code : `verifyTotp` recevait l'instant pour calculer le pas,
  mais otplib vérifiait contre l'horloge réelle. Les deux pouvaient diverger et
  l'anti-rejeu aurait enregistré un pas qui n'était pas celui qui avait répondu.
  L'instant est désormais passé explicitement.
- Un verrou possible : une session en observation ne peut pas s'enrôler (lecture
  seule) et n'aurait pas pu sortir. Non atteignable — un administrateur
  d'instance n'obtient aucune session avant enrôlement — mais /instance/exit est
  ajouté à la liste blanche par précaution.
- Du code mort que j'avais écrit : `requirementFor` et `hashesEqual`, ce dernier
  testé mais jamais appelé, avec un commentaire sur les attaques temporelles qui
  ne s'appliquait à rien.

═══════════════════════════════════════════════════════════════════
VÉRIFICATIONS
═══════════════════════════════════════════════════════════════════

NON EXÉCUTÉES. Ni node ni pnpm n'étaient disponibles, node_modules n'était pas
installé. Aucun test, aucun typecheck, aucun lint. C'est le point le plus
important de ce message : ce commit contient du code qui n'a jamais tourné.

Relecture statique faite : constructeurs alignés dans les harnais existants
(AuthService en avait un de moins), longueurs de ligne contrôlées pour prettier,
39 entrées i18n ajoutées avec vérification programmatique qu'aucune chaîne ne
manque, recoupement automatisé des dépendances contre DEPENDENCIES.md.

Deux points s'appuient sur une API d'otplib non exécutée ici : `clone()` et
`checkDelta()`. Un échec y serait immédiat et bruyant au démarrage, pas une
faille silencieuse — mais c'est la première chose à confirmer.

À faire avant de lancer :
  pnpm install
  pnpm --filter @cythin/api exec prisma migrate deploy && prisma generate
  pnpm test && pnpm typecheck && pnpm lint

Le lockfile n'est PAS à jour : otplib et qrcode sont déclarées, pas installées.

Trois changements peuvent empêcher le démarrage en production :
  - JWT_SECRET doit faire 32 caractères (contre 16) — échec au démarrage sinon.
  - APP_URL devient obligatoire — même effet.
  - TRUSTED_PROXIES à 0 par défaut : derrière un proxy, les plafonds restent
    globaux tant qu'il n'est pas renseigné.

À rejouer à la main : téléverser un document de chaque type accepté et refusé ;
vérifier dans un onglet joueur qu'aucun call:reminder n'arrive plus ; activer le
TOTP sur un compte, consommer un code de secours, et vérifier qu'un administrateur
d'organisation au délai expiré ne voit plus que l'écran d'enrôlement.

Co-Authored-By: Claude (RCA)
parent cad802b0
Loading
Loading
Loading
Loading
+25 −1
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@

Conformément à la politique interne, toute dépendance tierce est documentée avec sa **licence** et une **évaluation de son niveau de maintenance**. Les versions sont **épinglées** (aucune version flottante) dans les `package.json` ; le fichier `pnpm-lock.yaml` fait foi.

_Dernière mise à jour : 2026-08-21 (contenus embarqués : fiches réflexes, kit vitrine)._
_Dernière mise à jour : 2026-08-22 (second facteur TOTP : otplib et qrcode)._

> **nodemailer** est la seule dépendance capable d'émettre du trafic réseau sortant. Elle est
> confinée au module `platform-mail`, qui n'est **jamais** appelé depuis le moteur d'exercice, et
@@ -16,15 +16,21 @@ _Dernière mise à jour : 2026-08-21 (contenus embarqués : fiches réflexes, ki
| @nestjs/config | 3.3.0 | MIT | Active |
| @nestjs/jwt | 10.2.0 | MIT | Active |
| @nestjs/serve-static | 4.0.2 | MIT | Active |
| @nestjs/websockets, /platform-socket.io | 10.4.7 | MIT | Active (temps réel de l'exercice) |
| @prisma/client | 5.22.0 | Apache-2.0 | Très active (Prisma) |
| bcryptjs | 2.4.3 | MIT | Stable (hachage de mots de passe, pur JS — pas de compilation native) |
| class-validator | 0.14.1 | MIT | Active (validation des DTO) |
| class-transformer | 0.5.1 | MIT | Active (requis par le ValidationPipe) |
| cookie-parser | 1.4.7 | MIT | Stable (lecture du cookie de session) |
| multer | 1.4.5-lts.1 | MIT | **À réévaluer** — branche 1.x en maintenance seule, une branche 2.x existe en amont (cf. note ci-dessous) |
| nodemailer | 6.9.16 | MIT | Très active (référence de l'envoi SMTP en Node, ~20 M téléchargements/semaine, sans dépendance tierce) |
| otplib | 12.0.1 | MIT | **Stable, peu active** — dernière publication en 2021. Implémente RFC 4226/6238, une norme figée : l'absence d'évolution n'y est pas un signal négatif. Aucune dépendance réseau. À surveiller (cf. « À réévaluer ») |
| qrcode | 1.5.4 | MIT | Active (référence de la génération de QR en JS). Utilisée **côté navigateur uniquement** : le QR d'enrôlement est rendu localement, rien ne sort de l'instance |
| reflect-metadata | 0.2.2 | Apache-2.0 | Stable (requis par NestJS) |
| rxjs | 7.8.1 | Apache-2.0 | Très active |
| react, react-dom | 18.3.1 | MIT | Très active (Meta) |
| socket.io | 4.8.1 | MIT | Très active (référence du temps réel en Node) |
| socket.io-client | 4.8.1 | MIT | Très active (côté navigateur) |

## Outillage (développement)

@@ -45,6 +51,8 @@ _Dernière mise à jour : 2026-08-21 (contenus embarqués : fiches réflexes, ki
| @types/bcryptjs | 2.4.6 | MIT | Types |
| @types/nodemailer | 6.4.17 | MIT | Types |
| @types/cookie-parser | 1.4.8 | MIT | Types |
| @types/multer | 1.4.12 | MIT | Types |
| @types/qrcode | 1.5.5 | MIT | Types |
| tsconfig-paths | 4.2.0 | MIT | Stable |

## Images de conteneurs
@@ -70,6 +78,22 @@ Le code n'est pas seul à être licencié : les contenus pédagogiques versionn
> **Avant d'ajouter un contenu tiers**, vérifier sa licence, la mention d'attribution attendue et
> sa compatibilité avec l'AGPL-3.0 du dépôt — la licence du code ne couvre pas le contenu d'un tiers.

## À réévaluer

Relevé par l'audit de sécurité du 2026-08-22. Aucune vulnérabilité n'est constatée ici : ces
deux paquets sont signalés au titre de l'**évaluation du niveau de maintenance** que la politique
interne exige, pas d'un défaut connu. **Aucune analyse de composition (`npm audit` ou
équivalent) n'a été exécutée** — elle reste à faire.

| Paquet | Constat | Ce qu'il faut décider |
|---|---|---|
| multer 1.4.5-lts.1 | La branche 1.x ne reçoit plus que de la maintenance ; une branche 2.x existe en amont. | Évaluer la migration. Elle touche les **trois** routes de téléversement (documents, images, logos) et comporte des ruptures d'API : à ne pas faire sans repasser ces trois parcours. |
| otplib 12.0.1 | Dernière publication en 2021. La norme TOTP ne bouge pas, donc le code non plus — mais un paquet d'authentification sans mainteneur actif est un risque à suivre, pas à ignorer. | Surveiller. En cas d'abandon confirmé, `otpauth` (MIT, activement maintenue) couvre le même besoin, et la primitive est isolée dans `apps/api/src/auth/totp.ts` — un seul fichier à reprendre, couvert par des tests. |
| bcryptjs 2.4.3 | Publiée en 2017. Fonctionne, sans dépendance native, mais n'évolue plus à ce rythme. | Décider entre le statu quo, la montée vers la branche suivante, et un passage à `argon2id`. Un changement d'algorithme impose une **remigration des empreintes à la connexion** : ce n'est pas une montée de version, c'est un chantier. |

> Ces deux points n'ont **pas** été traités par le correctif d'audit : une montée de version
> majeure sans pouvoir exécuter la suite de tests aurait été plus risquée que le statu quo.

## Principe

Toutes les dépendances retenues sont sous licence **permissive** (MIT / Apache-2.0 / BSD / PostgreSQL), compatibles avec une distribution open-source et un usage commercial. Toute nouvelle dépendance doit être ajoutée à ce tableau avec sa licence et une évaluation de maintenance avant d'être introduite.
+41 −2
Original line number Diff line number Diff line
@@ -88,12 +88,51 @@ docs/ Cahier des charges, ADR, spec technique, maquettes

| Variable | Rôle | Défaut |
|---|---|---|
| `JWT_SECRET` | Secret de signature des sessions (**obligatoire en prod**) | — |
| `JWT_SECRET` | Secret de signature des sessions. **Obligatoire en prod**, 32 caractères minimum | — |
| `DATABASE_URL` | Connexion PostgreSQL | voir `.env.example` |
| `APP_URL` | URL publique (liens joueurs) | `http://localhost:3000` |
| `APP_URL` | URL publique (liens joueurs, activation, mot de passe). **Obligatoire en prod** : l'API refuse de construire un lien sans elle, plutôt que d'en envoyer un vers `localhost` | `http://localhost:3000` (hors prod) |
| `TRUSTED_PROXIES` | Nombre de proxys inverses devant l'API. **À renseigner dès qu'il y en a un** (voir ci-dessous) | `0` |
| `SECRETS_KEY` | Clé de chiffrement des secrets stockés (mot de passe SMTP). À défaut, dérivée de `JWT_SECRET` | — |
| `RETENTION_DAYS` | Purge auto des exercices archivés après N jours (`0` = désactivé) | `0` |
| `AUDIT_RETENTION_DAYS` | Purge du journal d'audit d'instance après N jours (`0` = désactivé) | `730` |
| `SIGNUP_RETENTION_DAYS` | Purge des demandes d'inscription **traitées** après N jours (`0` = désactivé). Les demandes en attente ne sont jamais purgées | `90` |
| `WEBRTC_ICE_SERVERS` | Serveurs ICE pour les appels vocaux, en JSON. Vide = candidats locaux seulement | — |
| `PORT` | Port HTTP | `3000` |

### Second facteur perdu : réarmer un compte

Le second facteur est obligatoire pour les administrateurs. Un administrateur d'organisation
qui a perdu son téléphone et ses codes de secours se réarme depuis la console d'instance
(Organisations → membre → réarmer, motif obligatoire). Pour un **administrateur d'instance**,
qui n'a personne au-dessus de lui :

```bash
pnpm --filter @cythin/api mfa:reset -- --email=root@exemple.org --confirm
```

En production, où `ts-node` n'est pas installé :

```bash
node dist/scripts/reset-mfa.js --email=root@exemple.org --confirm
```

La commande avertit et n'écrit rien sans `--confirm`. Le compte se reconnecte alors avec son
seul mot de passe, et l'obligation le renvoie immédiatement vers un nouvel enrôlement.

### `TRUSTED_PROXIES` : à ne pas laisser au hasard

Le plafonnement des routes publiques (inscription, mot de passe oublié, bac à sable) compte les
tentatives **par adresse IP**. Les deux réglages erronés se paient, et dans les deux sens :

- **Trop bas** (`0` derrière un proxy) : l'API voit l'adresse du proxy pour tout le monde. Tous les
  visiteurs partagent alors un seul compteur — trois créations de bac à sable par heure pour la
  planète entière, et le plafond frappe les innocents au lieu de l'attaquant.
- **Trop haut** (sans proxy devant) : un client peut forger `X-Forwarded-For` et se donner une
  adresse neuve à chaque requête, donc contourner tout plafond.

Mettez le **nombre réel** de proxys inverses traversés : `1` derrière un Nginx ou un Traefik, plus
si plusieurs sont chaînés.

## Sauvegarde & restauration

Les données sont dans le volume PostgreSQL `db_data`.
+209 −8

File changed.

Preview size limit exceeded, changes collapsed.

+4 −2
Original line number Diff line number Diff line
@@ -11,7 +11,8 @@
    "db:generate": "prisma generate",
    "db:migrate": "prisma migrate dev",
    "db:migrate:deploy": "prisma migrate deploy",
    "db:seed": "ts-node prisma/seed.ts"
    "db:seed": "ts-node prisma/seed.ts",
    "mfa:reset": "ts-node scripts/reset-mfa.ts"
  },
  "prisma": {
    "seed": "ts-node prisma/seed.ts"
@@ -23,7 +24,6 @@
    "@nestjs/core": "10.4.7",
    "@nestjs/jwt": "10.2.0",
    "@nestjs/platform-express": "10.4.7",
    "nodemailer": "6.9.16",
    "@nestjs/platform-socket.io": "10.4.7",
    "@nestjs/serve-static": "4.0.2",
    "@nestjs/websockets": "10.4.7",
@@ -33,6 +33,8 @@
    "class-validator": "0.14.1",
    "cookie-parser": "1.4.7",
    "multer": "1.4.5-lts.1",
    "nodemailer": "6.9.16",
    "otplib": "12.0.1",
    "reflect-metadata": "0.2.2",
    "rxjs": "7.8.1",
    "socket.io": "4.8.1"
+18 −0
Original line number Diff line number Diff line
-- Revocation de session (constat M3 de l'audit de securite).
--
-- Le jeton de session porte le role et l'organisation active, et vaut 8 heures.
-- Le garde ne verifiait que la suspension de l'organisation : retirer une
-- appartenance, retrograder un role ou REINITIALISER UN MOT DE PASSE ne coupait
-- donc aucune session en cours. Le dernier cas est le plus genant : c'est le
-- geste que l'on fait quand on soupconne une compromission, et l'attaquant
-- gardait son acces jusqu'a huit heures.
--
-- Ce compteur est recopie dans le jeton a la signature et compare a chaque
-- requete. L'incrementer perime instantanement tous les jetons deja emis pour ce
-- compte.
--
-- Defaut a 0 : les jetons deja en circulation ne portent pas ce champ. Le garde
-- traite son absence comme 0, donc les sessions ouvertes au moment du deploiement
-- restent valides jusqu'a leur expiration normale — une deconnexion generale de
-- tous les utilisateurs n'est pas un effet acceptable pour un correctif.
ALTER TABLE "User" ADD COLUMN "sessionEpoch" INTEGER NOT NULL DEFAULT 0;
Loading