Modèle conceptuel
Tenant (Établissement)
├── Users (étudiants, professeurs, coordinateurs, admin institutionnel)
├── Courses → Modules → Lessons
├── ClassGroups (groupes de classe)
├── MediaAssets (bibliothèque de médias)
├── Automations
├── EmailTemplates
├── VideoProviderConfig (BBB/Zoom/Teams/Meet)
├── TenantApiKey (clés IA propres)
├── TenantSubscription (facturation)
├── ...toutes les autres entités
Les utilisateurs sans tenantId sont des utilisateurs B2C (plateforme directe). Les utilisateurs avec tenantId appartiennent à un établissement.
Isolation des données — 3 couches
Couche 1 : Filtrage obligatoire dans les requêtes
Toute requête Prisma dans le code applicatif filtre par tenantId :
const { tenantId } = requireTenant(user);
const courses = await prisma.course.findMany({
where: { tenantId }, // OBLIGATOIRE
});
requireTenant() dans apps/web/lib/tenant.ts retourne NextResponse 403 si l'utilisateur n'a pas de tenant. Sans cet appel, il est impossible d'accéder aux données B2B.
Couche 2 : RLS dans Supabase
En tant que filet de sécurité contre les bugs, les politiques RLS dans Supabase renforcent l'isolement même pour les requêtes directes. Si un code oublie le filtre tenantId, la base de données refuse.
Couche 3 : Audit de l'admin global
Lorsqu'un admin global doit accéder aux données d'un tenant (support, débogage), il utilise l'usurpation d'identité via un cookie HMAC avec :
- TTL fixe de 1h (non prolongeable)
- Signature HMAC-SHA256 avec
IMPERSONATION_SECRET - Audit dans
AdminAuditLog(actionimpersonate.start, IP, user-agent) getUserProfile()retourneisImpersonating: true+ overlay en mémoire- L'authentification Supabase n'est JAMAIS modifiée (overlay uniquement)
- Une bannière persistante sur /institution/ avertit l'admin pendant la session
Le panneau admin SaaS global inclut l'impersonation pour le support.
Clés API par tenant
Par défaut, tous les appels LLM utilisent les clés globales (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) de Studeia. Les coûts sont imputés au tenant via le métering.
Mais les établissements qui disposent déjà de comptes Anthropic/OpenAI/Google peuvent utiliser leurs propres clés (TenantApiKey, chiffrée AES-256-GCM en base) :
- Les coûts vont DIRECTEMENT sur le compte du tenant chez Anthropic/OpenAI
- Studeia ne facture pas de marge IA — seulement l'abonnement mensuel
- Résolution automatique :
TenantApiKey→ProviderApiKeyglobal →process.env(cascade dansapps/web/lib/api-key-resolver.ts)
Ne définit JAMAIS process.env en runtime (cela créerait des conflits entre tenants) — passe la clé via les options du SDK.
White-label complet
Chaque tenant peut personnaliser :
| Aspect | Comment |
|---|---|
| Logo, favicon | Upload dans les paramètres |
| Couleurs (primaire, accent, arrière-plan) | Éditeur avec prévisualisation |
| Police (Google Fonts) | Liste déroulante |
| Thème visuel (parmi 9 options) | Bascule par utilisateur ou valeur par défaut du tenant |
| CSS personnalisé | Assaini, max 10 Ko |
| Domaine personnalisé | DNS CNAME + TXT _studeia-verify + TLS automatique Caddy (on-demand) |
| Logo dans les e-mails | Par template |
| E-mail expéditeur (SMTP/Resend/SendGrid) | TenantEmailConfig |
| Masquer la marque Studeia | Oui (plan Enterprise) |
Rôles et permissions
| Rôle | Périmètre | Peut faire |
|---|---|---|
student | Sa propre progression | Chat tuteur, cours, simulations, gamification |
parent | Enfants associés | Voir la progression, alertes, rapports |
teacher | Ses propres classes/cours | Créer des cours, uploader du contenu, voir les élèves de ses classes |
coordinator | Toutes les classes du tenant | Gérer les classes, voir tous les élèves |
pedagogue | Tous les élèves du tenant | Accompagnement pédagogique, rapports |
institution_admin | Tout le tenant | Config IA, clés API, white-label, utilisateurs |
admin | Global (plateforme) | Tout + gérer les tenants |
Limites par plan
Limites appliquées via checkTenantResourceLimit(tenantId, resource) dans apps/web/lib/plan-limits.ts :
| Plan | Étudiants max | Professeurs | Cours | IA |
|---|---|---|---|---|
| Demo | 1 | 1 | 1 | Haiku uniquement, 10 msgs/jour |
| Mini | 10 | Illimité | Illimité | Tous les providers |
| Crescimento | 50 | Illimité | Illimité | Tous les providers |
| Escala | 100 | Illimité | Illimité | Tous les providers |
| Enterprise | Personnalisé (maxStudentsOverride) | Illimité | Illimité | Tous les providers |
7 points d'application (tous audités le 2026-04-11) :
POST /api/courses/[courseId]/enroll— auto-inscription de l'étudiantPOST /api/institution/courses/[id]/clone— clonage de coursPOST /api/institution/courses/import— import IMS CCexecuteEnrollUser()dans une automatisationPOST /api/scim/v2/Users— provisionnement SCIMPOST /api/institution/users— associer un utilisateur existantPATCH /api/institution/users/[id]— promouvoir un rôle
Comment demander un dépassement Enterprise
Les tenants Enterprise peuvent avoir Tenant.maxStudentsOverride: Int? ajusté par l'admin global. Sans dépassement (null), Enterprise = illimité. Contactez le service commercial via contact@studeia.com.
Limitations
- Un User appartient à UN seul tenant. Pour les professeurs intervenant dans plusieurs établissements, créer des utilisateurs distincts.
- Le partage de cours entre tenants n'est pas natif (chaque tenant dispose de son propre CMS isolé). Feuille de route : marketplace de cours avec licences.
- La migration d'un tenant vers un autre plan est instantanée, mais le passage à un plan inférieur qui viole les limites actuelles est bloqué jusqu'à ce que le tenant ajuste ses ressources.