block / block/proto-fleet

Catalog: distinguish org-scoped vs scoped permissions for UI gates

Open
#401 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
55
Forks
16
Avg merge
1d 17h
Merged PRs (30d)
84

Description

## Problem

`useHasPermission(key)` reads from `UserInfo.permissions`, which is a flat union across all scopes (`server/internal/domain/authz/effective.go:188` — `FlatKeys`). UI gates can't tell whether the user has the permission **org-scoped** vs **scoped to a specific resource**.

Most RPCs that the UI calls with empty `ResourceContext` require the **org-scoped** form (e.g. `ListSites`, `ListBuildings`, `ListMinerStateSnapshots`, etc.). So a scoped-only user passes `useHasPermission("...:read")`, the gated component mounts, and the org-scoped RPC returns PermissionDenied — usually surfacing as a "Failed to load X" toast.

This has bitten twice already on the multi-site PR (#368):

- **site:read** — fixed inline by threading `ConnectError` code through `listSites.onError` and using PermissionDenied as an access-blocked signal in `FleetLayout`. Commit `36f8f58b`.
- **miner:read** — flagged by codex on `FleetLayout` for `CompleteSetup`. Currently mitigated by the catalog-level `useHasPermission("miner:read")` gate, but scoped-only `miner:read` users would still get the toast on Fleet tab open.

PR #408 adds a curtailment-specific case: manual curtailment preview/start/stop controls should require org-scoped `curtailment:manage`. A flat `useHasPermission("curtailment:manage")` check may not prove the server will allow the org-scoped public curtailment RPC.

## Proposed fix

Surface org-scoped checks distinctly. Options:

- **A.** Add `useHasOrgPermission(key)` that reads a separate org-only projection from `UserInfo`. Mirror server's `Has(key, emptyResourceContext)` semantics.
- **B.** Extend `useHasPermission` to take a scope: `useHasPermission("miner:read", { scope: "org" })`.
- **C.** Replace the flat union projection entirely with a `{ orgScope: Set, perResource: Map }` shape and require callers to be explicit.

Option B is probably the smallest delta — keep `UserInfo.permissions` flat, add a sibling `UserInfo.orgPermissions` (or similar), and let `useHasPermission(key, { scope: "org" })` consult it.

## Audit

Once the new API exists, sweep all `useHasPermission(...)` call sites against the corresponding RPC's required scope in `server/internal/handlers/middleware/rpc_permissions.go`. Likely candidates beyond site/miner:

- `rack:read` / `rack:manage`
- `pool:read` / `pool:manage`
- `apikey:manage`
- `schedule:read` / `schedule:manage`
- `serverlog:read`
- `activity:read`
- `curtailment:read`
- `curtailment:manage` for manual curtailment preview/start/stop controls after #408

## Acceptance

- New API exists and is documented in `store/hooks/useAuth.ts`.
- All UI gates that map to RPCs requiring org-scoped checks are migrated.
- Curtailment manual preview/start/stop UI checks org-scoped `curtailment:manage`, not only the flat permission union.
- Inline workarounds (the listSites `Code.PermissionDenied` plumbing in `useSites` / `FleetLayout`) can be unwound or simplified.

## References

- PR #368 thread (site:read fix): https://github.com/block/proto-fleet/pull/368#discussion_r3359418149
- PR #368 thread (miner:read raised, deferred to this issue): https://github.com/block/proto-fleet/pull/368#discussion_r3364755424
- Server: `server/internal/domain/authz/effective.go` (FlatKeys + Has)
- Server: `server/internal/handlers/middleware/rpc_permissions.go` (per-RPC permission map)

Contributor guide

Open the contributing guide

Research direction

Start with store/hooks/useAuth.ts and the UserInfo permission projection, then compare server/internal/domain/authz/effective.go with server/internal/handlers/middleware/rpc_permissions.go. Audit the listed useHasPermission call sites and the useSites/FleetLayout workaround. Done means org-scoped UI gates use the new API, including curtailment controls, and the documented acceptance cases are covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, typescript
Domain
authorization, backend, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.