Ruoli e scope
Ogni richiesta a un backend Rebase pone una sola domanda: questo chiamante può farlo?
La risposta è uno scope, una stringa nella forma resource:action: data:read,
users:write, cron:read. La sessione di una persona, una chiave API, un token MCP e un
ruolo possiedono tutti degli scope, e usano tutti le stesse stringhe. Una concessione si legge allo stesso modo
su una chiave, su un ruolo e su una schermata di consenso.
Due piani
Sezione intitolata “Due piani”Gli scope si dividono in due piani, e una persona li possiede in modo diverso.
Il piano dati è data:*, storage:* e functions:invoke. Ogni
persona autenticata lo possiede per intero. Cosa una persona può fare con una riga lo decidono
le regole di sicurezza della collezione, riga per riga,
e cosa può fare con un file le policy dello storage.
Per una persona non lo decide mai uno scope. Su una chiave o un token, gli scope del piano dati
restringono: una chiave che possiede solo data:read:posts legge posts e nient’altro,
qualunque cosa consentano le regole.
Il piano di amministrazione è tutto il resto: utenti, schema, database, backup,
cron, log e chiavi. Nessuno lo possiede implicitamente. Il ruolo integrato admin lo possiede
per intero. Ogni altro ruolo possiede ciò che l’app dichiara per esso.
Gli scope
Sezione intitolata “Gli scope”| Scope | Piano | Target | Cosa consente |
|---|---|---|---|
data:read |
dati | collezione | Leggere righe, attraverso la sicurezza a livello di riga del chiamante |
data:write |
dati | collezione | Creare e aggiornare righe, attraverso la sicurezza a livello di riga del chiamante |
data:delete |
dati | collezione | Eliminare righe, attraverso la sicurezza a livello di riga del chiamante |
storage:read |
dati | sorgente di storage | Elencare e scaricare file |
storage:write |
dati | sorgente di storage | Caricare file e creare cartelle |
storage:delete |
dati | sorgente di storage | Eliminare file |
functions:invoke |
dati | funzione | Chiamare funzioni personalizzate. Una funzione può fare tutto ciò che fa il suo codice |
users:read |
admin | — | Elencare gli account e i loro ruoli |
users:write |
admin | — | Creare, modificare ed eliminare account, reimpostare password e secondi fattori, assegnare ruoli fino a quelli di chi lo possiede |
schema:read |
admin | — | Leggere lo schema delle collezioni, pianificare modifiche allo schema, eseguire l’audit RLS, leggere la documentazione privata dell’API |
schema:write |
admin | — | Applicare modifiche allo schema: modifica i file delle collezioni e altera il database |
database:read |
admin | — | Elencare database, tabelle, ruoli Postgres e branch |
database:write |
admin | — | Eseguire SQL come proprietario del database, al di fuori della sicurezza a livello di riga, e creare o eliminare branch |
backups:read |
admin | — | Elencare e scaricare i backup: ogni riga, al di fuori della sicurezza a livello di riga |
cron:read |
admin | — | Elencare i cron job e leggere la cronologia delle loro esecuzioni |
cron:write |
admin | — | Avviare i cron job e attivarli o disattivarli |
logs:read |
admin | — | Leggere i log del server |
keys:read |
admin | — | Elencare le chiavi di servizio del progetto. Mai concedibile a una chiave |
keys:write |
admin | — | Creare, modificare e revocare chiavi di servizio. Mai concedibile a una chiave |
GET /api/auth/scopes restituisce questo elenco per il backend in esecuzione, con in più gli
scope propri dell’app, oltre agli scope che il chiamante possiede. Qualsiasi chiamante autenticato
può leggerlo:
const { scopes, held } = await client.personalKeys.listScopes();// scopes: [{ scope: "data:read", label: "Read data", plane: "data", target: "collection", … }, …]// held: ["data:read", "data:write", …]Uno scope del piano dati può essere ristretto a un solo target, dopo un secondo due punti:
data:read:postslegge solo la collezioneposts. Il target è lo slug di una collezione.storage:write:avatarscarica solo sulla sorgente di storageavatars. L’id della sorgente predefinita è(default):storage:read:(default).functions:invoke:exportchiama solo la funzioneexport.
Lo scope semplice copre ogni target. Uno scope ristretto copre il proprio target e
nient’altro. Non risponde mai a una domanda su tutti i target: una chiave che possiede
data:read:posts non può elencare ogni collezione.
Gli scope del piano di amministrazione non accettano target. Uno scope dell’app ne accetta uno quando dichiara un
target, come più avanti.
Dichiarare i ruoli
Sezione intitolata “Dichiarare i ruoli”I ruoli si dichiarano sulla collezione users, sotto auth.roles. Un ruolo è un nome
che il database vede, a cui i criteri RLS possono corrispondere, più un elenco di scope del piano di amministrazione
e dell’app.
import { defineCollection } from "@rebasepro/cms-types";
export const usersCollection = defineCollection({ slug: "users", name: "Users", table: "users", auth: { enabled: true, roles: { support: { name: "Support", description: "Helps people back into their accounts.", scopes: ["users:read", "users:write", "logs:read"] }, developer: { name: "Developer", scopes: ["schema:read", "database:read", "logs:read", "cron:read"] } } }, properties: { email: { name: "Email", type: "string" }, roles: { name: "Roles", type: "array", columnType: "text[]", of: { name: "Role", type: "string", enum: { admin: "Admin", support: "Support", developer: "Developer", editor: "Editor" } } } }});Una persona possiede un ruolo quando la sua colonna roles lo elenca. Assegnalo nel pannello
di amministrazione, oppure con PUT /api/admin/users/:uid.
L’avvio rifiuta una dichiarazione che sembrerebbe una concessione che non è:
adminnon può essere dichiarato. È integrato e possiede ogni scope.- Un ruolo non può elencare uno scope del piano dati. Ogni persona possiede già il piano
dati.
data:writesu un ruolo non concederebbe nulla e sembrerebbe concedere qualcosa. Cosa un ruolo può fare con le righe va nellesecurityRulesdella collezione. - Ogni scope deve esistere. Un nome sconosciuto fa fallire l’avvio ed elenca quelli validi.
Un ruolo che non dichiari è comunque un ruolo. editor qui sopra non ha una voce, quindi
non possiede alcuno scope del piano di amministrazione, e un criterio RLS può comunque corrispondergli.
defaultRole, il ruolo che riceve ogni nuovo utente registrato, non può essere admin né un
ruolo dichiarato che possiede uno scope del piano di amministrazione. Uno sconosciuto che si registra non deve
possedere nulla che gestisca il progetto. L’avvio lo rifiuta.
GET /api/admin/roles elenca admin e ogni ruolo dichiarato con i suoi scope.
Richiede users:read:
const { roles } = await client.admin.listRoles();// [{ id: "admin", name: "Admin", scopes: [...], builtIn: true },// { id: "support", name: "Support", scopes: ["users:read", "users:write", "logs:read"], builtIn: false }, …]Scope dell’app
Sezione intitolata “Scope dell’app”Un’app può dare un nome di scope alle proprie operazioni, sotto auth.scopes:
import { defineCollection } from "@rebasepro/cms-types";
export const usersCollection = defineCollection({ slug: "users", name: "Users", table: "users", auth: { enabled: true, scopes: { "project:deploy": { label: "Deploy projects", description: "Starts a deploy of one project.", target: "project" } } }, properties: { email: { name: "Email", type: "string" } }});Il nome è resource:action, in minuscolo, senza target. Non può riutilizzare una
risorsa integrata: data, storage, functions, users, schema,
database, backups, cron, logs e keys sono già in uso. label è obbligatorio,
perché è ciò che una persona legge quando concede lo scope. target indica cosa
rappresenta un target, così una chiave può possedere project:deploy:p1.
Ogni persona autenticata possiede ogni scope dell’app. Come per il piano dati, è il codice dietro lo scope a decidere se questa persona può agire. Lo scope esiste perché una chiave possa essere ristretta a quella sola azione. Anche un ruolo può elencare scope dell’app.
Verificane uno in una funzione personalizzata con requireScope:
import { defineFunction, requireAuth, requireScope, getUserId } from "@rebasepro/server/functions";
export default defineFunction((app) => { app.post( "/:project", requireAuth, requireScope("project:deploy", c => c.req.param("project")), async (c) => { // A person always passes requireScope. Decide here whether // this person may deploy this project. return c.json({ project: c.req.param("project"), by: getUserId(c) }); } );});Una chiave che possiede project:deploy:p1 passa per p1 e riceve 403 SCOPE_MISSING
per qualsiasi altro progetto. Ha anche bisogno di functions:invoke, oppure di
functions:invoke:<name> per questa funzione, per raggiungere la funzione.
hasScope(c, scope, target) e getScopes(c) rispondono alla stessa domanda dentro un
handler.
Cosa significa admin
Sezione intitolata “Cosa significa admin”admin è l’unico ruolo integrato, ed è più di un elenco di scope:
- Possiede ogni scope del piano di amministrazione,
keys:*compresi. - È un ruolo che il database vede. I criteri predefiniti che Rebase aggiunge a ogni
collezione lo ammettono, quindi un admin legge e scrive ogni riga di una collezione
che li mantiene. Una collezione con
disableDefaultPolicies: trueli elimina. - Solo un admin può concedere
admin. Un ruolo che elenca ogni scope del piano di amministrazione non è comunqueadmin: non può concedereadminad altri, e i criteri predefiniti non lo ammettono.
requireAdmin verifica il ruolo. Preferisci requireScope per tutto ciò che un ruolo
più ristretto o una chiave deve poter fare.
Nessuno concede più di quanto possiede
Sezione intitolata “Nessuno concede più di quanto possiede”Una sola regola copre ogni via che distribuisce accesso: nulla viene concesso con più di quanto possiede chi lo concede.
Per le chiavi:
- Gli scope di una chiave devono rientrare in quelli del suo creatore. Altrimenti
403 SCOPE_EXCEEDS_CREATOR. - I ruoli RLS di una chiave di servizio devono essere ruoli che il suo creatore possiede, a meno che il creatore
non sia un admin. Altrimenti
403 ROLE_EXCEEDS_CREATOR. keys:readekeys:writenon vanno mai su una chiave. Una chiave che gestisce le chiavi potrebbe creare il proprio successore.400 KEY_MANAGEMENT_SCOPE.
Per gli account, chi possiede users:write:
- non può modificare, reimpostare o eliminare un account che possiede un ruolo o uno scope che lui non possiede:
403 ACCOUNT_OUTRANKS_CALLER. Senza questa regola, un ruolo di supporto potrebbe reimpostare la password di un admin e accedere come lui. - non può concedere ruoli che possiedono più di quanto possiede lui:
403 ROLE_EXCEEDS_CALLER.
Come ogni credenziale possiede gli scope
Sezione intitolata “Come ogni credenziale possiede gli scope”| Credenziale | Agisce come | Possiede |
|---|---|---|
| La sessione di una persona | la persona | il piano dati, ogni scope dell’app e gli scope dei suoi ruoli. Un admin possiede tutto |
Chiave di servizio rk_live_… |
api-key:<id>, con i ruoli RLS service più i propri roles |
esattamente i suoi scope |
Chiave personale rk_live_… |
il suo proprietario, con i ruoli del proprietario così come sono a ogni richiesta | i suoi scope, ridotti a ciò che il proprietario possiede ora |
| Token MCP | la persona che l’ha collegato | data:read, data:write, data:delete come concessi, eventualmente per collezione |
REBASE_SERVICE_KEY |
service, con il ruolo admin |
tutto |
Una chiave o un token non aggira mai la sicurezza a livello di riga. I suoi scope sono un limite, e i criteri del database per l’identità con cui agisce sono un altro.
Quando manca uno scope
Sezione intitolata “Quando manca uno scope”La risposta è 403 SCOPE_MISSING, e details.requiredScope nomina lo scope,
con il suo target quando ce n’è uno:
{ "error": { "message": "This API key does not hold the \"cron:write\" scope. Create a key that includes it.", "code": "SCOPE_MISSING", "details": { "requiredScope": "cron:write" } }}Per una persona, la soluzione è un ruolo che elenca lo scope. Per una chiave, è una nuova chiave che lo possiede.
Passaggi successivi
Sezione intitolata “Passaggi successivi”- Chiavi API: chiavi di servizio, chiavi personali e le regole di creazione
- Regole di sicurezza (RLS): cosa può fare una persona con ogni riga
- Indice degli endpoint: lo scope richiesto da ogni route
- Codici di errore: ogni rifiuto descritto sopra