docs(architecture): update for v0.7.3 OAuth2 and single-instance wiring

- Bump header date/version to 2026-04-13 / v0.7.3
- Correct Tauri command count (25 → 34) and add the missing commands
- Add `auth_commands.rs` section (5 commands) and expand `license_commands.rs`
  with the 4 activation commands that already existed
- New "Plugins Tauri" section documenting init order constraints
  (single-instance must be first, deep-link before setup)
- New "OAuth2 et deep-link" section explaining the end-to-end flow,
  why single-instance is required, and why `on_open_url` is used
  instead of `app.listen()`
- Note the temporary auto-update gate opening in entitlements
- Update CI/CD: GitHub Actions → Forgejo Actions, add check.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-04-13 17:25:57 -04:00
parent f5d74b4664
commit 43c5be0c84

View file

@ -1,6 +1,6 @@
# Architecture technique — Simpl'Résultat
> Document mis à jour le 2026-04-11 — Version 0.7.0
> Document mis à jour le 2026-04-13 — Version 0.7.3
## Stack technique
@ -153,7 +153,7 @@ Chaque hook encapsule la logique d'état via `useReducer` :
| `useLicense` | État de la licence et entitlements |
| `useAuth` | Authentification Compte Maximus (OAuth2 PKCE, subscription status) |
## Commandes Tauri (25)
## Commandes Tauri (34)
### `fs_commands.rs` — Système de fichiers (6)
@ -182,7 +182,7 @@ Chaque hook encapsule la logique d'état via `useReducer` :
- `verify_pin` — Vérification du PIN
- `repair_migrations` — Réparation des checksums de migration (rusqlite)
### `license_commands.rs` — Licence (6)
### `license_commands.rs` — Licence et activation machine (10)
- `validate_license_key` — Validation offline d'une clé de licence (JWT Ed25519)
- `store_license` — Stockage de la clé dans le répertoire app data
@ -190,10 +190,60 @@ Chaque hook encapsule la logique d'état via `useReducer` :
- `read_license` — Lecture de la licence stockée
- `get_edition` — Détection de l'édition active (free/base/premium)
- `get_machine_id` — Génération d'un identifiant machine unique
- `activate_machine` — Activation en ligne (appel API serveur de licences, issue #49)
- `deactivate_machine` — Désactivation d'une machine enregistrée
- `list_activated_machines` — Liste des machines activées pour la licence
- `get_activation_status` — État d'activation de la machine courante
### `auth_commands.rs` — Compte Maximus / OAuth2 PKCE (5)
- `start_oauth` — Génère un code verifier PKCE et retourne l'URL d'authentification Logto
- `refresh_auth_token` — Rafraîchit l'access token via le refresh token
- `get_account_info` — Lecture du cache `account.json` (sans appel réseau)
- `check_subscription_status` — Vérifie l'abonnement (max 1×/jour, fallback cache gracieux)
- `logout` — Efface tokens.json + account.json
Note : `handle_auth_callback` n'est PAS exposée comme commande — elle est appelée depuis le handler deep-link `on_open_url` dans `lib.rs`. Voir section "OAuth2 et deep-link" plus bas.
### `entitlements.rs` — Entitlements (1)
- `check_entitlement` — Vérifie si une feature est autorisée selon l'édition
- Source de vérité : `FEATURE_TIERS` dans `entitlements.rs`. Modifier cette constante pour changer les gates, jamais ailleurs dans le code
- Temporaire : `auto-update` est ouvert à `free` en attendant le serveur de licences (issue #49). À re-gater à `[base, premium]` quand l'activation payante sera live
## Plugins Tauri
Ordre d'initialisation dans `lib.rs` (certains plugins ont des contraintes d'ordre) :
| Plugin | Rôle | Contrainte |
|--------|------|-----------|
| `tauri-plugin-single-instance` | Empêche les doubles lancements et forwarde les URLs deep-link au processus existant | **Doit être le premier plugin** ; feature `deep-link` requise pour le forwarding d'URL |
| `tauri-plugin-opener` | Ouverture d'URLs externes et de fichiers | — |
| `tauri-plugin-dialog` | Dialogues de sélection de fichier/dossier | — |
| `tauri-plugin-process` | Relaunch après mise à jour | — |
| `tauri-plugin-deep-link` | Gère le scheme custom `simpl-resultat://` | Doit être initialisé avant `setup()` pour que `on_open_url` soit disponible |
| `tauri-plugin-updater` | Mise à jour auto (gated par entitlement `auto-update`) | Initialisé dans `setup()` derrière `#[cfg(desktop)]` |
| `tauri-plugin-sql` | SQLite + migrations | Doit être initialisé avec les migrations pour que le schéma soit prêt |
## OAuth2 et deep-link (Compte Maximus)
Flow complet (v0.7.3+) :
1. Frontend appelle `start_oauth` → génère un code verifier PKCE (64 chars), le stocke dans `OAuthState` (Mutex en mémoire du processus), retourne l'URL Logto
2. Frontend ouvre l'URL via `tauri-plugin-opener` → le navigateur système affiche la page Logto
3. L'utilisateur s'authentifie (ou Logto auto-consent si session existante) → redirection 303 vers `simpl-resultat://auth/callback?code=...`
4. L'OS route le custom scheme vers une nouvelle instance de l'app → `tauri-plugin-single-instance` (feature `deep-link`) détecte l'instance existante, **ne démarre PAS un nouveau processus**, et forwarde l'URL à l'instance vivante
5. Le callback `app.deep_link().on_open_url(...)` enregistré via `DeepLinkExt` reçoit les URLs. Pour chaque URL :
- Si un param `code` est présent → appelle `handle_auth_callback` (token exchange vers `/oidc/token`, fetch `/oidc/me`, écriture `tokens.json` + `account.json` avec perms 0600, émission de l'event `auth-callback-success`)
- Si un param `error` est présent → émission de l'event `auth-callback-error` avec `error: error_description`
6. Le hook `useAuth` (frontend) écoute `auth-callback-success` / `auth-callback-error` et met à jour l'état
Pourquoi cet enchaînement est critique :
- **Sans `tauri-plugin-single-instance`** : une nouvelle instance démarre à chaque callback, le `OAuthState` est vide (pas de verifier), le token exchange échoue
- **Sans `on_open_url`** : l'ancien listener `app.listen("deep-link://new-url", ...)` ne recevait pas les URLs forwardées par single-instance. L'API canonique v2 via `DeepLinkExt` est nécessaire
- **Sans gestion des erreurs** : un callback `?error=...` laissait l'UI bloquée en état "loading" infini
Fichiers : `src-tauri/src/lib.rs` (wiring), `src-tauri/src/commands/auth_commands.rs` (PKCE + token exchange), `src/hooks/useAuth.ts` (frontend).
## Pages et routing
@ -233,12 +283,24 @@ Page spéciale : `ProfileSelectionPage` (affichée quand aucun profil n'est acti
## CI/CD
Workflow GitHub Actions (`release.yml`) déclenché par les tags `v*` :
Deux workflows Forgejo Actions (avec miroir GitHub) dans `.forgejo/workflows/` :
### `check.yml` — Vérifications sur branches et PR
Déclenché sur chaque push de branche (sauf `main`) et chaque PR vers `main`. Lance en parallèle :
- `cargo check` + `cargo test` (Rust)
- `npm run build` (tsc + vite)
- `npm test` (vitest)
Doit être vert avant tout merge. Évite de découvrir des régressions au moment du tag de release.
### `release.yml` — Build et publication
Déclenché par les tags `v*`. Deux jobs :
1. **build-windows** (windows-latest) → Installeur `.exe` (NSIS)
2. **build-linux** (ubuntu-22.04) → `.deb` + `.AppImage`
2. **build-linux** (ubuntu-22.04) → `.deb` + `.rpm`
Fonctionnalités :
- Signature des binaires (clés TAURI_SIGNING_PRIVATE_KEY)
- JSON d'updater pour mises à jour automatiques
- Release GitHub automatique avec notes d'installation
- JSON d'updater publié sur `https://git.lacompagniemaximus.com/api/packages/maximus/generic/simpl-resultat/latest/latest.json`
- Release Forgejo automatique avec assets et release notes extraites du CHANGELOG.md