Say where the one-gate rule applies, and record why allows stays open
This commit is contained in:
@@ -186,7 +186,9 @@ Revisit only if the stated reason stops holding.
|
||||
node names exactly one of `public`, `session`, `permission` — discovery refuses none, two, and a
|
||||
flag spelled anything but `true`, so a forgotten gate fails the boot rather than publishing a page.
|
||||
`src/auth/gate.ts` is the one home of the rule the plugin router, the host's own route table and
|
||||
the menu all read. `session` exists because a plugin whose data is
|
||||
the menu all read. Exactly-one-gate is a discovery-time rule on manifests, not a runtime invariant:
|
||||
`allows({}, user)` stays open **by design**, because the central override's `groups` builds header
|
||||
nodes that carry no gate. Making `allows` fail closed would hide every operator-grouped section. `session` exists because a plugin whose data is
|
||||
the visitor's own — their upstream account, their own tokens — has no distinction a permission could
|
||||
name; the alternative, granting every newly registered user a permission, couples the identity
|
||||
lifecycle to a Keto write that nothing retries when it fails. A page scoped to "mine" joins on
|
||||
|
||||
@@ -920,9 +920,10 @@ The menu is **driven entirely by config** and assembled from two sources:
|
||||
export default defineMenu({ branding: { name: "Acme Ops" }, override: { hide: ["teams"] } });
|
||||
```
|
||||
|
||||
Every nav item names one gate — a `permission`, **`public: true`** (everyone) or **`session: true`**
|
||||
(anyone signed in); the rendered tree is **filtered per user** from the session JWT (no per-request
|
||||
authz call), so the menu only shows what that person can reach.
|
||||
Every nav node a **plugin** declares names one gate — a `permission`, **`public: true`** (everyone)
|
||||
or **`session: true`** (anyone signed in); a header this override groups takes none, and shows
|
||||
whenever a child does. The rendered tree is **filtered per user** from the session JWT (no
|
||||
per-request authz call), so the menu only shows what that person can reach.
|
||||
Branding (name, logo, default theme) renders in the app shell.
|
||||
|
||||
**One menu, one shell, everywhere.** A single menu (`src/ui/chrome.ts` `buildPluginChrome`) renders
|
||||
|
||||
@@ -35,10 +35,9 @@ export function actionForMethod(method: string): AdminAction {
|
||||
return verb === "GET" || verb === "HEAD" ? "read" : "write";
|
||||
}
|
||||
|
||||
// The plugin's nav fragment: an ungated "Admin" header + its three screens, each gated on its own
|
||||
// read permission. The header carries no `permission` because a user may hold one screen's and not
|
||||
// another's; composeNav drops a header left with no visible children, so a user holding none of the
|
||||
// three never sees the section. The host current-marks the active item — no `current`/`open` here.
|
||||
// The plugin's nav fragment: the "Admin" header + its four screens, each gated on its own read
|
||||
// permission. composeNav drops a header left with no visible children, so a user holding none of
|
||||
// them never sees the section. The host current-marks the active item — no `current`/`open` here.
|
||||
export const ADMIN_NAV: NavNode = {
|
||||
children: [
|
||||
{ href: ADMIN_USERS_BASE, icon: "i-users", id: "users", label: "admin.nav.users", permission: permissionName("users", "read") },
|
||||
|
||||
Reference in New Issue
Block a user