Modelo conceitual
Tenant (Instituicao)
├── Users (alunos, professores, coordenadores, admin institucional)
├── Courses → Modules → Lessons
├── ClassGroups (turmas)
├── MediaAssets (biblioteca de midia)
├── Automations
├── EmailTemplates
├── VideoProviderConfig (BBB/Zoom/Teams/Meet)
├── TenantApiKey (chaves proprias de IA)
├── TenantSubscription (billing)
├── ...todas as outras entidades
Usuarios sem tenantId sao B2C (plataforma direta). Usuarios com tenantId pertencem a uma instituicao.
Isolamento de dados — 3 camadas
Camada 1: Filtro obrigatorio em queries
Toda query Prisma em codigo de aplicacao filtra por tenantId:
const { tenantId } = requireTenant(user);
const courses = await prisma.course.findMany({
where: { tenantId }, // OBRIGATORIO
});
requireTenant() em apps/web/lib/tenant.ts retorna NextResponse 403 se o usuario nao tem tenant. Sem essa chamada, e impossivel acessar dados B2B.
Camada 2: RLS no Supabase
Como safety net contra bugs, policies RLS no Supabase reforcam isolamento ate em queries diretas. Se um codigo esquecer o filtro tenantId, o banco recusa.
Camada 3: Auditoria de admin global
Quando um admin global precisa acessar dados de um tenant (suporte, debug), ele usa impersonacao via cookie HMAC com:
- TTL fixo de 1h (nao prorrogavel)
- Assinatura HMAC-SHA256 com
IMPERSONATION_SECRET - Auditoria em
AdminAuditLog(acaoimpersonate.start, IP, user-agent) getUserProfile()retornaisImpersonating: true+ overlay em memoria- Supabase auth NUNCA modificado (apenas overlay)
- Banner sticky no /institution/ alerta o admin durante a sessao
Detalhes em painel admin SaaS.
Per-tenant API keys
Por padrao, todas as chamadas LLM usam chaves globais (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc) do Studeia. Custos vao para o tenant via metering.
Mas instituicoes que ja tem contas Anthropic/OpenAI/Google podem usar proprias chaves (TenantApiKey, criptografada AES-256-GCM no banco):
- Custos vao DIRETO para a conta do tenant na Anthropic/OpenAI
- Studeia nao cobra margem de IA — apenas a assinatura mensal
- Resolucao automatica:
TenantApiKey→ProviderApiKeyglobal →process.env(cascata emapps/web/lib/api-key-resolver.ts)
NUNCA seta process.env em runtime (conflitaria entre tenants) — passa a key via options do SDK.
White-label completo
Cada tenant pode personalizar:
| Aspecto | Como |
|---|---|
| Logo, favicon | Upload no settings |
| Cores (primary, accent, background) | Editor com preview |
| Fonte (Google Fonts) | Dropdown |
| Tema visual (de 9 opcoes) | Toggle por usuario ou padrao do tenant |
| Custom CSS | Sanitizado, max 10KB |
| Dominio customizado | DNS CNAME + TLS automatico Traefik |
| Logo no email | Por template |
| Sender email (SMTP/Resend/SendGrid) | TenantEmailConfig |
| Esconder marca Studeia | Sim (plano enterprise) |
Roles e permissoes
| Role | Escopo | Pode fazer |
|---|---|---|
student | Proprio progresso | Chat tutor, cursos, simulados, gamificacao |
parent | Filhos vinculados | Ver progresso, alertas, relatorios |
teacher | Proprias turmas/cursos | Criar cursos, upload material, ver alunos das turmas |
coordinator | Todas turmas do tenant | Gerenciar turmas, ver todos os alunos |
pedagogue | Todos alunos do tenant | Orientacao educacional, relatorios |
institution_admin | Todo o tenant | Config IA, API keys, white-label, usuarios |
admin | Global (plataforma) | Tudo + gerenciar tenants |
Limites por plano
Limites enforceados via checkTenantResourceLimit(tenantId, resource) em apps/web/lib/plan-limits.ts:
| Plano | Alunos max | Professores | Cursos | IA |
|---|---|---|---|---|
| Demo | 1 | 1 | 1 | Haiku only, 10 msgs/dia |
| Mini | 10 | Ilimitado | Ilimitado | Todos providers |
| Crescimento | 50 | Ilimitado | Ilimitado | Todos providers |
| Escala | 100 | Ilimitado | Ilimitado | Todos providers |
| Enterprise | Custom (maxStudentsOverride) | Ilimitado | Ilimitado | Todos providers |
7 pontos de enforcement (todos auditados em 2026-04-11):
POST /api/courses/[courseId]/enroll— self-enroll do alunoPOST /api/institution/courses/[id]/clone— clone de cursoPOST /api/institution/courses/import— import IMS CCexecuteEnrollUser()em automacaoPOST /api/scim/v2/Users— provisionamento SCIMPOST /api/institution/users— vincular usuario existentePATCH /api/institution/users/[id]— promover role
Como solicitar override enterprise
Tenants enterprise podem ter Tenant.maxStudentsOverride: Int? ajustado pelo admin global. Sem override (null), enterprise = ilimitado. Contato comercial via suporte@studeia.com.
Limitacoes
- Um User pertence a UM tenant. Para professores que atendem multiplas escolas, criar usuarios separados.
- Compartilhamento de cursos entre tenants nao e nativo (cada tenant tem seu CMS isolado). Roadmap: marketplace de cursos com licenciamento.
- Migracao de tenant para outro plano e instantanea, mas downgrade que viola limites atuais e bloqueado ate o tenant ajustar os recursos.