Aller au contenu principal

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​

Flux de connexion à la Console Web
  1. Saisie des identifiants : L'opérateur saisit son adresse email et son mot de passe sur la page de connexion.
  2. Validation 2FA (si activée) : Saisie du code à usage unique de validation.
  3. Émission de la session : Création d'un cookie de session sécurisé (HttpOnly, SameSite=Strict).
  4. 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 :

  1. 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.
  2. Validation 2FA par Email OTP :
    • Si la politique MFA de l'organisation n'est pas DISABLED, le backend retourne requireOtp: true ainsi qu'un preAuthTicket (Base64 contenant email, 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-otp dès que le compte à rebours cooldownSec atteint 0.
    • En environnement de développement uniquement (mode développement et ota.security.otp.dev-bypass=true), un encart d'aide affiche le code de test devMockCode (123456). Ce mécanisme est formellement bloqué en production par OtpHardProductionGuard.

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 (sinon HTTP 409 Conflict), génère un OTP de type REGISTER_VERIFY et 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 = false par défaut en mode Solo) et crée l'utilisateur avec le rôle ROLE_ADMIN et le statut ACTIVE.

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ématiquement HTTP 200 OK avec 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 flux PASSWORD_RESET est 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'OTP PASSWORD_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 contenant username, email, fullName, role, tenantId, organizationName, token et loggedInAt.
  • 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 cookieContenuUsage dans middleware.ts
tks_sessionJeton de session (ou "authenticated")Prouve que l'opérateur possède une session active
tks_tenantIdentifiant du tenant (ex. sara-money)Injecté dans l'en-tête HTTP x-tenant-id
tks_roleRô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 :

  1. Surveillance d'activité : Écoute les événements DOM mousemove, mousedown, keydown, scroll et touchstart.
  2. 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).
  3. 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 et tks_session_locked = "true" est écrit dans localStorage (empêchant tout contournement par rafraîchissement F5).
  4. 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 sur POST /api/auth/login) qui vérifie en base de données que le compte est toujours ACTIVE et 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_session est redirigée vers /login?redirect=<pathname> (ou reçoit HTTP 401 s'il s'agit d'une route /api/*).
  • Redirection si déjà connecté : Si un opérateur authentifié accède à /login, /register ou /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: DENY
    • X-Content-Type-Options: nosniff
    • Referrer-Policy: strict-origin-when-cross-origin
    • Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
    • Strict-Transport-Security: max-age=31536000; includeSubDomains