Panoramica
I team sono l’unità che Snapp utilizza per delimitare l’accesso all’interno di un’organizzazione.
Un team viene creato sotto un’organizzazione e poi referenziato da:
- membership (chi fa parte del team)
- policy (cosa ogni ruolo può fare nel contesto di quel team)
- binding alle risorse (quali URL sono condivisi con il team)
Snapp utilizza le organizzazioni di Better-Auth insieme a statement di access control per applicare tutto questo.
Concetti
Organizzazione
Un’organizzazione è il confine per identità e permessi. In Snapp, le organizzazioni vengono create e selezionate per dominio host.
I team esistono solo all’interno di un’organizzazione.
Team
Un team è un contenitore che può essere usato come contesto di permessi.
Nel modello di policy, la chiave di contesto è l’id del team:
permissions: {
[teamId]: ['create', 'read', 'update', 'delete']
}
Questo consente regole per-team senza hardcodare i nomi dei team nel codice.
Ruoli
Snapp utilizza ruoli a livello di organizzazione:
owneradminmember
Il ruolo definisce le capacità di default a livello organizzativo ed è anche l’asse su cui si basano le policy dei team.
Modello dei permessi
Statement
Snapp definisce un set ridotto di azioni:
createreadupdatedeletecancel
Una policy è rappresentata come:
type TPermissions = Record<string, ('create' | 'read' | 'update' | 'delete' | 'cancel')[]>
La struttura persistita è una mappa ruolo → teamId → azioni:
Record<Role, Record<TeamId, Action[]>>
Questa struttura è memorizzata nel database nel campo organizationRole.permission.
Dove vivono le policy
Le policy sono memorizzate per organizzazione nella tabella organizationRole:
organizationId(l’organizzazione)role(owner|admin|member)permission(blob JSON)
A runtime, Snapp carica le policy dell’organizzazione corrente e costruisce il grafo effettivo dei ruoli.
Creazione dei team
Flusso UI
I team vengono creati dal pannello dell’organizzazione.
Il form di creazione raccoglie:
- nome del team
- permessi iniziali per ruolo (editor a matrice)
Il payload inviato include:
organizationIdnamepermissions(stringa JSON)
Flusso server
La creazione di un team esegue tre operazioni:
Controllo di autorizzazione Il chiamante deve avere il permesso di creare team:
permissions: { team: ['create'] }Creazione del team in Better-Auth.
Materializzazione della policy Per ogni ruolo presente in
organizationRole, Snapp aggiorna il JSON dei permessi aggiungendo:permission[team.id] = permissions[roleName]
Dopo la creazione, le cache vengono invalidate:
- cache del client di autenticazione (per host)
- cache dei ruoli (per organizzazione)
Questo garantisce che ogni richiesta successiva veda il grafo di policy aggiornato.
Modifica delle policy di un team
Editor delle policy
La UI mostra una matrice di permessi:
- righe = ruoli (
owner,admin,member) - colonne = azioni (
create,read,update,delete) - target = uno specifico team id
Il toggle di una checkbox aggiorna la mappa locale dei permessi e avvia il salvataggio.
Il ruolo owner è bloccato su accesso completo nella UI.
Operazione di salvataggio
Al salvataggio, Snapp invia un aggiornamento batch:
[{ id: roleRowId, p: permissionsForThatRole }]
Lato server, questo aggiorna organizationRole.permission per ogni riga di ruolo.
Dopo l’update:
- la cache dei ruoli viene invalidata
- la cache del client di autenticazione viene invalidata
Il successivo controllo dei permessi utilizza quindi il nuovo grafo di policy.
Come funziona l’enforcement
I controlli dei permessi sono lato server
Snapp non si fida della UI. La UI gestisce solo la comodità; l’enforcement avviene sul server tramite hasPermission.
I controlli usano una chiave di contesto
Quando una risorsa è condivisa con un team, l’id del team diventa il contesto dei permessi.
Esempi dalle operazioni sugli URL:
- Creare un URL con team associati richiede il permesso
createsu almeno uno dei team selezionati. - Aggiornare o eliminare un URL di proprietà altrui richiede il permesso corrispondente per i team a cui l’URL è assegnato.
Questo è applicato valutando:
hasPermission({
organizationId,
context: teamId,
permissions: ['create' | 'update' | 'delete']
})
Enforcement in creazione URL
Quando un URL viene creato con team:
- Snapp verifica il chiamante rispetto a ogni team selezionato
- azione richiesta:
create - l’operazione è consentita se almeno uno dei controlli ha esito positivo
Se nessuno ha esito positivo, la richiesta viene rifiutata.
Enforcement in aggiornamento URL
Durante l’aggiornamento di un URL:
se il chiamante è owner o admin → consentito
altrimenti, Snapp calcola l’unione di:
- team già assegnati all’URL
- team richiesti nell’aggiornamento
Poi verifica:
- azione richiesta:
update - consentito se almeno un controllo sui team ha esito positivo
Questo supporta modifiche incrementali senza permettere di aggirare le policy rimuovendo i team.
Enforcement in eliminazione URL
Durante l’eliminazione degli URL:
- owner e admin possono eliminare (secondo la logica del ruolo)
- i non-owner possono eliminare solo se tutti i controlli sui team passano per
delete
Questo produce una regola più restrittiva per le operazioni distruttive.
Assegnazione dei team alle risorse
Snapp collega i team agli URL tramite una tabella di join (teamToUrl).
Questo serve a due scopi:
- condivisione: i membri del team possono operare sugli URL condivisi se la policy lo consente
- contesto di enforcement: l’id del team diventa la chiave di policy per l’autorizzazione
La tabella di join viene aggiornata durante create/update dell’URL nella stessa transazione DB della mutazione dell’URL.
Comportamento delle cache
Sono coinvolte due cache:
- cache dell’istanza di autenticazione (
authCache.auth) indicizzata per origin host - cache dei ruoli (
authCache.roles) indicizzata per id organizzazione (slugify(host.origin))
Ogni operazione che modifica team o policy invalida le chiavi di cache rilevanti, mantenendo coerenti i controlli dei permessi.
Riepilogo
- I team sono contesti di permessi all’interno di un’organizzazione.
- Le policy sono memorizzate come ruolo → teamId → azioni in
organizationRole.permission. - La UI modifica le policy, ma l’enforcement avviene lato server tramite
hasPermission. - Le operazioni sugli URL usano l’assegnazione ai team per determinare quali policy applicare.
- L’invalidazione delle cache mantiene coerente il grafo effettivo dei permessi tra le richieste.