Sistema Multi-Tenant¶
Cos'è un Tenant¶
Un Tenant è uno spazio completamente isolato che rappresenta un'organizzazione o un gruppo di utenti. Dati, configurazioni e moduli di un tenant non sono mai visibili a un altro.
Campi principali dell'entità Tenant¶
| Campo | Tipo | Note |
|---|---|---|
id |
UUID | Identificatore univoco interno |
name |
string | Nome leggibile dell'organizzazione |
slug |
string | Identificatore URL-safe (kebab-case) |
modules |
string[] | Lista moduli abilitati per il tenant |
aiTier |
fast / slow |
Velocità motore AI |
logoUrl |
string | Logo personalizzato |
isActive |
boolean | Tenant abilitato o disabilitato |
Regola Slug¶
Lo slug deve rispettare la regex: /^[a-z0-9]+(?:-[a-z0-9]+)*$/
Esempi validi: my-company, itnetlab, cliente123
Esempi non validi: My Company, cliente_123, --test
Come viene Identificato il Tenant¶
Il TenantMiddleware intercetta ogni richiesta HTTP e identifica il tenant in due modi (in ordine di priorità):
- Header HTTP
X-Tenant-Slug: my-company - Fallback URL path — primo segmento del path come euristica
Il tenant viene allegato all'oggetto Request per essere disponibile nei controller downstream.
Isolamento dei Dati — Regola Zero-Trust¶
Regola NON derogabile
Ogni query che riguarda dati di un tenant DEVE includere il filtro tenantId.
Ogni query che riguarda dati privati di un utente DEVE includere anche ownerId.
// Modello corretto — isolamento garantito
where: {
tenantId: user.tenantId, // SEMPRE — isola tra tenant
ownerId: user.id // SEMPRE per entità private dell'utente
}
Se uno di questi filtri manca, un utente potrebbe vedere dati di un altro tenant o di un altro utente. Tutti i service con @ai-critical riguardano questo aspetto.
Gerarchia Configurazione¶
Per SMTP e Storage, la configurazione segue questa gerarchia (la più specifica ha la precedenza):
Utente ──→ massima priorità
↓ se non configurato
Tenant
↓ se non configurato
Globale ──→ fallback finale
Creazione di un Nuovo Tenant¶
Quando il Global Admin crea un nuovo tenant, il sistema automaticamente:
- Valida lo slug (formato kebab-case strict)
- Crea il record
Tenantconmodules: []— vuoto, i moduli si assegnano dopo - Crea o assegna un Tenant Admin
- Forza
isTwoFactorEnabled: truesul Tenant Admin (MFA obbligatorio) - Imposta
requiresPasswordChange: true— cambio password obbligatorio al primo accesso
Global Admin ≠ Tenant
I Global Admin hanno tenantId: null — non appartengono a nessun tenant.
Non possono essere creati nel contesto di un tenant.