Note technique : modèle de données révisé, RLS complets, décisions en suspens
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a549c08240
commit
ac60ea63ad
2 changed files with 181 additions and 155 deletions
39
css/gms.css
39
css/gms.css
|
|
@ -283,12 +283,30 @@
|
|||
}
|
||||
|
||||
/* Legacy button classes — redéclarées après .btn pour corriger la cascade */
|
||||
.btn-p { background: var(--gms-primary); color: #fff; box-shadow: 0 1px 4px rgba(232,146,42,.3); }
|
||||
.btn-p:hover:not(:disabled) { background: var(--gms-primary-dark, #d07820); }
|
||||
.btn-s { background: #fff; color: var(--color-black); border: 1px solid var(--gms-grey-light); }
|
||||
.btn-s:hover:not(:disabled) { background: var(--gms-grey-xlight, #f5f5f5); }
|
||||
.btn-d { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
|
||||
.btn-d:hover:not(:disabled) { background: #fecaca; }
|
||||
.btn-p {
|
||||
background: var(--gms-primary);
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 4px rgba(232, 146, 42, 0.3);
|
||||
}
|
||||
.btn-p:hover:not(:disabled) {
|
||||
background: var(--gms-primary-dark, #d07820);
|
||||
}
|
||||
.btn-s {
|
||||
background: #fff;
|
||||
color: var(--color-black);
|
||||
border: 1px solid var(--gms-grey-light);
|
||||
}
|
||||
.btn-s:hover:not(:disabled) {
|
||||
background: var(--gms-grey-xlight, #f5f5f5);
|
||||
}
|
||||
.btn-d {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
.btn-d:hover:not(:disabled) {
|
||||
background: #fecaca;
|
||||
}
|
||||
|
||||
/* ===== DATA TABLE (portée du GMS) ===== */
|
||||
.data-table {
|
||||
|
|
@ -859,8 +877,11 @@ body.route-suivi-eleves #pageContent {
|
|||
border: var(--gms-border);
|
||||
border-radius: var(--gms-radius-l);
|
||||
padding: var(--gms-pad-xl);
|
||||
overflow-y: auto;
|
||||
max-height: 75vh;
|
||||
}
|
||||
/* Dans une vue liste, le scroll est celui de la page — pas du flip-card (transform-style:preserve-3d bloque overflow) */
|
||||
.gms-page-content .data-table {
|
||||
max-height: none;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.gms-une-classe-wrap {
|
||||
|
|
@ -909,7 +930,7 @@ body.route-suivi-eleves #pageContent {
|
|||
.gms-class-tabs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 9.6vw;
|
||||
left: 11.6rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,28 @@
|
|||
# Note technique — Conquiers Ta Vie
|
||||
# Note technique : Conquiers Ta Vie
|
||||
|
||||
## Interface enseignant & API · Architecture, sécurité & conformité RGPD
|
||||
|
||||
**Version** : 1.0 — Mai 2026
|
||||
**Version** : 1.0, mai 2026
|
||||
|
||||
---
|
||||
|
||||
## Décisions en suspens
|
||||
|
||||
Les points suivants sont identifiés dans ce document comme nécessitant un arbitrage avant finalisation ou mise en production.
|
||||
|
||||
| # | Sujet | Section | Statut |
|
||||
|---|-------|---------|--------|
|
||||
| 1 | Durée de conservation des données de progression élève | 5.3 | À définir |
|
||||
| 2 | Structure de la table de progression (chapitres, étapes, conditions) | 3.7 | À préciser avec l'équipe World Game |
|
||||
| 3 | Désactivation sans suppression d'un code élève (`is_active`) | 3.4 | À décider |
|
||||
| 4 | Tentatives multiples sur une activité (contrainte UNIQUE sur `activity_results`) | 3.8 | À décider |
|
||||
| 5 | Rédaction et signature du DPA | 5.1 | À produire avant mise en production |
|
||||
| 6 | Séparation `free_access` (aventure principale) et accès activités vie réelle (actuellement mélangés dans le proto) | 3.9 | À modéliser |
|
||||
| 7 | Spécifications API à transmettre à l'équipe app tierce | 6.2 | À rédiger (Arthur Deleye) |
|
||||
| 8 | Choix du domaine : `conquiers-ta-vie.com` (Infomaniak) ou sous-domaine `world-game` | 10.4 | À confirmer |
|
||||
| 9 | Signature DPA World Game / Département | 5.1 | À planifier (Jules Zimmermann / DSI) |
|
||||
| 10 | Périmètre et calendrier audit RGPD DRAM | 5.5 | Prévu septembre (DRAM) |
|
||||
| 11 | Stratégie de monitoring applicatif (seuils, astreinte, escalade) | 11.2 | À définir avant mise en production |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -9,12 +30,12 @@
|
|||
|
||||
World Game développe deux composants : une **interface web enseignant** (SvelteKit) permettant de gérer les classes, créer des modules pédagogiques et suivre la progression des élèves, ainsi qu'une **API REST** que les applications élèves tierces (développées indépendamment, déployées sur les tablettes JAMF) consomment pour authentifier les élèves, récupérer les contenus assignés et soumettre la progression.
|
||||
|
||||
L'architecture retenue repose sur des services managés éprouvés — **Supabase** pour la base de données et l'authentification, **Infomaniak** pour l'hébergement — conformément aux échanges menés lors de la réunion avec la DSI et la DTNI.
|
||||
L'architecture retenue repose sur des services managés éprouvés : **Supabase** pour la base de données et l'authentification, **Infomaniak** pour l'hébergement, conformément aux échanges menés lors de la réunion avec la DSI et la DTNI.
|
||||
|
||||
**Points clés :**
|
||||
|
||||
- **Anonymat total côté élève** : aucun nom, email ou identifiant nominatif n'est stocké dans le système. Les élèves sont identifiés uniquement par un code opaque (ex. `BR37-14763`) généré aléatoirement par l'enseignant. La correspondance code ↔ élève reste dans la classe, hors de tout système numérique.
|
||||
- **Aucun formulaire d'inscription public** : l'accès à l'interface enseignant nécessite un code d'inscription fourni par World Game à l'établissement, éliminant tout risque de spam ou de création de compte non autorisée.
|
||||
- **Inscription verrouillée par code établissement** : la page d'inscription est accessible publiquement, mais la création de compte est impossible sans un code fourni par World Game à l'établissement. Un formulaire sans ce code est rejeté côté serveur : aucun compte non autorisé ne peut être créé.
|
||||
- **Hébergement souverain** : Infomaniak (Suisse, certifié ISO 27001), données non soumises au Cloud Act américain.
|
||||
- **Conformité RGPD** : la seule donnée personnelle traitée côté serveur est l'email de l'enseignant. Les données élèves relèvent de la pseudonymisation et ne permettent pas de réidentifier un individu, même en cas d'accès complet à la base de données.
|
||||
- **Intégration native** : whitelist Netskope confirmée, déploiement via JAMF School, auto-sauvegarde avant la coupure automatique de 21h.
|
||||
|
|
@ -38,9 +59,9 @@ L'architecture retenue repose sur des services managés éprouvés — **Supabas
|
|||
|
||||
```
|
||||
┌──────────────────────┐ ┌─────────────────────────────┐
|
||||
│ Apps élèves │ API REST · HTTPS/WSS │ Supabase Edge Functions │
|
||||
│ Apps élèves │ API REST · HTTPS │ Supabase Edge Functions │
|
||||
│ (tierces, tablettes │ ───────────────────────▶ │ API publique documentée │
|
||||
│ JAMF — hors scope) │ └──────────────┬──────────────┘
|
||||
│ JAMF, hors scope) │ └──────────────┬──────────────┘
|
||||
└──────────────────────┘ │
|
||||
│
|
||||
┌──────────────────────┐ Supabase JS · HTTPS ┌──────────────▼──────────────┐
|
||||
|
|
@ -51,32 +72,33 @@ L'architecture retenue repose sur des services managés éprouvés — **Supabas
|
|||
|
||||
**Points d'attention infrastructure :**
|
||||
|
||||
- **Domaine API** : `api.conquiers-ta-vie.world-game` — à whitelister dans Netskope (interlocuteur : Fabien Benard).
|
||||
- **WebSockets** : Supabase Realtime utilise des WebSockets (`wss://`). En cas de déchiffrement SSL actif dans Netskope, une configuration spécifique est nécessaire pour ce domaine. World Game informera l'équipe réseau en amont.
|
||||
- **Domaine API** : `api.conquiers-ta-vie.com` (sous-réserve de confirmation, voir point 8 des décisions en suspens), à whitelister dans Netskope (interlocuteur : Fabien Benard).
|
||||
- **Trois environnements** :
|
||||
- **Développement** : faux collège virtuel existant (JAMF + Netskope), utilisable pour les premiers testeurs.
|
||||
- **Recettage** : projet Supabase dédié, données fictives, accessible pour les tests d'intégration avant déploiement.
|
||||
- **Développement** : environnement local des développeurs World Game.
|
||||
- **Pré-production** : projet Supabase dédié, données fictives. Accès partagé avec le client à des moments clés (démonstrations, tests d'intégration) selon les besoins.
|
||||
- **Production** : projet Supabase production + hébergement Infomaniak, accès restreint à l'équipe World Game.
|
||||
|
||||
La DSI/DTNI dispose d'un environnement de test existant (JAMF + Netskope, faux collège virtuel) qui pourrait en théorie servir aux tests d'intégration. World Game choisit néanmoins de maintenir son propre environnement de pré-production, configuré à l'identique de la production : cela garantit que les tests se déroulent dans des conditions maîtrisées et reproductibles, indépendamment des évolutions de l'infrastructure cliente.
|
||||
|
||||
---
|
||||
|
||||
## 3. Modèle de données
|
||||
|
||||
### 3.1 Codes d'inscription établissement
|
||||
|
||||
Chaque établissement reçoit de World Game un code d'inscription unique. Ce code est la seule "clé d'entrée" permettant à un enseignant de créer son compte. Il n'existe pas de formulaire d'inscription public.
|
||||
Chaque établissement reçoit de World Game un code d'inscription unique. La page d'inscription est accessible publiquement, mais ce code est obligatoire pour créer un compte (sans lui, la création est rejetée côté serveur).
|
||||
|
||||
```sql
|
||||
school_registration_codes (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code text UNIQUE NOT NULL, -- ex: "DEPAR-2026-CLGVAUBAN"
|
||||
label text, -- usage interne WG (nom du collège)
|
||||
used_by_teacher uuid REFERENCES teachers(id),
|
||||
used_at timestamptz,
|
||||
expires_at timestamptz -- expiration optionnelle
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code text UNIQUE NOT NULL, -- ex: "DEPAR-2026-CLGVAUBAN"
|
||||
label text, -- nom du collège, usage interne WG
|
||||
expires_at timestamptz
|
||||
)
|
||||
```
|
||||
|
||||
Le nombre d'enseignants inscrits avec un code donné est retrouvable via `teachers.registration_code_id` : pas besoin de le dénormaliser ici.
|
||||
|
||||
### 3.2 Enseignants
|
||||
|
||||
L'email et le mot de passe sont gérés par Supabase Auth (stockés chiffrés, jamais en clair). La table `teachers` ne contient que les métadonnées de profil.
|
||||
|
|
@ -93,30 +115,41 @@ teachers (
|
|||
|
||||
### 3.3 Classes
|
||||
|
||||
Une classe peut être partagée entre plusieurs enseignants. Un enseignant rejoint une classe existante en saisissant son `class_code` dans l'interface : une ligne est alors insérée dans `teacher_classes`.
|
||||
|
||||
```sql
|
||||
classes (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
teacher_id uuid NOT NULL REFERENCES teachers(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
class_code text UNIQUE NOT NULL, -- ex: "BR37" — code de la classe, visible par l'enseignant
|
||||
class_code text UNIQUE NOT NULL, -- ex: "BR37", partageable entre enseignants
|
||||
school_year text NOT NULL, -- ex: "2025-2026"
|
||||
created_by uuid REFERENCES teachers(id), -- créateur (information, pas contrainte d'accès)
|
||||
created_at timestamptz DEFAULT now()
|
||||
)
|
||||
|
||||
-- Table de jonction : relation many-to-many entre enseignants et classes
|
||||
teacher_classes (
|
||||
teacher_id uuid NOT NULL REFERENCES teachers(id) ON DELETE CASCADE,
|
||||
class_id uuid NOT NULL REFERENCES classes(id) ON DELETE CASCADE,
|
||||
joined_at timestamptz DEFAULT now(),
|
||||
PRIMARY KEY (teacher_id, class_id)
|
||||
)
|
||||
```
|
||||
|
||||
Supprimer un enseignant retire ses lignes dans `teacher_classes` (cascade) mais laisse la classe intacte si d'autres enseignants y sont rattachés.
|
||||
|
||||
### 3.4 Codes élèves — aucune donnée nominative
|
||||
|
||||
C'est le cœur de la stratégie d'anonymat. Un code élève est une chaîne opaque générée aléatoirement. Il n'est lié à aucun nom, email, date de naissance ou tout autre identifiant. La correspondance entre ce code et l'identité de l'élève n'existe que chez l'enseignant, sur papier ou tableur local, et n'est jamais transmise au système.
|
||||
|
||||
```sql
|
||||
student_codes (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
class_id uuid NOT NULL REFERENCES classes(id) ON DELETE CASCADE,
|
||||
code text UNIQUE NOT NULL, -- ex: "BR37-14763"
|
||||
device_fingerprint text, -- hash de l'appareil, 1ère connexion
|
||||
first_used_at timestamptz, -- null = code jamais utilisé
|
||||
is_active boolean DEFAULT true,
|
||||
created_at timestamptz DEFAULT now()
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
class_id uuid NOT NULL REFERENCES classes(id) ON DELETE CASCADE,
|
||||
code text UNIQUE NOT NULL, -- ex: "BR37-14763"
|
||||
first_used_at timestamptz, -- null = code jamais utilisé
|
||||
is_active boolean DEFAULT true, -- À décider : désactivation sans suppression
|
||||
created_at timestamptz DEFAULT now()
|
||||
|
||||
-- Aucun champ : name, email, birth_date, student_id, national_id.
|
||||
-- La liste papier (ou tableur) chez l'enseignant est le seul endroit
|
||||
|
|
@ -124,8 +157,6 @@ student_codes (
|
|||
)
|
||||
```
|
||||
|
||||
**Fonctionnement du device\_fingerprint :** lors du premier appel API avec un code élève, l'app tierce fournit un identifiant d'appareil opaque (UUID généré côté client, sans PII). L'API l'enregistre comme empreinte de l'appareil. Les appels suivants depuis un autre appareil avec le même code sont refusés jusqu'à réinitialisation par l'enseignant. En cas de perte de tablette, l'enseignant réinitialise l'empreinte via l'interface — la progression est conservée et récupérable par l'app sur la nouvelle tablette.
|
||||
|
||||
### 3.5 Activités (modules pédagogiques)
|
||||
|
||||
```sql
|
||||
|
|
@ -143,51 +174,38 @@ activities (
|
|||
)
|
||||
```
|
||||
|
||||
Le champ `import_id` permet à un enseignant de partager une activité avec un collègue via un identifiant unique. L'import crée un **duplicata indépendant** — chaque enseignant possède sa propre copie, modifiable librement.
|
||||
Le champ `import_id` permet à un enseignant de partager une activité avec un collègue via un identifiant unique. L'import crée un **duplicata indépendant** : chaque enseignant possède sa propre copie, modifiable librement.
|
||||
|
||||
### 3.6 Assignation classes ↔ activités avec fenêtres temporelles
|
||||
### 3.6 Assignation classes / activités
|
||||
|
||||
```sql
|
||||
activity_assignments (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
activity_id uuid NOT NULL REFERENCES activities(id) ON DELETE CASCADE,
|
||||
class_id uuid NOT NULL REFERENCES classes(id) ON DELETE CASCADE,
|
||||
available_from timestamptz, -- null = disponible immédiatement
|
||||
available_until timestamptz, -- null = pas de date de fin
|
||||
assigned_at timestamptz DEFAULT now(),
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
activity_id uuid NOT NULL REFERENCES activities(id) ON DELETE CASCADE,
|
||||
class_id uuid NOT NULL REFERENCES classes(id) ON DELETE CASCADE,
|
||||
assigned_at timestamptz DEFAULT now(),
|
||||
UNIQUE (activity_id, class_id)
|
||||
)
|
||||
```
|
||||
|
||||
La vérification de disponibilité est effectuée **côté serveur**, sur l'heure Supabase — et non sur l'heure de la tablette. Un élève ne peut pas débloquer un module avant son heure d'ouverture en modifiant la date système de son appareil.
|
||||
|
||||
### 3.7 Progression (histoire principale)
|
||||
|
||||
> ⚠️ **À préciser** — La structure de progression dépend du modèle narratif définitif (nombre de chapitres, étapes, conditions de déblocage). Cette table sera spécifiée après arbitrage avec l'équipe World Game.
|
||||
|
||||
### 3.8 Résultats d'activités
|
||||
|
||||
```sql
|
||||
progression (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
student_code_id uuid NOT NULL REFERENCES student_codes(id) ON DELETE CASCADE,
|
||||
chapter smallint NOT NULL CHECK (chapter BETWEEN 1 AND 4),
|
||||
steps_completed smallint NOT NULL DEFAULT 0 CHECK (steps_completed BETWEEN 0 AND 4),
|
||||
updated_at timestamptz DEFAULT now(),
|
||||
UNIQUE (student_code_id, chapter)
|
||||
-- Total maximum : 4 chapitres × 4 étapes = 16 étapes.
|
||||
activity_results (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
student_code_id uuid NOT NULL REFERENCES student_codes(id) ON DELETE CASCADE,
|
||||
activity_id uuid NOT NULL REFERENCES activities(id) ON DELETE CASCADE,
|
||||
result jsonb NOT NULL, -- structure libre selon le type d'activité
|
||||
completed_at timestamptz DEFAULT now(),
|
||||
UNIQUE (student_code_id, activity_id) -- une tentative par élève ; à retirer si on autorise plusieurs essais
|
||||
)
|
||||
```
|
||||
|
||||
### 3.8 Résultats quiz et activités
|
||||
|
||||
```sql
|
||||
quiz_results (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
student_code_id uuid NOT NULL REFERENCES student_codes(id) ON DELETE CASCADE,
|
||||
activity_id uuid NOT NULL REFERENCES activities(id) ON DELETE CASCADE,
|
||||
question_index smallint NOT NULL,
|
||||
answer_index smallint NOT NULL,
|
||||
is_correct boolean NOT NULL,
|
||||
answered_at timestamptz DEFAULT now()
|
||||
)
|
||||
```
|
||||
La structure du champ `result` est définie par l'app tierce et validée dans l'Edge Function à la soumission. Exemple pour un quiz : `{ "answers": [1, 0, 2, 1], "score": 3, "duration_seconds": 120 }`.
|
||||
|
||||
### 3.9 Accès libre par classe
|
||||
|
||||
|
|
@ -202,34 +220,19 @@ free_access (
|
|||
)
|
||||
```
|
||||
|
||||
### 3.10 Sessions de jeu (temps de jeu et statistiques)
|
||||
|
||||
```sql
|
||||
play_sessions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
student_code_id uuid NOT NULL REFERENCES student_codes(id) ON DELETE CASCADE,
|
||||
started_at timestamptz NOT NULL,
|
||||
ended_at timestamptz, -- null si session interrompue
|
||||
duration_seconds integer -- calculé à la fermeture ou à l'auto-save
|
||||
)
|
||||
```
|
||||
|
||||
**Auto-save 20h55 :** un timer côté application déclenche une sauvegarde automatique à 20h55 heure serveur (Supabase), 5 minutes avant la fermeture automatique de l'application par JAMF à 21h. Cela prévient toute perte de progression.
|
||||
|
||||
---
|
||||
|
||||
## 4. Stratégie d'anonymat
|
||||
|
||||
### 4.1 Tableau des flux de données
|
||||
|
||||
| Donnée | Stockée où | Accessible par | Donnée personnelle ? |
|
||||
| ------------------------- | --------------------------------------- | ----------------------- | --------------------------------- |
|
||||
| Email enseignant | Supabase Auth (chiffré) | L'enseignant lui-même | **Oui** |
|
||||
| Code élève (`BR37-14763`) | Base de données | Enseignant de la classe | **Non** — opaque, aléatoire |
|
||||
| Prénom élève | `localStorage` du navigateur enseignant | Personne d'autre | Non — hors système |
|
||||
| Progression / résultats | Base de données | Enseignant de la classe | **Non** — lié au code |
|
||||
| Device fingerprint | Base de données | Système uniquement | **Non** — hash non-réidentifiable |
|
||||
| Avatar, personnalisation | Stockage local app tierce (hors scope) | L'élève uniquement | Non — hors système |
|
||||
| Donnée | Stockée où | Accessible par | Donnée personnelle ? |
|
||||
| ------------------------- | --------------------------------------- | ----------------------- | --------------------------- |
|
||||
| Email enseignant | Supabase Auth (chiffré) | L'enseignant lui-même | **Oui** |
|
||||
| Code élève (`BR37-14763`) | Base de données | Enseignant de la classe | **Non** (opaque, aléatoire) |
|
||||
| Prénom élève | `localStorage` du navigateur enseignant | Personne d'autre | Non (hors système) |
|
||||
| Progression / résultats | Base de données | Enseignant de la classe | **Non** (lié au code) |
|
||||
| Avatar, personnalisation | Stockage local app tierce (hors scope) | L'élève uniquement | Non (hors système) |
|
||||
|
||||
### 4.2 Principes fondamentaux
|
||||
|
||||
|
|
@ -237,13 +240,14 @@ play_sessions (
|
|||
|
||||
**Le code est opaque.** `BR37-14763` est généré aléatoirement. Il ne contient aucun segment dérivé du nom, prénom, date de naissance ou numéro de l'élève. Il est non-devinable et non-corrélable à un individu.
|
||||
|
||||
**Le chaînon manquant reste hors système.** La correspondance entre le code et l'identité de l'élève (ex. "BR37-14763 = Thomas Dupont") existe uniquement dans un document tenu par l'enseignant — tableur local, liste papier — qui n'est jamais transmis au serveur de World Game.
|
||||
**Le chaînon manquant reste hors système.** La correspondance entre le code et l'identité de l'élève (ex. "BR37-14763 = Thomas Dupont") existe uniquement dans un document tenu par l'enseignant (tableur local, liste papier) qui n'est jamais transmis au serveur de World Game.
|
||||
|
||||
**L'anonymat total s'applique aux élèves, pas aux enseignants.** L'email de l'enseignant est conservé pour la récupération de mot de passe : à 3 000 à 4 000 comptes, un reset manuel par World Game est intenable, seul l'email permet un flux autonome et sécurisé. L'email est déclaré dans le registre des traitements, base légale « contrat de service », et ne sort jamais du périmètre Supabase Auth.
|
||||
|
||||
**Résistance à une compromission de la base de données.** Même en cas d'accès non autorisé à l'intégralité de la base de données PostgreSQL, il est impossible de remonter à l'identité d'un élève. Il ne manque pas un mot de passe, il manque un document qui n'a jamais existé dans le système.
|
||||
|
||||
**Données locales et données API sont séparées.** L'avatar, les préférences et la personnalisation de l'élève sont gérés localement par l'application tierce et ne transitent pas par l'API World Game. Seules la progression dans l'histoire et les réponses aux activités sont transmises à l'API. En cas de perte de tablette, les données purement locales (avatar, personnalisation) ne sont pas récupérables depuis le serveur — ce qui est voulu.
|
||||
**Données locales et données API sont séparées.** L'avatar, les préférences et la personnalisation de l'élève sont gérés localement par l'application tierce et ne transitent pas par l'API World Game. Seules la progression dans l'histoire et les réponses aux activités sont transmises à l'API. En cas de perte de tablette, les données purement locales (avatar, personnalisation) ne sont pas récupérables depuis le serveur, ce qui est voulu.
|
||||
|
||||
**Réinitialisation tablette perdue.** L'enseignant peut dissocier un code de son appareil depuis l'interface. Le prochain appareil qui utilisera ce code récupère la progression serveur. Les données locales de l'ancienne tablette ne sont pas transmises.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -251,6 +255,8 @@ play_sessions (
|
|||
|
||||
### 5.1 Rôles
|
||||
|
||||
Le RGPD impose d'identifier deux acteurs distincts dès lors que des données personnelles sont traitées. Le **responsable de traitement** est l'entité qui décide pourquoi et comment les données sont collectées : ici l'établissement ou le département, qui déploie l'application auprès de ses élèves. Le **sous-traitant** est l'entité qui traite ces données pour son compte : ici World Game, qui les héberge et les gère sur ses serveurs. Cette distinction implique la signature d'un contrat formel entre les deux parties (appelé DPA, *Data Processing Agreement*). Ce document est rédigé par World Game (qui connaît ses propres pratiques de traitement, son infrastructure et ses sous-traitants en cascade : Supabase, Infomaniak) puis soumis à la validation du département avant signature. Il sera produit une fois la structure de données et les fonctionnalités définitivement arrêtées, avant le déploiement en production.
|
||||
|
||||
| Rôle | Entité |
|
||||
| ------------------------- | ------------------------------------------- |
|
||||
| Responsable de traitement | Établissement scolaire / Département |
|
||||
|
|
@ -261,24 +267,24 @@ play_sessions (
|
|||
| Catégorie | Données | Base légale |
|
||||
| ----------- | --------------------------------------- | ------------------ |
|
||||
| Enseignants | Email, nom d'affichage | Contrat de service |
|
||||
| Élèves | Aucune — codes pseudonymisés uniquement | — |
|
||||
| Élèves | Aucune (codes pseudonymisés uniquement) | s.o. |
|
||||
|
||||
Les codes élèves sont des **données pseudonymisées** au sens du RGPD : ils ne permettent pas, par eux-mêmes ou par croisement avec d'autres données du système, de réidentifier une personne physique. La clé de correspondance (liste enseignant) est externe au système.
|
||||
|
||||
### 5.3 Durée de conservation
|
||||
|
||||
- **Données de progression élève** : supprimées automatiquement à la fin de l'année scolaire configurée, ou à la suppression de la classe par l'enseignant.
|
||||
- **Données de progression élève** : durée de conservation à définir.
|
||||
- **Compte enseignant** : conservé jusqu'à la résiliation, puis supprimé sur demande.
|
||||
- **Sauvegardes PostgreSQL** : rétention Supabase par défaut (7 jours pour le plan Pro, configurable).
|
||||
|
||||
### 5.4 Droits des personnes
|
||||
|
||||
- **Enseignants** : accès, rectification et suppression du compte via l'interface ou sur demande à World Game.
|
||||
- **Élèves** : aucune donnée nominative côté serveur → droits RGPD exercés indirectement via l'enseignant (suppression du code = suppression des données serveur de l'élève).
|
||||
- **Élèves** : aucune donnée nominative côté serveur. Les droits RGPD sont exercés indirectement via l'enseignant (suppression du code = suppression des données serveur de l'élève).
|
||||
|
||||
### 5.5 Audit prévu
|
||||
|
||||
Un audit de conformité RGPD est prévu en septembre par la DRAM. Cette note technique constitue le support de conformité principal. World Game tient à disposition le registre des traitements et le DPA sur simple demande.
|
||||
Un audit de conformité RGPD est prévu en septembre par la DRAM. Cette note technique constitue le support de conformité principal. World Game tient à jour un registre interne des traitements (obligatoire au titre de l'article 30 du RGPD) et fournira le DPA au département avant le déploiement en production.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -287,15 +293,17 @@ Un audit de conformité RGPD est prévu en septembre par la DRAM. Cette note tec
|
|||
### 6.1 Enseignant
|
||||
|
||||
**Inscription :**
|
||||
|
||||
1. L'enseignant accède à la page d'inscription de l'interface web.
|
||||
2. Il saisit : email, mot de passe, **code établissement** (fourni par World Game à son collège).
|
||||
3. Une Edge Function valide le code d'établissement côté serveur.
|
||||
4. Si le code est valide et non utilisé : création du compte Supabase Auth + création du profil `teachers` + marquage du code comme utilisé.
|
||||
5. Si le code est invalide ou expiré : refus — aucun compte n'est créé.
|
||||
4. Si le code est valide et non expiré : création du compte Supabase Auth + création du profil `teachers`.
|
||||
5. Si le code est invalide ou expiré : refus, aucun compte n'est créé.
|
||||
|
||||
Ce mécanisme rend impossible toute inscription non autorisée. Il n'existe pas de formulaire d'inscription accessible publiquement, ce qui élimine le vecteur d'attaque par bots.
|
||||
Ce mécanisme rend impossible toute inscription non autorisée. La page d'inscription est accessible publiquement, mais sans code établissement valide aucun compte ne peut être créé, ce qui élimine le vecteur d'attaque par bots.
|
||||
|
||||
**Connexion :**
|
||||
|
||||
- Email + mot de passe → Supabase Auth → JWT signé (algorithme RS256, expiration 1h) + refresh token (rotation automatique).
|
||||
|
||||
### 6.2 Élève — contrat d'API
|
||||
|
|
@ -306,34 +314,40 @@ L'élève n'a **aucun compte**. Les apps tierces s'authentifient auprès de l'AP
|
|||
|
||||
```json
|
||||
// Corps de la requête
|
||||
{ "code": "BR37-14763", "device_id": "<uuid-opaque-généré-par-l-app>" }
|
||||
{ "code": "BR37-14763" }
|
||||
|
||||
// Réponse (200 OK)
|
||||
{ "token": "<jwt>", "student_code_id": "<uuid>" }
|
||||
|
||||
// Erreurs
|
||||
// 401 — code invalide ou inactif
|
||||
// 403 — code déjà lié à un autre device_id (tablette non autorisée)
|
||||
```
|
||||
|
||||
- Le JWT retourné contient `student_code_id` en claim et est signé par Supabase.
|
||||
- L'app tierce est responsable du stockage sécurisé de ce JWT (Keychain, secure storage…).
|
||||
- L'app tierce est responsable du stockage sécurisé de ce JWT.
|
||||
- Expiration du JWT : configurable (recommandé : 7 jours sur tablette gérée JAMF).
|
||||
- **`device_id`** : identifiant opaque fourni par l'app tierce — sans PII, non-réidentifiable.
|
||||
|
||||
**Réinitialisation (tablette perdue) :**
|
||||
- L'enseignant réinitialise le code depuis l'interface → `device_fingerprint` supprimé → le prochain appel `POST /api/student/auth` avec un nouveau `device_id` est accepté → progression conservée côté serveur.
|
||||
|
||||
### 6.3 Row Level Security (RLS)
|
||||
|
||||
Toutes les tables Supabase sont protégées par des politiques RLS. Elles constituent une deuxième ligne de défense indépendante du code applicatif.
|
||||
Toutes les tables Supabase sont protégées par des politiques RLS. Elles constituent une deuxième ligne de défense indépendante du code applicatif. Les opérations qui requièrent des droits étendus (inscription, jonction d'une classe par code, import d'une activité) sont traitées par des Edge Functions avec la `service_role` et ne sont jamais exposées directement au client.
|
||||
|
||||
| Acteur | Politique |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| Enseignant | Lecture/écriture uniquement sur ses propres ressources (`auth.uid() = teacher_id`) |
|
||||
| Élève | Écriture uniquement sur sa propre progression (`auth.jwt() ->> 'student_code_id' = student_code_id`) |
|
||||
| Autre élève | Zéro accès aux données d'un autre élève |
|
||||
| `service_role` | Accès complet — réservé aux Edge Functions côté serveur, jamais exposé au client |
|
||||
| Table | Enseignant authentifié | Élève authentifié (JWT) | Non authentifié |
|
||||
| -------------------------- | --------------------------------------------------- | ---------------------------------------- | --------------- |
|
||||
| `teachers` | Lecture/écriture sur son propre profil | Aucun accès | Aucun accès |
|
||||
| `classes` | Lecture/écriture sur ses classes | Aucun accès | Aucun accès |
|
||||
| `teacher_classes` | Lecture de ses propres adhésions | Aucun accès | Aucun accès |
|
||||
| `student_codes` | Lecture/création/suppression pour ses classes | Aucun accès direct | Aucun accès |
|
||||
| `activities` | Lecture/écriture sur ses propres activités | Lecture des activités publiées assignées à sa classe | Aucun accès |
|
||||
| `activity_assignments` | Lecture/écriture pour ses activités et ses classes | Lecture pour sa classe | Aucun accès |
|
||||
| `activity_results` | Lecture pour ses classes | Écriture sur ses propres résultats | Aucun accès |
|
||||
| `free_access` | Lecture/écriture pour ses classes | Lecture pour sa classe | Aucun accès |
|
||||
| `school_registration_codes`| Aucun accès direct | Aucun accès | Aucun accès |
|
||||
|
||||
**Opérations via Edge Function (`service_role`) :**
|
||||
- Inscription enseignant (validation du code établissement + création du compte)
|
||||
- Jonction d'une classe par code (`class_code`)
|
||||
- Import d'une activité par identifiant de partage (`import_id`)
|
||||
- Authentification élève (`POST /api/student/auth`)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -341,15 +355,17 @@ Toutes les tables Supabase sont protégées par des politiques RLS. Elles consti
|
|||
|
||||
### 7.1 Protection anti-bots à l'inscription
|
||||
|
||||
L'absence de formulaire d'inscription public est la protection la plus efficace : aucun endpoint n'accepte une création de compte sans code établissement valide. Un bot ne peut pas créer de comptes en masse.
|
||||
La page d'inscription est publique, mais l'endpoint de création de compte valide systématiquement le code établissement côté serveur avant toute création. Un bot ne peut pas créer de comptes en masse faute de posséder un code valide.
|
||||
|
||||
### 7.2 Rate limiting
|
||||
|
||||
Les Edge Functions appliquent un rate limiting sur les tentatives de codes invalides (élève ou établissement). Un délai croissant est introduit après plusieurs échecs consécutifs depuis un même IP.
|
||||
Les Edge Functions appliquent un rate limiting sur les tentatives de codes invalides (élève ou établissement). Au-delà d'un seuil d'échecs consécutifs depuis un même IP, l'endpoint retourne une erreur HTTP 429 (Too Many Requests) avec un délai d'attente croissant avant de pouvoir réessayer. Côté app, cela se traduit par un message explicite à l'utilisateur et un blocage temporaire du formulaire.
|
||||
|
||||
### 7.3 Validation côté serveur
|
||||
|
||||
Toutes les données entrantes sont validées avec la bibliothèque **Zod** dans les Edge Functions, indépendamment de la validation côté client. Un input malformé est rejeté avant tout traitement.
|
||||
Toutes les données entrantes sont validées dans les Edge Functions, indépendamment de la validation côté client. Un payload malformé est rejeté avant tout traitement.
|
||||
|
||||
Les payloads de l'API CTV étant simples (un code élève, un objet résultat), la validation est réalisée par des vérifications explicites dans le code (type, présence des champs obligatoires). Si les payloads venaient à se complexifier, l'adoption d'une bibliothèque de validation de schéma (par exemple Zod) serait à envisager.
|
||||
|
||||
### 7.4 Protection CSRF
|
||||
|
||||
|
|
@ -365,7 +381,7 @@ Le format `XX##-#####` (lettres + chiffres) génère plusieurs millions de combi
|
|||
|
||||
### 8.1 Injection SQL
|
||||
|
||||
Le client Supabase JS utilise exclusivement des **requêtes paramétrées** — il n'y a jamais de concaténation de chaînes pour construire des requêtes SQL. Aucune entrée utilisateur n'est interpolée dans une requête brute.
|
||||
Le client Supabase JS utilise exclusivement des **requêtes paramétrées** : il n'y a jamais de concaténation de chaînes pour construire des requêtes SQL. Aucune entrée utilisateur n'est interpolée dans une requête brute.
|
||||
|
||||
Les politiques RLS constituent une deuxième ligne de défense : même si une requête malformée parvenait à contourner la validation applicative, elle resterait contrainte par les politiques de sécurité définies directement dans la base de données.
|
||||
|
||||
|
|
@ -375,7 +391,7 @@ SvelteKit échappe automatiquement toutes les variables interpolées dans les te
|
|||
|
||||
### 8.3 Validation des contenus des modules
|
||||
|
||||
Les contenus de modules (questions, réponses, éléments de timeline…) sont stockés en JSONB et validés par schéma Zod à la création et à la modification. Un enseignant ne peut pas injecter de code via le contenu d'une activité.
|
||||
Les contenus de modules (questions, réponses, éléments de timeline…) sont stockés en JSONB et validés à la création et à la modification. Un enseignant ne peut pas injecter de code via le contenu d'une activité.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -399,20 +415,18 @@ PostgreSQL (accès complet, protégé par isolation réseau)
|
|||
|
||||
### 9.2 Clés d'accès
|
||||
|
||||
| Clé | Utilisée où | Droits |
|
||||
| ------------------ | ------------------------ | ----------------------------- |
|
||||
| `anon key` | Client (app, navigateur) | Limités par RLS |
|
||||
| `service_role key` | Edge Functions (serveur) | Complets — jamais côté client |
|
||||
Supabase utilise deux types de clés (nouvelle nomenclature 2025) :
|
||||
|
||||
La `service_role key` n'est jamais incluse dans le code source du client, jamais dans un bundle JS téléchargeable, jamais dans les variables d'environnement exposées au navigateur.
|
||||
| Clé | Utilisée où | Droits |
|
||||
| -------------------------------- | ------------------------ | ---------------------------- |
|
||||
| `publishable key` (`sb_publishable_xxx`) | Client (app, navigateur) | Limités par RLS |
|
||||
| `secret key` (`sb_secret_xxx`) | Edge Functions (serveur) | Complets, jamais côté client |
|
||||
|
||||
La `secret key` n'est jamais incluse dans le code source du client, jamais dans un bundle JS téléchargeable, jamais dans les variables d'environnement exposées au navigateur.
|
||||
|
||||
### 9.3 Transport
|
||||
|
||||
Toutes les communications sont chiffrées en TLS 1.2 minimum (TLS 1.3 privilégié). Aucune donnée ne transite en clair.
|
||||
|
||||
### 9.4 WebSockets
|
||||
|
||||
Supabase Realtime (mises à jour temps réel) utilise des WebSockets (`wss://`). En cas de déchiffrement SSL actif dans Netskope pour le domaine `api.conquiers-ta-vie.world-game`, une exception doit être configurée ou le déchiffrement doit être adapté pour les WebSockets. World Game transmettra la demande technique à Fabien Benard en amont du déploiement.
|
||||
Toutes les communications sont chiffrées via TLS (protocoles obsolètes TLS 1.0 et 1.1 désactivés). Aucune donnée ne transite en clair.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -420,11 +434,11 @@ Supabase Realtime (mises à jour temps réel) utilise des WebSockets (`wss://`).
|
|||
|
||||
### 10.1 Périmètre World Game côté tablette
|
||||
|
||||
L'application élève (app tierce, hors périmètre World Game) consomme l'API via HTTPS. Du côté World Game, la seule exigence réseau est que le domaine `api.conquiers-ta-vie.world-game` soit accessible depuis les tablettes. La distribution et la gestion de l'app tierce sur les tablettes JAMF restent de la responsabilité de l'équipe client.
|
||||
L'application élève (app tierce, hors périmètre World Game) consomme l'API via HTTPS. Du côté World Game, la seule exigence réseau est que le domaine `api.conquiers-ta-vie.com` soit accessible depuis les tablettes. La distribution et la gestion de l'app tierce sur les tablettes JAMF restent de la responsabilité de l'équipe client.
|
||||
|
||||
### 10.2 Gestion des sessions
|
||||
|
||||
La plage d'utilisation (jusqu'à 21h) est gérée exclusivement par JAMF School. L'application World Game n'implémente pas de logique de déconnexion forcée — JAMF s'en charge nativement.
|
||||
La plage d'utilisation (jusqu'à 21h) est gérée exclusivement par JAMF School. L'application World Game n'implémente pas de logique de déconnexion forcée : JAMF s'en charge nativement.
|
||||
|
||||
**Auto-save à 20h55 :** l'application déclenche une sauvegarde automatique basée sur l'heure serveur Supabase 5 minutes avant la coupure, pour prévenir toute perte de progression.
|
||||
|
||||
|
|
@ -434,9 +448,9 @@ Les recommandations d'accessibilité (OpenDyslexic, menu options, VoiceOver) con
|
|||
|
||||
### 10.4 Réseau
|
||||
|
||||
- **Domaine à whitelister** : `api.conquiers-ta-vie.world-game` (et sous-domaines Supabase associés)
|
||||
- **Domaines envisagés** : interface enseignant sur `conquiers-ta-vie.com`, API sur `api.conquiers-ta-vie.com`, hébergés chez Infomaniak sur un domaine géré par World Game. À confirmer (voir point 8 des décisions en suspens).
|
||||
- **Domaine à whitelister dans Netskope** : `api.conquiers-ta-vie.com` (et sous-domaines Supabase associés)
|
||||
- **Interlocuteur** : Fabien Benard (administrateur réseau)
|
||||
- **WebSockets** : à signaler à l'équipe réseau pour configuration Netskope
|
||||
- **Connectivité** : les tablettes ont accès au Wi-Fi en établissement (fibré) et peuvent se connecter depuis le domicile
|
||||
|
||||
---
|
||||
|
|
@ -446,42 +460,33 @@ Les recommandations d'accessibilité (OpenDyslexic, menu options, VoiceOver) con
|
|||
### 11.1 Hébergement
|
||||
|
||||
**Infomaniak** (Genève, Suisse) :
|
||||
|
||||
- Certification ISO 27001 (management de la sécurité de l'information)
|
||||
- Données hébergées en Suisse, non soumises au Cloud Act américain
|
||||
- Politique RSE engagée (énergie renouvelable, neutralité carbone)
|
||||
- Conforme RGPD
|
||||
|
||||
**Supabase** (infrastructure PostgreSQL managée) :
|
||||
|
||||
- Chiffrement au repos (AES-256)
|
||||
- Chiffrement en transit (TLS)
|
||||
- Sauvegardes automatiques PostgreSQL avec rétention configurable et point-in-time recovery
|
||||
- SOC 2 Type II
|
||||
|
||||
### 11.2 Environnements
|
||||
### 11.2 Monitoring et alertes
|
||||
|
||||
| Environnement | Usage | Infrastructure |
|
||||
| ----------------- | ---------------------------------------------------- | -------------------------------- |
|
||||
| **Développement** | Tests quotidiens, faux collège virtuel JAMF/Netskope | Supabase projet dev |
|
||||
| **Recettage** | Tests d'intégration, primo-testeurs | Supabase projet staging |
|
||||
| **Production** | Déploiement département | Supabase production + Infomaniak |
|
||||
**Supabase** fournit nativement via son dashboard :
|
||||
- Logs des requêtes base de données, des Edge Functions et des événements d'authentification
|
||||
- Métriques de performance (CPU, RAM, connexions actives)
|
||||
- Alertes automatiques en cas de dépassement des quotas du plan souscrit
|
||||
|
||||
### 11.3 Monitoring et alertes
|
||||
**Infomaniak** fournit nativement :
|
||||
- Surveillance de l'uptime et alertes par email en cas d'indisponibilité
|
||||
- Alertes espace disque
|
||||
- Logs serveur accessibles depuis le panneau de gestion
|
||||
|
||||
- Logs applicatifs : Supabase Dashboard (requêtes, erreurs Edge Functions, auth)
|
||||
- Alertes infrastructure : Infomaniak
|
||||
- Indicateurs clés surveillés : latence API, taux d'erreur, espace disque
|
||||
La stratégie de monitoring applicatif (seuils d'alerte personnalisés, interlocuteur d'astreinte, procédure d'escalade en cas d'incident) reste à définir avant la mise en production.
|
||||
|
||||
---
|
||||
|
||||
## Annexe — Questions ouvertes
|
||||
|
||||
| Sujet | Statut | Interlocuteur |
|
||||
| ------------------------------------------------------ | ------------------------------------- | -------------------------- |
|
||||
| Spécifications API à transmettre à l'équipe app tierce | À rédiger | Arthur Deleye / World Game |
|
||||
| Configuration WebSockets Netskope | À initier avant déploiement recettage | Fabien Benard |
|
||||
| Signature DPA World Game / Département | À planifier | Jules Zimmermann / DSI |
|
||||
| Périmètre audit RGPD DRAM | Prévu septembre | DRAM |
|
||||
|
||||
---
|
||||
|
||||
*Document rédigé par World Game — toute question technique peut être adressée à Arthur Deleye.*
|
||||
_Document rédigé par World Game. Toute question technique peut être adressée à Arthur Deleye._
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue