Panoramica
Snapp supporta più domini pubblici all’interno di un’unica installazione.
Questo non è implementato tramite istanze virtuali, database duplicati o applicazioni separate. Snapp utilizza invece le organizzazioni di Better-Auth come confine strutturale tra i domini.
Ogni dominio esposto mappa:
- una organizzazione
- un contesto di autenticazione
- uno scope di accesso predefinito
- un set di limiti e opzioni comportamentali
Tutti i domini condividono lo stesso runtime e database, mentre autorità e controllo restano isolati.
Principio fondamentale
Un dominio mappa un’organizzazione.
Questa regola guida l’intero modello.
Ogni host definito in settings.yaml:
hosts:
- origin: https://example.org
- origin: https://another-domain.dev
corrisponde a:
- un’organizzazione con
id = slugify(origin) - uno scope di accesso isolato
- un’istanza Better-Auth dedicata
- un grafo di permessi distinto
Non esiste discovery implicita né mapping inferito.
Flusso di inizializzazione
All’avvio, Snapp esegue una sequenza di bootstrap strutturata.
1. Bootstrap degli admin
Per ogni admin definito in configurazione:
admin:
- email: admin@example.org
username: admin
Snapp:
- crea l’utente se mancante
- assegna il ruolo
admin - stampa la password generata una sola volta
- non rimuove gli admin esistenti
Questo mantiene recuperabile l’accesso amministrativo.
2. Bootstrap delle organizzazioni
Per ogni host configurato:
hosts:
- origin: https://example.org
Snapp assicura che:
- esista un’organizzazione con
id = slugify(origin) - l’origine originale sia salvata nei metadati
- l’organizzazione resti stabile tra i riavvii
Le organizzazioni mancanti vengono create durante l’inizializzazione.
3. Enforcement delle membership
Tutti gli admin vengono aggiunti a ogni organizzazione come owner.
Questo previene:
- organizzazioni orfane
- domini inaccessibili
- domini senza un utente privilegiato
4. Materializzazione dei ruoli
I ruoli sono memorizzati per organizzazione:
owneradminmember
Ogni ruolo contiene un grafo di permessi serializzato.
Quando le definizioni dei ruoli cambiano, Snapp:
- invalida la cache di autenticazione in memoria
- ricarica i permessi on-demand
- evita di usare ACL obsolete
Risoluzione del dominio a runtime
Per ogni richiesta in ingresso, Snapp determina l’host attivo:
- Legge
event.url.origin - Normalizza il protocollo (sviluppo vs produzione)
- Confronta con
settings.hosts - Effettua fallback sul primo host configurato se necessario
L’host risolto definisce:
- l’organizzazione attiva
- il client Better-Auth in uso
- i limiti e i feature flag applicabili
Le richieste non vengono processate senza un host risolto.
Isolamento dell’autenticazione
Ogni host ha la propria istanza Better-Auth.
Le istanze sono cachate tramite:
slugify(host.origin)
Questo garantisce isolamento per:
- sessioni
- cookie
- callback OAuth
- rate limit
- regole di signup
Quando cambiano le opzioni dell’host, la cache correlata viene invalidata.
Enforcement dell’organizzazione attiva
Nelle richieste autenticate:
- se non è impostata alcuna organizzazione attiva
- Snapp assegna l’organizzazione associata all’host corrente
Se l’utente non è membro di quell’organizzazione:
- l’accesso viene negato
- vengono controllati eventuali inviti pendenti
- vengono applicati i flussi di invito
- non viene applicato alcun fallback silenzioso
Gli utenti non possono operare in un dominio a cui non appartengono.
Risoluzione degli shortcode
Gli shortcode non sono strettamente limitati a una singola organizzazione.
La risoluzione segue una sequenza definita.
1. Match esatto scoped all’organizzazione
active = true
organizationId = organizzazione corrente
shortcode = richiesto
Questo è il percorso di lookup primario.
2. Fallback case-insensitive scoped all’organizzazione
Se abilitato:
disable:
lowerCaseFallback: false
Il lookup resta limitato all’organizzazione corrente.
3. Fallback globale (cross-organizzazione)
Se non viene trovato alcun match a livello di organizzazione:
active = true
shortcode = richiesto
ORDER BY createdAt ASC
LIMIT 1
Questo lookup è globale ed è usato come ultimo passaggio.
Motivazione del fallback globale
Il fallback globale supporta:
- shortcode creati prima del supporto multi-dominio
- link canonici che devono risolversi su più domini
- risoluzione prevedibile invece di 404 silenziosi
- riuso dei link esistenti senza duplicazione
Solo il comportamento di redirect è condiviso.
Proprietà e controllo restano invariati.
Cosa resta isolato
Anche quando viene usato il fallback globale:
- la proprietà non cambia
- i permessi non vengono bypassati
- i segreti sono applicati
- gli aggiornamenti non sono consentiti
- le analytics restano legate all’URL originale
- le organizzazioni restano isolate
L’host risolve il link senza assumere controllo su di esso.
Enforcement della sicurezza
Prima del redirect, Snapp applica:
- controlli di scadenza
- validazione dello stato attivo
- validazione VirusTotal
- regole di watchlist
- verifica del segreto (se configurato)
Il redirect avviene solo se tutti i controlli hanno esito positivo.
Riepilogo del modello dati
| Concetto | Scope |
|---|---|
| Utenti | Globale |
| Organizzazioni | Per host |
| Sessioni | Per host |
| Permessi | Per organizzazione |
| URL | Proprietà di un’organizzazione |
| Risoluzione shortcode | Org → fallback globale |
| Redirect | Stateless |
Modello mentale
Snapp può essere visto come:
- un solo runtime
- più domini
- un database condiviso
- confini di autorità espliciti
- risoluzione cross-dominio controllata
I domini restano isolati per il controllo, mentre i link restano interoperabili per la risoluzione.
Implicazioni di configurazione
Aggiungere un dominio:
- crea un’organizzazione
- assegna la membership agli admin
- inizializza i ruoli
- abilita immediatamente l’accesso
Rimuovere un dominio:
- elimina l’organizzazione
- invalida le cache di autenticazione
- lascia gli URL intatti ma irraggiungibili da quell’host
Riepilogo
- Il supporto multi-dominio è nativo
- Le organizzazioni definiscono i confini
- L’autenticazione è scoped per host
- I permessi sono espliciti
- Il fallback degli shortcode segue un ordine definito
- Non avviene alcuna fuga implicita di dati
Il modello è deterministico, osservabile e reversibile.