Modèle conceptuel
Tenant (Établissement)
├── Users (étudiants, professeurs, coordinateurs, admin institutionnel)
├── Courses → Modules → Lessons
├── ClassGroups (groupes de classe)
├── MediaAssets (bibliothèque multimédia)
├── Automations
├── EmailTemplates
├── VideoProviderConfig (BBB/Zoom/Teams/Meet)
├── TenantApiKey (clés API propres)
├── TenantSubscription (facturation)
├── ...toutes les autres entités
Les utilisateurs sans tenantId sont 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 un 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'isolation 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'impersonation via un cookie HMAC avec :
- TTL fixe d'1 heure (non prolongeable)
- Signature HMAC-SHA256 avec
IMPERSONATION_SECRET - Audit dans
AdminAuditLog(actionimpersonate.start, IP, user-agent) getUserProfile()retourneisImpersonating: true+ overlay en mémoire- L'auth Supabase n'est JAMAIS modifiée (overlay uniquement)
- Une bannière sticky 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 de Studeia (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.). Les coûts sont imputés au tenant via le metering.
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 de données) :
- Les coûts vont DIRECTEMENT sur le compte du tenant chez Anthropic/OpenAI
- Studeia ne facture pas de marge sur l'IA — uniquement l'abonnement mensuel
- Résolution automatique :
TenantApiKey→ProviderApiKeyglobal →process.env(cascade dansapps/web/lib/api-key-resolver.ts)
Ne modifie JAMAIS process.env à l'exécution (conflits entre tenants) — la clé est transmise via les options du SDK.
White-label complet
Chaque tenant peut personnaliser :
| Aspect | Comment |
|---|---|
| Logo, favicon | Import dans les paramètres |
| Couleurs (primary, accent, background) | Éditeur avec aperçu |
| Police (Google Fonts) | Menu déroulant |
| Thème visuel (parmi 9 options) | Basculement par utilisateur ou 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 modèle |
| 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, examens blancs, gamification |
parent | Enfants liés | Voir la progression, alertes, rapports |
teacher | Ses propres classes/cours | Créer des cours, importer du contenu, voir les étudiants de ses classes |
coordinator | Toutes les classes du tenant | Gérer les classes, voir tous les étudiants |
pedagogue | Tous les étudiants du tenant | Accompagnement pédagogique, rapports |
institution_admin | Tout le tenant | Config IA, clés API, white-label, utilisateurs |
admin | Global (plateforme) | Tout + gestion des 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 msg/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— duplication de coursPOST /api/institution/courses/import— import IMS CCexecuteEnrollUser()dans une automatisationPOST /api/scim/v2/Users— provisionnement SCIMPOST /api/institution/users— rattacher un utilisateur existantPATCH /api/institution/users/[id]— promouvoir un rôle
Comment demander un override Enterprise
Les tenants Enterprise peuvent faire ajuster Tenant.maxStudentsOverride: Int? par l'admin global. Sans override (null), Enterprise = illimité. Contactez l'équipe commerciale via contact@studeia.com.
Limitations
- Un User appartient à UN seul tenant. Pour les enseignants 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 système de licences.
- La migration d'un tenant vers un autre plan est instantanée, mais un downgrade qui violerait les limites actuelles est bloqué jusqu'à ce que le tenant ajuste ses ressources.