Vai al contenuto

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à):

  1. Header HTTP X-Tenant-Slug: my-company
  2. 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:

  1. Valida lo slug (formato kebab-case strict)
  2. Crea il record Tenant con modules: [] — vuoto, i moduli si assegnano dopo
  3. Crea o assegna un Tenant Admin
  4. Forza isTwoFactorEnabled: true sul Tenant Admin (MFA obbligatorio)
  5. 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.