You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduce an explicit capability model — a set of named privileges, a role that bundles them, and a store slice the UI consults — so "what may this person do in the app" becomes one answerable question instead of an assumption spread across every menu.
Why
The app assumes every user may do everything. That assumption is baked in implicitly: the Processing menu is always live, Add Data will fetch any URL, plugins can be installed, the AI assistant is available, PostGIS connections can be added, exports always work. There is no single place that could answer "may this user publish publicly?" or "may this user install a plugin?", so there is nowhere to put the answer.
That blocks several concrete requests at once:
a deployment where students may explore and export but not install plugins or add arbitrary remote URLs;
a project opened from a view-only share link that should genuinely be view-only rather than cosmetically limited (Share links: access roles, expiry, password, and revoke #1527 makes exactly this point about maponly, which hides chrome without deciding anything);
an organization where only publishers may share publicly (the organizations issue);
a shared-update group member who may edit content but not change its sharing.
Right now each of those would be implemented as one-off UI hiding in whichever component happens to be involved, and would drift.
Proposal
1. A privilege vocabulary
Name the privileges once, in @geolibre/core, close to the store. A first cut, drawn from what the app actually gates on:
Ship four defaults — Viewer, Editor, Publisher, Administrator — as named bundles, and allow a custom role that picks privileges individually. Roles come from whichever authority is in play: the deployment config, the organization, the group, or the share link the project was opened from. When several apply, take the intersection.
3. A store slice and one hook
An ephemeral capabilities slice (same shape as the collaboration slice: never serialized into a project, never in undo history), plus a useCapability('processing:run') hook. Components disable with a reason rather than hide — a tooltip saying "your organization does not allow this" is a support ticket avoided, a missing button is one created.
4. Independent server enforcement
The slice is a mirror, not a gate. The sidecar must reject a processing call from a client that lacks processing:sidecar on its own authority, and the share host must reject a public publish from a client lacking project:share-public on its own authority. State this in the implementation as a rule: no privilege is considered enforced until a server refuses it.
The default for a plain local desktop user, with no org and no share-link role, is every privilege granted — this must not add friction for the single-user case that is the overwhelming majority.
Scope notes
Precedent to follow: the collaboration relay already computes effective edit permission identically on client and server (participantCanEdit()), and the client UI mirrors the relay. Generalize that pattern rather than inventing a parallel one.
Summary
Introduce an explicit capability model — a set of named privileges, a role that bundles them, and a store slice the UI consults — so "what may this person do in the app" becomes one answerable question instead of an assumption spread across every menu.
Why
The app assumes every user may do everything. That assumption is baked in implicitly: the Processing menu is always live, Add Data will fetch any URL, plugins can be installed, the AI assistant is available, PostGIS connections can be added, exports always work. There is no single place that could answer "may this user publish publicly?" or "may this user install a plugin?", so there is nowhere to put the answer.
That blocks several concrete requests at once:
maponly, which hides chrome without deciding anything);Right now each of those would be implemented as one-off UI hiding in whichever component happens to be involved, and would drift.
Proposal
1. A privilege vocabulary
Name the privileges once, in
@geolibre/core, close to the store. A first cut, drawn from what the app actually gates on:layers:edit·layers:add-remote·layers:add-local·processing:run·processing:sidecar·project:save·project:share·project:share-public·plugins:install·assistant:use·connections:manage·export:data·export:image·settings:manage2. Roles as bundles, plus custom roles
Ship four defaults — Viewer, Editor, Publisher, Administrator — as named bundles, and allow a custom role that picks privileges individually. Roles come from whichever authority is in play: the deployment config, the organization, the group, or the share link the project was opened from. When several apply, take the intersection.
3. A store slice and one hook
An ephemeral
capabilitiesslice (same shape as thecollaborationslice: never serialized into a project, never in undo history), plus auseCapability('processing:run')hook. Components disable with a reason rather than hide — a tooltip saying "your organization does not allow this" is a support ticket avoided, a missing button is one created.4. Independent server enforcement
The slice is a mirror, not a gate. The sidecar must reject a processing call from a client that lacks
processing:sidecaron its own authority, and the share host must reject a public publish from a client lackingproject:share-publicon its own authority. State this in the implementation as a rule: no privilege is considered enforced until a server refuses it.The default for a plain local desktop user, with no org and no share-link role, is every privilege granted — this must not add friction for the single-user case that is the overwhelming majority.
Scope notes
participantCanEdit()), and the client UI mirrors the relay. Generalize that pattern rather than inventing a parallel one.Effort
Wishlist — the model is modest, but it touches most of the UI and needs matching server-side checks to mean anything.
Part of #1665 (access control umbrella).