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 :
- Login + mot de passe corrects
- L'app génère un secret TOTP et affiche un QR code
- L'utilisateur scanne avec Google Authenticator / Authy / 1Password / etc.
- Il saisit le code à 6 chiffres pour confirmer
- Le secret est sauvé dans
auth.yaml
Aux connexions suivantes :
- Login + mot de passe → demande directe du code TOTP
- 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 :
nullou 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 :
- Playwright headless lance un profil temporaire isolé
- Login Keycloak avec les creds + TOTP fourni
- Scrape la liste des classes accessibles dans Escada
- Filtre MP / MI / classes « Formation »
- Sauvegarde dans
auth.yaml:allowed_classes,escada_username,escada_password - 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.