Deriva il possesso lato server
Un ID di record identifica qualcosa; non dimostra chi può leggerlo. supastack delimita i record utente per workspaceId. Il server risolve il workspace dell'utente autenticato e include quel confine quando legge o modifica un record.
Non trattare un ID di workspace inviato dal browser come prova di possesso. Il browser può richiedere un'azione, ma il contesto server fidato deve stabilire di chi è. Questa regola deve valere per stato delle attività, configurazione dei provider salvata, record di utilizzo e ogni risorsa propria del prodotto. Layout e componenti possono filtrare ciò che viene renderizzato, ma il rendering non è autorizzazione — il controllo appartiene al punto dove il record viene davvero letto o scritto.
Tutto il resto di questo post discende da quella regola: il possesso si deriva, non si richiede. Nel momento in cui un workspaceId inviato dal client viene considerato affidabile da qualche parte, ogni altro controllo diventa decorazione. Il post sull'autenticazione oltre il form di login spiega come la sessione stessa viene stabilita a monte di questa frontiera.
L'appartenenza fa parte del confine
La delimitazione risponde a quale workspace appartiene un record; l'appartenenza risponde se questo utente appartiene a quel workspace. Risolvi entrambe sul server. supastack modella workspace personali e di organizzazione con ruoli owner, admin e member — la guida ai workspace documenta il modello.
I ruoli aggiungono un secondo asse: un member può leggere ciò che solo un admin può cambiare. Tieni distinti i due controlli — «appartiene a questo workspace» e «può eseguire questa azione» — così una retrocessione o rimozione ha effetto sulla stessa frontiera di tutto il resto. Inviti e rimozioni fanno parte dello stesso ciclo di vita, non un ripensamento amministrativo.
I cambi di appartenenza devono anche avere effetto immediato. La prossima richiesta di un membro rimosso dovrebbe fallire alla frontiera, non quando un token in cache capita di scadere — un altro motivo per cui il controllo vive dove i record vengono letti, invece che dentro una credenziale emessa prima.
Rendi il ripristino utile senza concedere accesso
Un URL di attività permette a un utente di tornare dopo un refresh. È utile proprio perché conserva un riferimento al lavoro. L'API autenticata deve comunque verificare che l'attività richiesta appartenga al workspace corrente prima di restituire stato o risultati.
Lo stesso principio si applica al ripristino delle submission. Una chiave di idempotenza aiuta a identificare una richiesta ripetuta e a evitare lavoro duplicato. Non è né una sessione né un token di accesso. Riprovare una submission salvata richiede comunque autenticazione, possesso del workspace e il permesso appropriato.
I percorsi di comodo sono dove l'isolamento si erode silenziosamente. Un link che aiuta un utente legittimo a riprendere il lavoro — dopo un refresh, da un'email di completamento — deve passare per lo stesso controllo autenticato di ogni altra richiesta. La pipeline da richiesta a risultato usa chiavi di idempotenza delimitate all'attività esattamente su questo principio.
Testa le richieste che devono fallire
Un test che permette a un proprietario di recuperare un record dimostra il percorso felice. Non dimostra l'isolamento. Abbinalo a un secondo workspace che richiede lo stesso record, una richiesta anonima e una scrittura che tenta di sostituire un altro proprietario.
Verifica il comportamento esterno quanto il risultato persistito. Una richiesta rifiutata non deve mutare i dati di un altro workspace, e una risposta d'errore non deve far trapelare segreti né dettagli grezzi del database. Questi controlli mantengono il confine revisionabile quando l'implementazione cambia.
- Un secondo workspace richiede lo stesso ID di record — rifiutato.
- Una richiesta anonima colpisce lo stesso endpoint — rifiutata.
- Un membro rimosso dal workspace riprova — rifiutato.
- Una scrittura nomina un proprietario o workspace diverso — rifiutata, senza mutare nulla.
- Le risposte d'errore restano generiche — niente segreti, niente dettagli dello schema.
Porta la regola nel tuo prodotto
Quando aggiungi una nuova tabella di prodotto, decidi il suo possesso prima di costruire la schermata. Includi workspaceId nel suo modello di persistenza, risolvi il workspace tramite la frontiera server esistente e usa quel contesto sia nelle operazioni di lista sia di dettaglio.
I worker in background richiedono la stessa cura. Un'attività in coda deve portare abbastanza contesto fidato per caricare il record giusto, mentre le credenziali dei provider restano sul server. Un job pianificato che carica righe per semplice ID è comunque dentro il confine. Una regola di possesso coerente è più facile da mantenere di una collezione di eccezioni per route.
La superficie API appartiene alla stessa frontiera. Le chiavi API del workspace e i webhook firmati in uscita portano con sé la delimitazione del workspace — una chiave non deve mai diventare un modo per leggere attraverso i tenant, e un payload di webhook non deve mai trasportare i record di un vicino. La guida al modulo API copre la forma di quella superficie.
Domande frequenti sull'isolamento dei workspace
L'isolamento multi-tenant solleva la stessa manciata di domande in ogni progetto. Queste sono quelle che conviene chiarire presto.
- Basta la delimitazione a livello applicativo, o serve la row-level security? — La delimitazione fatta valere dal server su ogni lettura e scrittura copre il threat model della maggior parte dei SaaS. La RLS del database aggiunge difesa in profondità ma non sostituisce i controlli alla frontiera — worker e chiavi API hanno comunque bisogno della stessa regola.
- Come dovrebbe funzionare la condivisione tra workspace? — Modellala esplicitamente — un invito, una riga di appartenenza — invece di indebolire la delimitazione. «Chiunque abbia il link» dovrebbe essere una funzionalità progettata con i propri controlli, non un incidente.
- Cosa si rompe per primo nelle app multi-tenant? — I percorsi di comodo: link di ripresa, strumenti admin, worker in background che caricano record per semplice ID. Verifica ogni percorso di codice che tocca un record senza passare per la delimitazione normale.
- Come aggiungo la delimitazione a una tabella esistente? — Aggiungi
workspaceId, riempila a ritroso, falla valere in letture e scritture, poi tocca la UI. Rilascia prima i test negativi così il confine è dimostrato invece che presunto. - Le server action hanno bisogno di controlli se la pagina è protetta? — Sì. I guard di pagina nascondono schermate; non autorizzano operazioni. Ogni action risolve sessione e workspace in modo indipendente.