Chi può vedere cosa
Una dashboard che chiunque abbia l'URL può leggere è uno screenshot con passaggi in più. La ragione per mantenere le dashboard in una piattaforma gestita invece che in una cartella di HTML esportato è che la piattaforma ricorda chi è autorizzato ad aprirle — e continua a ricordarlo anche dopo che la persona che le ha costruite se n'è andata.
Questa pagina è la mappa onesta di quel modello: cosa applica oggi, dove si ferma, e quali parti puoi raggiungere da codice.
I tre ruoli
Ogni appartenenza a un account porta esattamente un ruolo:
| Ruolo | Valore | Cosa significa |
|---|---|---|
| Admin | admin | Bypassa ogni controllo dei diritti di accesso. Gestisce membri, gruppi e grant. |
| Creator | creator | Può creare e modificare. Soggetto ai diritti di accesso. |
| Viewer | viewer | Sola lettura. Rifiutato su qualsiasi route che modifica. |
Ci sono tre cose da sapere su questo prima di progettarci attorno.
Il ruolo vive nella riga di appartenenza, non nel token. Il JWT non porta alcuna claim di ruolo, quindi ogni decisione sul ruolo è una lettura dal database sulla coppia account/utente. È un po' più lento e molto più difficile da falsificare.
Fallisce in chiuso. Un'appartenenza il cui ruolo salvato non è riconosciuto — un
valore legacy, un errore di battitura — si risolve in viewer, non in admin.
Admin è un override, non un rango superiore. Un admin non è "un creator con più grant": il controllo dei permessi va direttamente ad allow prima che venga consultato qualsiasi grant. Non puoi negare l'accesso a un admin da una cartella.
Una chiave API si risolve nell'utente che l'ha creata, con il ruolo di quell'utente. Una chiave generata da un admin è una chiave admin, e tutto ciò che segue si applica ad essa esattamente come si applica a quella persona nel browser. Non esiste un ambito di permessi separato e più ristretto per le chiavi, e una chiave non scade quando il ruolo di chi l'ha creata viene declassato — revoca la chiave.
Ventiquattro route sono deliberatamente chiuse alle chiavi e richiedono una vera sessione browser: fatturazione e pagamenti (16 di esse), la gestione delle chiavi API stessa, la lettura del tuo abbonamento, l'eliminazione dell'account, il cambio password, la telemetria e la firma dei token di embed. Tutto il resto accetta una chiave — incluse tutte le route di diritti di accesso, gruppi, cartelle, dashboard e dataset.
Un'eccezione deliberata. Rendere una dashboard public è
vincolato da un interruttore a livello di account, apiKeysMayPublish, che ha valore
predefinito false e può essere attivato solo da un admin in un browser. Quindi una
chiave — e di conseguenza un agente che la possiede — può costruire, pubblicare e
condividere dentro il tuo account, ma per impostazione predefinita non può essere ciò
che rende qualcosa leggibile da chiunque al mondo. Vedi
Pubblica e condividi.
Gruppi
Un gruppo è un insieme nominato di utenti dentro un account. Concedi al gruppo; l'appartenenza cambia senza toccare il grant.
Due limiti, entrambi deliberati:
- I gruppi contengono utenti, non altri gruppi. Non c'è annidamento, quindi non c'è una gerarchia di gruppi di cui tenere conto.
- Le letture sono aperte a ogni membro dell'account; le scritture sono solo per gli admin. Qualsiasi membro può elencare i gruppi e vedere chi ne fa parte. Solo un admin può creare, rinominare, eliminare o modificare l'appartenenza.
I gruppi sono presenti nell'SDK:
from vibedasher.models import GroupCreate, GroupUpdate
created = client.groups.create(GroupCreate(name="analytics", description="Read-only analysts"))
client.groups.update(created.group.id, GroupUpdate(user_ids_to_add=["<user-id>"]))
for g in client.groups.list().groups:
print(g.name, g.user_count)
const created = await client.groups.create({ name: 'analytics', description: 'Read-only analysts' });
await client.groups.update(created.group.id, { userIdsToAdd: ['<user-id>'] });
const { groups } = await client.groups.list();
Diritti di accesso — il grant è sulla cartella
Questa è la frase più importante di questa pagina, e quella che più probabilmente ti sorprenderà:
Un grant nomina una cartella, mai una singola dashboard o un singolo dataset.
Un diritto di accesso è una riga della forma:
(account, folder, resource type, permission, principal) → allow | deny
- tipo di risorsa —
datasetoviz. Sono i due che un grant può nominare; vedi Cosa può nominare un grant per cosa governa tutto il resto. - permesso —
view,edit,delete,create. - principal — un utente o un gruppo.
- allow / deny — i due tipi di accesso.
Una dashboard è coperta da un grant perché si trova nella cartella a cui è stato concesso. Spostala in un'altra cartella e sarà invece governata dai grant di quella cartella. Non c'è alcun grant per singola dashboard di cui perdere traccia, il che è la metà positiva di questo design; l'altra metà è che la cartella è la più piccola unità che puoi concedere, quindi una cartella che condividi è una cartella che condividi interamente.
Questo non è un tetto a quanto stretta può essere una condivisione
La frase sopra riguarda la concessione, non la condivisione. Una cartella è la cosa più piccola che puoi nominare in una concessione — e sei tu a decidere cosa c'è in una cartella. Per condividere esattamente una dashboard, dalle una cartella tutta sua: sono tre chiamate che il tuo agente ha già:
create_folder(name="Q3 board pack")
move_to_folder(resource_type="viz", resource_id=<vizId>, folder_id=<newFolderId>)
grant_access(folder_id=<newFolderId>, resource_type="viz",
principal_id=<userId>, principal_type="user", permissions=["view"])
resource_type è dataset o viz nello spostamento, principal_type è user o group, e
access_type vale allow per default — deny è l'altro valore, e batte ogni allow.
Queste tre sono strumenti MCP, e l'MCP è la via supportata per farlo. Le rotte di
access-rights sono deliberatamente fuori dall'SDK pubblicato e dal riferimento API — sono
impiantistica di controllo accessi di cui non abbiamo fissato la forma, quindi non esiste un
metodo di client generato per esse e non va usato nessuno. create_folder è quella delle tre
con un gemello SDK, client.folders.create.
Spostare una dashboard cambia quali concessioni la governano e nient'altro — la dashboard mantiene il suo id, e il suo link non è toccato. Quindi «condividere per singola dashboard è impossibile» è la conclusione sbagliata da trarre dalla regola della cartella: sono tre chiamate, e la cartella è il modo di esprimerla.
Le due metà di una concessione su dashboard
Una dashboard e i dati sottostanti sono governati da tipi di risorsa distinti, quindi una
condivisione che ne nomina solo uno è fatta a metà. viz:view permette a qualcuno di aprire
la dashboard; dataset:view è ciò che permette alle sue tile di restituire numeri. La
sezione seguente spiega perché questo non è un dettaglio rinviabile.
Come viene effettivamente presa una decisione
In ordine. La prima regola che corrisponde vince:
- Admin → allow. Incondizionatamente, prima che venga letto qualsiasi grant.
- Il principal interno
system→ allow. Questa è l'identità worker della piattaforma stessa (job di aggiornamento, esecuzioni pianificate); non è un account in cui puoi accedere. - Proprietà → allow. L'utente proprietario della riga può sempre leggerla e modificarla.
- Altrimenti, decidono i grant della cartella, e un singolo deny batte ogni allow.
Vale la pena precisare deny-wins, perché è più ristretto di quanto sembri. Un deny rimuove una cartella dall'insieme che ti era stato concesso — non sottrae dalle regole 1–3. Non puoi negare l'accesso a un admin, e non puoi negare a qualcuno una riga di cui è proprietario. Se il tuo modello mentale è "deny è l'arresto di emergenza", non lo è: è una sottrazione solo all'interno dello strato dei grant.
Due comportamenti che ti morderanno
I grant non si propagano nelle sottocartelle. Il controllo è un test piatto di
appartenenza a un insieme sul folder_id proprio della risorsa. Le cartelle hanno
effettivamente una relazione genitore/figlio, e l'interfaccia la disegna come un albero —
ma nessun codice di autorizzazione la percorre. Un grant su Marketing non raggiunge
Marketing / Q3. Concedi ogni cartella che intendi concedere.
Un deny su create prima non faceva nulla — corretto il 2026-09-08. Il controllo
del permesso di creazione leggeva solo le righe allow; il diritto di bootstrap di un
Creator a creare nella radice dell'account o in una cartella di cui è proprietario, e una
riga allow esplicita altrove, bypassavano entrambi completamente qualsiasi deny. Una
riga create / deny esplicita di un admin ora sovrasta entrambi — la stessa regola
"un singolo deny batte ogni allow" che il resto di questa pagina descrive per
view/edit/delete. Gli admin restano non negabili, coerentemente con ogni altro permesso
qui.
Chi può modificare un grant
Un admin dell'account, o il proprietario della cartella su cui si trova il grant. La lettura dei grant è aperta a qualsiasi membro dell'account: lo stato di condivisione viene visualizzato nell'app su pagine che qualsiasi membro può aprire, quindi la lettura è deliberatamente non vincolata. Considera l'elenco dei grant stesso come informazione visibile a tutto l'account.
Cosa può nominare un grant
Due tipi di risorsa sono assegnabili: viz (dashboard) e dataset. Costruisci il tuo
modello di governance con questi due, tenuti in cartelle: è la forma che il modello applica
ed è la sola che la finestra di dialogo offre. POST /v1/access-rights rifiuta qualunque
altro tipo di risorsa con un 400, nel momento in cui lo scrivi anziché nel momento in cui
qualcuno ci conta.
Tre cose sono governate da qualcosa che non è un grant, e sapere quale da cosa è tutto il senso di questa sezione:
| Risorsa | Cosa la governa |
|---|---|
| Cartella | L'appartenenza all'account per le letture; il ruolo per le mutazioni. Una cartella è il contenitore che un grant nomina, non una cosa contro cui un grant si scrive. |
| Pianificazione di aggiornamento | La proprietà, più il confine dell'account. |
| Domanda | Solo il proprietario. Una domanda non può essere condivisa con nessun meccanismo. |
| Attività | L'ACL della dashboard a cui appartiene. |
| Connessione | Il suo proprietario e gli amministratori dell'account. Qualunque Creator può crearne una. |
Un elenco vuoto ti dice di quale genere di vuoto si tratta.
GET /v1/access-rights/{folder}/{type} porta un flag enforced, quindi total: 0 per un
tipo non assegnabile significa nessun diritto di questo tipo può esistere in alcun luogo,
mentre total: 0 per viz significa nessuno ne possiede uno. Le righe scritte prima del
rifiuto restano elencate, e restano rimovibili, con enforced: false accanto.
Condividere una dashboard senza condividerne i dati non funziona
È la domanda a cui il modello non risponde come la maggior parte si aspetta, quindi merita
una frase netta: viz:view da solo non dà a nessuno una dashboard funzionante.
I riquadri di una dashboard prendono i loro numeri con POST /v1/query, e quella rotta
autorizza ogni dataset coinvolto tramite l'ACL dei dataset, in modalità
nega-anziché-scartare: un solo dataset che il lettore non può leggere respinge l'intera query
con 403 query_dataset_forbidden. Non viene mai eseguita sul sottoinsieme che è invece
consentito. Quindi «fai vedere allo stagista questa dashboard ma non i dati dietro» produce
una dashboard i cui riquadri falliscono, non una dashboard con i numeri nascosti.
Concedi anche dataset:view sulla cartella che contiene i dataset — viz:view e
dataset:view vanno in coppia, e una concessione che porta solo la prima produce una
dashboard in cui ogni tile restituisce 403. Se i dataset non devono essere condivisi in
blocco, dagli una cartella loro come fa la ricetta sopra. Oppure, se l'obiettivo è
davvero mostrare un risultato senza consegnare i dati sottostanti, pubblica la dashboard o
incorporala. Entrambe queste corsie girano sotto l'identità di servizio della piattaforma e
non consultano mai questa ACL: è esattamente per questo che possono mostrare un numero a
qualcuno che non può interrogarlo.
Verificare il proprio lavoro
Ogni campo qui sopra descrive una regola; nessuno ti dice con cosa finisce una persona
specifica, e questo non si legge dall'elenco delle concessioni. I diritti si compongono —
appartenenze ai gruppi, un deny che batte ogni allow compreso uno ereditato da un gruppo,
la separazione per tipo di risorsa e il controllo di ruolo separato sulle mutazioni — in un
esito che nessun singolo elenco mostra. Il deny ereditato è il caso che conta: non compare
mai nell'elenco degli allow che leggi, quindi «perché questa persona non lo vede» non ha
risposta dal solo elenco.
GET /v1/access-rights/{folder}/effective?userId= lo risolve. Per ogni coppia (tipo di
risorsa, permesso) restituisce cosa decide l'ACL, quale regola ha deciso, se il ruolo
della persona lo consente, se il tipo è applicato, il prodotto di tutto ciò — e la riga di
diritto d'accesso che ha prodotto il verdetto, cioè l'id da revocare quando la risposta è un
deny. Il tuo agente ha la stessa cosa come describe_effective_access.
Risponde solo alla domanda sull'ACL, e lo dichiara in un elenco caveats presente in ogni
risposta: non può vedere le righe che la persona possiede (la proprietà è per riga e batte
l'ACL della cartella), non sa nulla delle corsie della dashboard pubblica e dell'embed e —
come sopra — una dashboard funzionante ha bisogno anche di diritti sui suoi dataset. Puoi
sempre chiedere di te stesso; chiedere di qualcun altro richiede la stessa posizione che
serve per cambiare l'ACL: un admin dell'account o il proprietario della cartella.
Raggiungere tutto questo da codice
| Superficie | Negli SDK? |
|---|---|
| Gruppi (creazione, elenco, modifica, appartenenza) | Sì — client.groups.* |
| Utenti (elenco, modifica, ruolo, appartenenza al gruppo) | Sì — client.users.* |
| Account | Sì — client.accounts.* |
| Diritti di accesso (i grant stessi) | No |
I diritti di accesso sono esclusi dalla superficie SDK generata, quindi non esiste
client.accessRights né una voce per essi nel
Riferimento API. Le route esistono e accettano una
chiave API — semplicemente non fanno parte del contratto client pubblicato e versionato,
e preferiamo dirtelo piuttosto che lasciarti costruire su una forma a cui non ci siamo
impegnati. Gestisci i grant nell'app, oppure dal tuo agente: il server MCP espone
strumenti per grant, revoke e list-access-rights che chiamano le stesse route sotto la
tua identità, ed ereditano la stessa regola admin-o-proprietario-della-cartella.
Filtraggio per riga: la corsia dell'embed, e solo quella
La sicurezza per riga è reale in Vibedasher, ma vive in un solo posto: le dashboard incorporate dietro un token firmato.
Quando il tuo server genera un token di embed, può allegare filtri al payload del
token. Su ogni query la piattaforma riscrive ogni riferimento a un dataset in una
subquery filtrata, quindi il visualizzatore non può fisicamente leggere righe al di
fuori della propria porzione. I filtri provengono esclusivamente dal token
verificato — mai dal corpo della richiesta — quindi un visualizzatore non può ampliare
il proprio ambito modificando una richiesta. Un filtro che nomina una colonna che non
esiste in nessuno schema di dataset viene rifiutato con un 403 invece di essere
ignorato silenziosamente, perché un filtro che silenziosamente non fa nulla è una falla.
Tre limiti da tenere in conto:
- Si applica solo alla corsia dell'embed. La corsia di query dell'SDK non ha alcuna iniezione obbligatoria di filtri: una chiave API è un chiamante lato server fidato, e limitare i propri utenti è compito tuo, non della piattaforma.
- L'ACL della cartella non è per riga. Decide quali dashboard e dataset esistono per te, non quali righe al loro interno puoi leggere. Sono domande diverse e Vibedasher le risolve con meccanismi diversi.
- Una dashboard che interroga nel browser non può essere ristretta affatto, ed è una
scelta di progettazione. È la decisione da prendere prima di costruire, non dopo. Una
dashboard auto-ospitata sulla corsia senza credenziali — l'albero espulso il cui
dataLaneèwasm, o qualunque cosa tu abbia costruito da solo sopra un estratto Parquet o JSON — scarica i dati ed esegue ogni query nel browser del visitatore. L'estratto è indirizzato per dataset e istantanea, non per query, quindi contiene tutte le righe di quel dataset, e ogni visitatore riceve lo stesso file. Non resta alcun server lungo il percorso per restringerlo: un filtro scritto nella pagina cambia ciò che viene disegnato, mai ciò che è stato consegnato.
Tutto il fascino di quella corsia sta nel fatto che non c'è alcuna chiave nel bundle, nessun token da generare e nessun backend tuo da far girare — e il prezzo è esattamente la stessa proprietà vista dall'altro lato: nulla può dare a un visualizzatore meno righe che a un altro. Non è una lacuna che verrà colmata; è ciò che significa mettere i dati nel browser.
Se alcuni visualizzatori devono vedere meno righe, tieni la query su un server. Due configurazioni lo fanno, ed entrambe tengono la credenziale fuori dalla pagina: il tuo backend genera un token di embed di breve durata che porta il filtro (la piattaforma aggiunge quel predicato a ogni query che autorizza, prima di qualsiasi join o aggregazione, così la pagina non può allargarlo), oppure codice server fidato interroga con una chiave API e restituisce righe che hai già ristretto. Scegliere prima la corsia veloce e poi "restringerla" con un filtro nella pagina produce una dashboard che sembra giusta, non solleva alcun errore, e consegna tutte le righe.
Vedi Incorporare per come un token viene generato e presentato.
Autenticazione a due fattori
Email + password è l'unico ingresso in Vibedasher che poggia su un solo segreto. Gli accessi
con Google e LinkedIn portano il secondo fattore del loro provider; le chiavi API e il token CLI
di vibedasher login non sono affatto accessi. Il secondo fattore è quindi opt-in, per
utente, solo sull'accesso con password: un codice TOTP a sei cifre da qualsiasi app di
autenticazione (Google Authenticator, 1Password, Authy…), attivato da Impostazioni →
Autenticazione a due fattori scansionando un codice QR e confermando un codice. L'attivazione
consegna otto codici di recupero monouso, mostrati una sola volta: un telefono perso accede
con uno di questi.
Cosa cambia all'accesso: dopo una password corretta il browser non riceve una sessione,
riceve una sfida di cinque minuti a scopo unico che solo il passaggio del codice può riscattare.
L'API non vede mai quella sfida come una credenziale: porta un'audience che l'API rifiuta in
partenza, quindi non può essere riprodotta contro api.*.vibedasher.com né infilata nel cookie
di sessione. Cinque codici errati bloccano la sfida per quindici minuti; un codice vale una sola
volta.
Tre limiti da conoscere:
- L'attivazione è personale. Ogni membro la attiva per il proprio account; non è qualcosa che un amministratore accende per tutti.
- Gli accessi OAuth non ricevono mai la sfida, per scelta: aggiungere il nostro fattore sopra quello del provider è attrito senza sicurezza.
- Lo sblocco è un'azione da amministratore. Un membro senza telefono e senza codice di recupero si rivolge a un amministratore dell'account, che reimposta il suo secondo fattore dalla pagina del membro (l'azione viene registrata). Un proprietario che è l'unico amministratore, senza codice di recupero, passa dal supporto.
Una configurazione pratica
Per un team di dieci persone con un paio di dataset sensibili:
- Rendi admin una o due persone che amministrano l'account. Ricorda che sono ingovernabili dai grant — è il punto del ruolo, ed è la ragione per mantenere l'elenco breve.
- Chiunque costruisca è un creator; chiunque legga soltanto è un viewer. Il ruolo viewer è un rifiuto netto su ogni modifica, quindi è un confine reale, non una convenzione dell'interfaccia.
- Metti dashboard e dataset in cartelle che rispecchino come vuoi condividerli — una cartella per pubblico, non per argomento. La cartella è il grant.
- Concedi ai gruppi, non alle persone. Il grant sopravvive così a chi entra o esce.
- Non creare cartelle annidate aspettandoti che il grant del genitore le raggiunga. Concedi ognuna.
- Se una dashboard deve arrivare a persone al di fuori dell'account, pubblicala e incorporala con un token firmato che porti i loro filtri per riga — non dare loro un account.