eptm_dashboard/data/docs/07-auth.md
2026-05-12 15:30:28 +02:00

6.4 KiB

Authentification, droits & profil

Login

Page : /login

L'identifiant + mot de passe sont vérifiés contre data/auth.yaml (mot de passe haché avec bcrypt).

Format auth.yaml :

credentials:
  usernames:
    prof.demo:
      password: "$2b$12$..."         # bcrypt
      name: "Prof Demo"
      role: "admin"                  # ou "user"
      email: "prof.demo@eptm.ch"     # destinataire pour reset mdp / enrôlement
      avatar_url: "/avatars/prof_demo.png"
      totp_secret: "ABCD1234..."     # rempli automatiquement à la 1ère 2FA
      allowed_classes: ["AUTOMAT 1", "EM-AU 2"]  # restriction d'accès
      escada_username: "prenom.nom@eptm.ch"      # email Escada (clé login)
      escada_password: "..."         # mot de passe Escada (stocké clair)

2FA TOTP (obligatoire)

À la première connexion d'un nouvel utilisateur :

  1. Login + mot de passe corrects
  2. L'app génère un secret TOTP et affiche un QR code
  3. L'utilisateur scanne avec Google Authenticator / Authy / 1Password / etc.
  4. Il saisit le code à 6 chiffres pour confirmer
  5. Le secret est sauvé dans auth.yaml

Aux connexions suivantes :

  1. Login + mot de passe → demande directe du code TOTP
  2. Code à 6 chiffres → connexion finalisée

Le code est valide ±30 s (paramètre valid_window=1 de pyotp) pour tolérer la dérive d'horloge.

Email de bienvenue / réinitialisation de mot de passe

Sur création d'un user depuis /users ou sur reset, un email est envoyé contenant un lien <APP_URL>/password_set?token=... qui expire après 24 h.

  • L'URL de base est lue depuis settings.app_base_url (configurée en /params → Application).
  • L'expéditeur et le SMTP sont configurés en /params → Configuration email.

Session persistante

L'authentification est stockée dans le localStorage du navigateur (champs username, name, role, photo_url, theme). La session survit aux rechargements de page et aux redémarrages du conteneur.

À chaque page protégée, AuthState.check_auth re-vérifie que l'utilisateur existe toujours dans auth.yaml et rafraîchit allowed_classes, escada_username, escada_has_password, etc. ; sinon, redirection forcée vers /login.

Droits d'accès par classe

Chaque user a une clé allowed_classes: list[str] | null :

  • null ou absente → restriction non encore appliquée (user nouveau)
  • [] vide → aucun accès → popup d'enrôlement obligatoire (cf. ci-dessous)
  • liste non vide → l'user ne voit que ces classes dans /classe, /fiche, et les filtres de stats sur /accueil
  • role=admin → voit tout (bypass de la restriction)

src/user_access.py:get_allowed_classes(username) est l'API canonique. Toutes les pages user-facing l'appellent pour filtrer.

Self-service enrôlement (popup obligatoire)

Quand un user se connecte et que allowed_classes est [] (ou non défini), un dialog forcé s'ouvre sur toutes les pages :

Configurez votre accès

Pour récupérer la liste des classes auxquelles vous avez accès dans Escadaweb, saisissez vos identifiants Escada + un code TOTP courant.

Champs :

  • Email Escada (= escada_username)
  • Mot de passe Escada (stocké clair dans auth.yaml)
  • Code 2FA courant (utilisé une fois — non stocké)

À la soumission, le script scripts/fetch_user_classes.py est lancé en arrière-plan (@rx.event(background=True) + asyncio.create_subprocess_exec) pour ne pas bloquer l'app pour les autres users :

  1. Playwright headless lance un profil temporaire isolé
  2. Login Keycloak avec les creds + TOTP fourni
  3. Scrape la liste des classes accessibles dans Escada
  4. Filtre MP / MI / classes « Formation »
  5. Sauvegarde dans auth.yaml : allowed_classes, escada_username, escada_password
  6. Log live dans operations.log (préfixe [fetch_classes:<username>]) → visible dans /logs

Le popup peut être fermé avec « Plus tard » (enroll_dismissed=True), il réapparaîtra au prochain login.

Page « Mon profil » (/profile)

Accessible via le popover sidebar :

  • Avatar
  • Liste actuelle des allowed_classes
  • Bouton « Relancer la synchronisation » pour rafraîchir la liste (même script que le popup)
  • Modifier les identifiants Escada (sans relancer la sync immédiatement)

Réinitialisation des droits (admin)

Sur la page /users, un bouton « Réinitialiser les droits » (par user) :

  • Efface allowed_classes
  • Efface escada_username + escada_password

→ Au prochain login de cet user, le popup d'enrôlement réapparaît.

Rôles

Page user (sans allowed_classes) user (avec allowed_classes) admin
/accueil (filtré) (filtré)
/classe (filtré) (filtré)
/fiche (filtré) (filtré)
/doc
/profile
/escada
/cron
/logs
/users
/params
/feedback

Gestion des utilisateurs (admin)

Page /users :

  • Créer / supprimer des utilisateurs
  • Changer le rôle
  • Réinitialiser le 2FA (efface totp_secret → forcera une nouvelle config au prochain login)
  • Réinitialiser les droits (cf. ci-dessus)
  • Définir / changer un avatar
  • Clic sur une ligne ouvre directement le panneau d'édition

Logout

Bouton « Déconnexion » dans le popover profil de la sidebar. Vide le localStorage (y compris enroll_dismissed) et redirige vers /login.

Stockage des avatars

Les fichiers sont sous assets/avatars/. Le chemin est référencé dans auth.yaml via avatar_url. Si avatar_url est vide, l'app affiche les initiales de name.