feat: authentification QR code / token pour Pronote

Ajoute le mode d'authentification PRONOTE_AUTH_MODE=qr_token comme alternative
au mode password pour les instances Pronote utilisant HubEduConnect/EduConnect
où l'authentification par mot de passe échoue (CAPTCHA, MFA, flux SAML).

Nouveaux éléments :
- PronoteSettings : auth_mode, qr_code_file, qr_pin (SecretStr)
- PronoteAuthState : persistance du token rotatif dans .pronote_auth_state.json
  (écriture atomique, permissions 0600, symlink-safe via O_EXCL|O_NOFOLLOW)
- PronoteClient._connect_qr_token() : token_login avec creds persistés,
  qrcode_login pour l'enrôlement initial, export_credentials persisté après
  chaque login réussi
- PronoteAuthRotationError : levée en cas d'échec de rotation du token,
  propagée sans wrapping à travers PronoteFetcher et fetch_step jusqu'à
  PipelineRunner.run() qui notifie via XMPP (si canal disponible et dry_run inactif)
- _is_pronotepy_configured() mode-aware : qr_token ne requiert que PRONOTE_URL
- _collect_auth_secrets() : redaction des secrets explicites (token, PIN, jeton QR)
  dans tous les logs du chemin d'authentification

Documentation :
- .env.example : PRONOTE_AUTH_MODE, PRONOTE_QR_CODE_FILE, PRONOTE_QR_PIN
- AGENTS.md : contrat d'authentification QR code / token
- Wiki GuidePronote : section enrôlement, exécutions suivantes, ré-enrôlement

Tests (686 passés, couverture 94.87%) :
- 5 tests config QR, 9 tests auth_state, 10 tests client QR, 3 tests propagation,
  4 tests intégration rotation end-to-end, 4 tests fallback mode-aware
- Tests de non-fuite : sentinelles distinctes pour token, PIN, jeton QR

Co-authored-by: coder/litellm/coder <coder@agents.invalid>
This commit is contained in:
2026-09-08 23:15:06 +02:00
parent 4a6207f716
commit 0363898669
16 changed files with 2128 additions and 39 deletions

View File

@@ -146,6 +146,31 @@ pronote-sync --dry-run
- Réutiliser un téléchargement/parsing iCal pour l'agenda et les devoirs pendant un même run, sans
cache global ni persistant.
### Contrat d'authentification QR code / token
- Le mode d'authentification est sélectionné par `PRONOTE_AUTH_MODE` :
- `password` (défaut) : authentification classique via URL, identifiant, mot de passe et ENT.
- `qr_token` : authentification par QR code puis token persistant (pour les instances Pronote
utilisant HubEduConnect/EduConnect où l'authentification par mot de passe échoue).
- En mode `qr_token`, le premier login utilise `pronotepy.qrcode_login(qr_code, pin, uuid)` avec
les paramètres `PRONOTE_QR_CODE_FILE` (chemin du JSON QR) et `PRONOTE_QR_PIN` (PIN SecretStr).
- Après chaque login réussi, les credentials exportées par `pronotepy.export_credentials()` sont
persistées dans `.pronote_auth_state.json` (permissions `0600`, format JSON versionné, écriture
atomique). Le token rotate à chaque session — le fichier doit être mis à jour après chaque run.
- Les logins suivants utilisent `pronotepy.token_login(**credentials)` avec le token persisté.
- En cas d'échec de `token_login` (token expiré/invalide), une `PronoteAuthRotationError` est levée.
Cette erreur se propage sans wrapping à travers `PronoteFetcher` et `fetch_step` jusqu'à
`PipelineRunner.run()`, qui :
- journalise l'erreur (expurgée) ;
- envoie une notification XMPP actionnable si le canal est disponible et `dry_run` est inactif ;
- retourne un résultat dégradé `(None, errors)`.
- `PronoteAuthRotationError` est re-levée telle quelle (`except PronoteAuthRotationError: raise`)
dans toutes les couches d'enveloppement du chemin critique (fetch_agenda, fetch_homework,
fetch_step). Ne pas l'attraper avec `except Exception` sans la re-léver d'abord.
- Le fichier `.pronote_auth_state.json` ne doit jamais être committé (couvert par `.gitignore`).
Son contenu (token vivant) ne doit jamais apparaître dans les logs, les messages d'erreur ou
les notifications XMPP.
### Contrat du provider `openai-compatible`
- Le provider `openai-compatible` réutilise `OpenAISynthesisProvider` avec un `base_url` personnalisé ; aucun nouveau provider n'est créé.
- `AI_BASE_URL` et `AI_MODEL` sont requis ; `AI_API_KEY` est requis (MVP).