Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/adr/0003-gestion-reactive-des-overlays-et-popovers.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ L'architecture initiale présentait plusieurs fragilités et dettes techniques :
- **Élimination complète des hacks DOM** : Plus aucun appel à `querySelector` pour manipuler ou supprimer des éléments de composants tiers.
- **Robustesse et extensibilité** : Tout nouvel overlay ou panneau ajouté dans l'application peut s'abonner à `OverlayManagerService` sans modifier `OverviewComponent`.
- **Fidélité visuelle et fin des sauts d'affichage** : Élimination définitive des scintillements et des sauts de dropdowns sous la barre de navigation.
- **Règle d'architecture formalisée** : Mise à jour de `GEMINI.md` imposant l'usage exclusif de `OverlayManagerService` pour coordonner la fermeture des overlays.
- **Règle d'architecture formalisée** : Règle imposant l'usage exclusif de `OverlayManagerService` pour coordonner la fermeture des overlays.
- **Périmètre et frontière claire** : `OverlayManagerService` est strictement restreint à la publication/souscription d'événements de fermeture ; les interactions de navigation clavier/souris au sein des fenêtres de suggestions sont découplées dans une directive dédiée (voir ADR-0005).
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 0006 - Authentification multi-forges découplée (Pattern Stratégie & Fournisseurs) et OAuth 2.0 PKCE

## Contexte
Historiquement, Git4School Visu ne prenait en charge que GitHub via un service monolithique `AuthService` s'appuyant directement sur Firebase Authentication (`firebase.auth().signInWithPopup(githubProvider)`).

L'introduction du support de GitLab (dans un premier temps la plateforme SaaS GitLab Cloud `gitlab.com`, puis ultérieurement des instances auto-hébergées GitLab Community) a nécessité la conception d'un mécanisme d'authentification robuste, sécurisé et entièrement exécutable côté client.

L'architecture initiale présentait plusieurs limites structurelles :
1. **Couplage fort au fournisseur GitHub** : `AuthService` était fortement couplé à GitHub et injecté dans plus de quinze composants et services (gardes de routage, barre de navigation, modales, assistants de devoirs).
2. **Absence de contrat d'abstraction** : Aucun contrat formel (interface ou classe abstraite) ne définissait les responsabilités d'un fournisseur d'authentification Git, contraignant les consommateurs à connaître les détails d'implémentation de la forge.
3. **Contraintes de sécurité d'une SPA statique** : Git4School étant une Single Page Application distribuée sans serveur applicatif dédié (hébergée statiquement sur Firebase Hosting), le flux OAuth standard avec secret client (*Authorization Code Grant*) était exclu sous peine de compromettre le secret d'application dans le bundle public. Le standard de l'industrie pour les clients publics sans secret est le protocole **OAuth 2.0 avec PKCE** (*Proof Key for Code Exchange*, RFC 7636).
4. **Duplication d'interface dans la modale d'ajout de compte** : Les panneaux de connexion de la modale (`AddAccountModalComponent`) dupliquaient l'arborescence HTML pour chaque onglet de forge.
5. **Conditions de course et latences d'API à l'authentification** : Les passerelles d'API distribuées (API Gateway de GitLab Cloud) peuvent présenter une brève latence de propagation du jeton nouvellement émis, renvoyant de façon transitoire une erreur 401 Unauthorized lors de l'appel immédiat à `/api/v4/user`. Sans reprise sur erreur et sans affectation atomique de l'état, cela pouvait conduire à des profils orphelins partiels.

## Options considérées
1. **Évolution du service monolithique `AuthService` avec branches conditionnelles** :
- *Rejeté* : Violait le principe de responsabilité unique (SRP) et le principe Ouvert/Fermé (OCP). Le service aurait combiné Firebase Auth, le protocole PKCE, la gestion multi-tokens et des requêtes HTTP hétérogènes. Tout ajout ultérieur de forge aurait requis la modification du code existant avec risques de régression.
2. **Fournisseurs indépendants injectés individuellement dans les composants** :
- *Rejeté* : Violait le principe d'inversion des dépendances (DIP). Les composants consommateurs auraient dû injecter concrètement `GithubAuthService`, `GitlabAuthService`, etc., multipliant le couplage et complexifiant la vérification globale de session.
3. **Conservation d'un alias de transition `AuthService` pointant vers `GithubAuthService`** :
- *Rejeté* : Introduisait une dette technique et une ambiguïté sémantique persistante. Un renommage intégral et sans compromis de toutes les occurrences dans la base de code a été préféré.
4. **Pattern Stratégie / Fournisseur avec contrat unifié `GitAuthProvider` et registre polymorphique `AccountsService` (Option retenue)**.

## Décision
1. **Contrat d'abstraction `GitAuthProvider` (`src/app/models/GitAuthProvider.model.ts`)** :
- Définition d'une interface formelle définissant le contrat de tout fournisseur d'authentification :
- Propriétés : `providerType: GitProviderType`.
- Méthodes du cycle de vie : `signIn(): Promise<void>`, `signOut(): Promise<void>`, `isSignedIn(): boolean`.
- Accesseurs de données : `getToken(): string | null`, `getUserProfile(): any | null`.
- Disponibilité fonctionnelle : `isAvailable(): boolean` (permettant la désactivation contextuelle via les feature flags).
2. **Spécialisation de la stratégie GitHub (`GithubAuthService`)** :
- Renommage exhaustif de `AuthService` en `GithubAuthService` à travers toute l'application (zéro dette technique, zéro shim de compatibilité).
- Implémentation du contrat `GitAuthProvider` au-dessus de Firebase Auth.
3. **Implémentation de la stratégie GitLab Cloud via OAuth 2.0 PKCE (`GitlabAuthService`)** :
- Implémentation complète de la RFC 7636 côté client sans dépendance tierce lourde :
- Génération d'un `code_verifier` aléatoire sécurisé via `window.crypto.getRandomValues`.
- Dérivation du `code_challenge` par hachage cryptographique `SHA-256` encodé en Base64-URL (`code_challenge_method=S256`).
- Validation d'état CSRF (`state`) pour prévenir les attaques par falsification de requête inter-sites.
- Fenêtre popup d'authentification avec composant callback dédié (`GitlabCallbackComponent`, route `/auth/callback`) communiquant le code d'autorisation via `window.postMessage` avec fallback synchronisé sur l'événement `storage` de `localStorage`.
- **Atomicité de l'état et résilience aux latences de passerelle** :
- L'affectation de `token` et de `currentUser` est strictement atomique : l'état n'est mis à jour qu'une fois le profil complet validé par l'API.
- Mécanisme de nouvelle tentative automatique (délai de 500 ms) en cas de code 401 passager lors de la requête `/api/v4/user` afin d'absorber la propagation de réplication du token sur les passerelles d'API GitLab.
4. **Registre polymorphique centralisé (`AccountsService`)** :
- Enregistrement des stratégies d'authentification dans un registre typé `Map<GitProviderType, GitAuthProvider>`.
- API de haut niveau pour les composants : `getProvider(type)`, `signIn(type)`, `signOut(type)`, `hasAccount(type)`, `getAccounts()`, `isEmpty()`.
- Source de vérité unique pour les comptes actifs dans l'application.
5. **Découplage de la garde d'authentification (`AuthGuard`)** :
- `AuthGuard` ne dépend plus directement de Firebase ni de `GithubAuthService`.
- La protection des routes s'appuie désormais sur la méthode universelle `!accountsService.isEmpty()`.
6. **Factorisation de l'interface utilisateur (`AddAccountModalComponent`)** :
- Remplacement de la duplication de code par des blocs réutilisables `ng-template` (`#connectedPanel`, `#disconnectedPanel`) utilisant `ngTemplateOutlet` avec passage de contexte polymorphique.
- Découplage de la vue vis-à-vis des services concrets (seul `AccountsService` est manipulé).
- Affichage dynamique du badge de sécurité indiquant le protocole utilisé (`ACCOUNTS.AUTH_TYPE_OAUTH_PKCE`).

## Conséquences
- **Conformité aux principes SOLID** :
- *SRP* : Chaque fournisseur gère sa propre logique d'authentification et ses protocoles spécifiques.
- *OCP* : L'ajout de nouveaux fournisseurs (instances GitLab Community auto-hébergées, forge académique, Bitbucket) se fait par simple extension de `GitAuthProvider` et ajout dans le registre de `AccountsService`, sans modifier les composants et gardes existants.
- *LSP* : Tout consommateur manipulant `GitAuthProvider` peut utiliser indifféremment l'une ou l'autre des implémentations.
- *DIP* : Les composants de l'application dépendent d'abstractions contractuelles et non d'implémentations concrètes de SDK tiers.
- **Sécurité et conformité client-side** : Intégration conforme aux recommandations de l'IETF pour les clients publics sans exposition de secret d'application.
- **Résilience opérationnelle** : Neutralisation des conditions de course et tolérance aux latences de réplication des tokens OAuth.
- **Base de code assainie** : Élimination définitive des dettes techniques de renommage et factorisation des templates d'interface.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 0007 - Services de données Git multi-fournisseurs (Pattern Stratégie, Façade & Registre)

## Contexte
Historiquement, la récupération des données de dépôts et de commits dans Git4School était concentrée dans un service monolithique `CommitsService`. Ce dernier combinait plus de 700 lignes de requêtes GraphQL spécifiques à l'API GitHub (`api.github.com`) avec les calculs statistiques et dictionnaires nécessaires aux visualisations D3.js.

L'intégration de GitLab (GitLab Cloud `gitlab.com` et futures instances académiques auto-hébergées) imposait de :
1. Découpler la logique de récupération de données distantes des algorithmes d'analyse et de visualisation.
2. Définir un contrat d'abstraction formel pour les fournisseurs de données Git.
3. Permettre la résolution dynamique de l'hôte distant (*instance host*) afin de supporter nativement les instances GitLab auto-hébergées sans modifier le code applicatif.
4. Conserver une façade unifiée et rétrocompatible pour les composants graphiques existants.

## Options considérées
1. **Ajout de conditions `if (provider === 'gitlab')` au sein de `CommitsService`** :
- *Rejeté* : Violation flagrante du principe Ouvert/Fermé (OCP) et du principe de responsabilité unique (SRP). Le service aurait accumulé les spécificités de multiples API (GraphQL GitHub vs REST v4 GitLab) au sein du même fichier.
2. **Injection directe des services de données concrets dans chaque composant** :
- *Rejeté* : Couplage fort des composants d'interface aux détails de chaque forge et duplication de la logique de sélection de stratégie.
3. **Architecture découplée en trois volets : Contrat `GitDataService`, Stratégies concrètes (`GithubDataService`, `GitlabDataService`), Registre polymorphique (`AccountsService`) et Façade (`CommitsService`) (Option retenue)**.

## Décision
1. **Contrat d'abstraction `GitDataService` (`src/app/models/GitDataService.model.ts`)** :
- Définit le contrat unifié de données Git :
- `readonly provider: GitProviderType`
- `getRepositories(repoTab: Repository[], startDate?: string, endDate?: string): Observable<Repository[]>`
- `getRepositoriesByAuthenticatedUser(cursor?: string, pageLimit?: number): Observable<GitDataSearchResult>`
- `getRepositoriesBySearch(searchFilter: string, cursor?: string, pageLimit?: number): Observable<GitDataSearchResult>`
- `verifyUserAccess(repoUrl: string): Observable<any>`
2. **Stratégie GitHub (`GithubDataService`)** :
- Encapsule les requêtes GraphQL optimisées par lots (*batch queries* de 4 dépôts), la pagination des commits via curseur et la recherche ciblée (dépôt, organisation, recherche globale).
3. **Stratégie GitLab (`GitlabDataService`)** :
- Implémente l'API REST v4 de GitLab (`/api/v4/projects`).
- **Support natif des instances auto-hébergées** : Extraction dynamique de l'origine de l'hôte (`new URL(repo.url).origin`) et du chemin de projet encodé (`encodeURIComponent(pathWithNamespace)`).
- Récupération unifiée des fichiers `IDENTITY.json` et `README.md` via l'API raw files et pagination récursive des commits (`x-next-page`).
4. **Registre centralisé (`AccountsService`)** :
- Maintient la table de correspondance `dataServices: Map<GitProviderType, GitDataService>`.
- Expose `getDataService(provider: GitProviderType): GitDataService` et `hasAccount(provider: GitProviderType): boolean`.
5. **Façade unifiée (`CommitsService`)** :
- Délègue les opérations de chargement et de recherche au `GitDataService` correspondant au type de devoir (`assignment.provider`).
- Conserve les fonctions de calcul de graphes (dictionnaires de questions, métadonnées, étudiants) en tant que logique métier pure indépendante du fournisseur.
6. **Mise à jour des formulaires et de la modale d'ajout de dépôts** :
- `ConfigurationComponent`, `EditRepositoriesComponent` et `ModalAddRepositoriesComponent` propagent le type de fournisseur et conditionnent les actions à l'authentification effective sur le fournisseur ciblé (`isConnectedToProvider`).
7. **Rétrocompatibilité et migration Dexie v4** :
- Migration automatique fixant `provider = "github"` sur tous les devoirs et dépôts préexistants.

## Conséquences
- **Extensibilité** : L'ajout d'une nouvelle forge (ex: forge d'enseignement hébergée, Bitbucket) ne nécessite que la création d'un service implémentant `GitDataService` et son enregistrement dans le registre.
- **Rétrocompatibilité totale** : Les devoirs créés avant la mise à jour continuent de fonctionner à l'identique avec GitHub.
- **Homogénéité mono-fournisseur garantie** : Un devoir est rattaché à une forge unique, évitant les mélanges complexes d'identifiants et de droits d'accès.
- **Expérience utilisateur fluide** : Le bouton de création d'un devoir s'adapte dynamiquement au filtre sélectionné par l'enseignant, tout en offrant 3 déclinaisons d'interaction ergonomiques évaluables via le prototype.
Loading
Loading