Authentification, 2FA OTP et Session
La console web TkushPatch implémente une chaîne d'authentification multi-facteurs complète combinant vérification de mot de passe BCrypt (coût 12), validation par code OTP à 6 chiffres envoyé par email (2FA), cookies de session cloisonnés par organisation (tenant) et verrouillage automatique d'inactivité.
Parcours d'authentification
- Saisie des identifiants : L'opérateur saisit son adresse email et son mot de passe sur la page de connexion.
- Validation 2FA (si activée) : Saisie du code à usage unique de validation.
- Émission de la session : Création d'un cookie de session sécurisé (
HttpOnly,SameSite=Strict). - Accès au tableau de bord : Redirection vers la vue d'ensemble des applications.
1. Page de connexion (/login)
La page /login (src/app/login/page.tsx) se déroule en deux étapes :
- Saisie des identifiants :
- Email professionnel (
email) et Mot de passe (password). - Option avancée : possibilité de spécifier l'identifiant d'organisation (
requestedTenantId) si un opérateur appartient à plusieurs espaces. - Première connexion d'un collaborateur invité (
status = INVITED,passwordHash = null) : le mot de passe saisi lors de la première connexion est automatiquement haché en BCrypt (BCryptPasswordEncoder(12)) et le statut passe àACTIVE. - Rétrocompatibilité : les anciens hachages SHA-256 hérités sont automatiquement migrés vers BCrypt lors d'une connexion valide.
- Email professionnel (
- Validation 2FA par Email OTP :
- Si la politique MFA de l'organisation n'est pas
DISABLED, le backend retournerequireOtp: trueainsi qu'unpreAuthTicket(Base64 contenantemail,tenantId,role,fullName), la durée d'expiration (expiresInSec) et le délai anti-spam (cooldownSec). - L'opérateur saisit le code à 6 chiffres reçu par email (interface à 6 cases avec support du copier-coller automatique).
- Un bouton Renvoyer le code appelle
POST /api/auth/resend-otpdès que le compte à rebourscooldownSecatteint0. - En environnement de développement uniquement (
mode développementetota.security.otp.dev-bypass=true), un encart d'aide affiche le code de testdevMockCode(123456). Ce mécanisme est formellement bloqué en production parOtpHardProductionGuard.
- Si la politique MFA de l'organisation n'est pas
2. Création d'organisation (/register)
La page /register (src/app/register/page.tsx) permet d'inscrire une nouvelle organisation (tenant) et son premier administrateur (ROLE_ADMIN) en 2 étapes via POST /api/auth/register :
- Étape 1 (Demande et envoi OTP) : L'opérateur renseigne son nom complet (
fullName), son adresse email (email), son mot de passe (password) et le nom de l'organisation (organizationName). Le backend vérifie qu'aucun compte actif n'existe déjà avec cet email (sinonHTTP 409 Conflict), génère un OTP de typeREGISTER_VERIFYet envoie l'email de vérification. - Étape 2 (Validation OTP et provisionnement) : L'opérateur soumet
action: "verify"avec le code OTP reçu. Le backend consomme l'OTP, crée l'entitéTenantJpaEntity(avec identifiant normalisé en minuscules et tirets,enforceFourEyes = falsepar défaut en mode Solo) et crée l'utilisateur avec le rôleROLE_ADMINet le statutACTIVE.
3. Mot de passe oublié (/forgot-password)
La page /forgot-password (src/app/forgot-password/page.tsx) permet de réinitialiser un mot de passe sans exposer l'existence des comptes :
- Étape 1 (
POST /api/auth/password-reset/request) : Prend uniquement{ email }. Pour empêcher toute énumération de comptes (Account Enumeration), l'API renvoie systématiquementHTTP 200 OKavec le message générique"Si un compte correspondant existe, un code de réinitialisation a été envoyé.". Si un compte actif existe, un OTP de fluxPASSWORD_RESETest généré et envoyé par email. - Étape 2 (
POST /api/auth/password-reset/confirm) : Prend{ email, code, password }(minimum 8 caractères). Vérifie et consomme l'OTPPASSWORD_RESET, puis met à jour le hachage BCrypt sur tous les comptes actifs associés à cet email.
4. Gestion de session et cookies (AuthContext)
Une fois authentifié, le contexte AuthProvider (src/lib/auth/AuthContext.tsx) persiste l'état de l'opérateur :
Stockage client (localStorage)
- Clé
tks_user_session: Objet JSON contenantusername,email,fullName,role,tenantId,organizationName,tokenetloggedInAt. - Clé
tks_session_locked: Indicateur booléen ("true") lorsque la session est verrouillée pour inactivité.
Cookies HTTP (lus par le Middleware Next.js)
Trois cookies sont posés avec path=/; max-age=86400; SameSite=Lax (durée de vie de 24 heures) :
| Nom du cookie | Contenu | Usage dans middleware.ts |
|---|---|---|
tks_session | Jeton de session (ou "authenticated") | Prouve que l'opérateur possède une session active |
tks_tenant | Identifiant du tenant (ex. sara-money) | Injecté dans l'en-tête HTTP x-tenant-id |
tks_role | Rôle RBAC (ex. ROLE_SECURITY_OFFICER) | Évalué par hasAccessToRoute(userRole, pathname) et injecté dans x-user-role |
Lors de la déconnexion (logout()), localStorage.removeItem("tks_user_session") et localStorage.removeItem("tks_session_locked") sont exécutés, les 3 cookies sont purgés (max-age=0) et l'opérateur est redirigé vers /login.
5. Verrouillage automatique d'inactivité (IdleTimer)
Le composant IdleTimer (src/lib/auth/IdleTimer.tsx) protège les postes de travail laissés sans surveillance :
- Surveillance d'activité : Écoute les événements DOM
mousemove,mousedown,keydown,scrollettouchstart. - Délai d'inactivité (
IDLE_TIMEOUT_MS) : Au bout de 15 minutes (15 * 60 * 1000 ms) sans interaction, une bannière d'alerte s'affiche avec un compte à rebours de 60 secondes (WARNING_SECONDS = 60). - Verrouillage (
isLocked = true) : Si l'opérateur ne clique pas sur Maintenir la session avant la fin des 60 secondes, l'écran est intégralement recouvert par un écran de verrouillage modal ettks_session_locked = "true"est écrit danslocalStorage(empêchant tout contournement par rafraîchissementF5). - Déverrouillage sécurisé BCrypt :
- L'opérateur doit saisir son mot de passe.
- Le composant interroge
POST /api/admin/v1/auth/unlock(avec repli surPOST /api/auth/login) qui vérifie en base de données que le compte est toujoursACTIVEet que le mot de passe correspond au hachage BCrypt (verifyPassword). - En cas de succès, le verrou est levé sans perte du contexte de travail en cours.
6. Middleware Next.js (src/middleware.ts)
Le fichier src/middleware.ts intercepte toutes les requêtes entrantes (hors fichiers statiques _next/static, _next/image, favicon.ico et images) :
- Routes publiques exemptées :
/login,/register,/forgot-password,/cli/login,/api/auth/*,/api/cli/token/*,/api/admin/*,/api/patches/*,/api/devices/*,/api/network-info/*. - Redirection si non connecté : Toute requête vers une page protégée sans cookie
tks_sessionest redirigée vers/login?redirect=<pathname>(ou reçoitHTTP 401s'il s'agit d'une route/api/*). - Redirection si déjà connecté : Si un opérateur authentifié accède à
/login,/registerou/forgot-password, il est automatiquement redirigé vers/. - Contrôle RBAC des pages : Si
!hasAccessToRoute(userRole, pathname), le middleware redirige vers/?forbidden=true&attempted=<pathname>&role=<userRole>. - En-têtes de sécurité injectés :
X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=(), payment=()Strict-Transport-Security: max-age=31536000; includeSubDomains