Skip to content

Alternative to #3041: gate UI on my/routes (allowed-route manifest)#3044

Merged
dvdstelt merged 15 commits into
authz-resource-scopesfrom
route-based-ui-gating
Jun 26, 2026
Merged

Alternative to #3041: gate UI on my/routes (allowed-route manifest)#3044
dvdstelt merged 15 commits into
authz-resource-scopesfrom
route-based-ui-gating

Conversation

@ramonsmits

@ramonsmits ramonsmits commented Jun 26, 2026

Copy link
Copy Markdown
Member

Alternative to:

Instead of gating the UI on ServiceControl's internal permission vocabulary (can("error:messages:retry") from my/permissions/all), this gates on the API routes the token may call (canCall(ApiRoutes.retryMessage) from GET /api/my/routes — ServiceControl PR Particular/ServiceControl#5556). ServicePulse couples only to the public HTTP routes it already calls, so a permission-model change on the server can't break the UI.

  • Central registry (apiRoutes.ts, capability → {method, path}) + structural matcher (param names collapsed to {}; couples to method+path only).
  • Manifest fetched from Primary + Monitoring and merged into a scope-ready Map; fail-open per instance (older ServiceControl 404 → UI unchanged).
  • View-model owns the gate: the resource-owning store/component computes the capability and exposes it; templates bind. The resource flows to canCall(entry, resource?) — the (dormant) seam for future per-resource scope.
  • Optimistic UI, authoritative server: a 403 surfaces ServiceControl's reason via a toast rather than failing silently.
  • Old permission-string model + dormant scope groundwork removed; User Permissions page reworked into a route-derived capability list.

Depends on ServiceControl my/routes (Particular/ServiceControl#5556). Targets the #3041 branch so the delta is reviewable in isolation.

Compare #3041 branch vs this branch: authz-resource-scopes...route-based-ui-gating

…-open tests

- fetchInstance() now validates Array.isArray(json) before using the parsed body;
  a non-array 200 response (error envelope, null, {}) returns null like a network
  failure instead of propagating into load() and rejecting refresh()
- Add test: both instances return non-array 200 → loaded=false
- Add test: both instances throw network error → loaded=false (global fail-open)
- Fix arrange() in useAllowedRoutes.spec.ts to set loadAttempted=true so future
  ready-computed tests work correctly by default
Auto-fix prettier in apiRoutes.ts, routeMatching.ts, AllowedRoutesStore.ts, AllowedRoutesStore.spec.ts. Remove async from mock json() helpers (require-await). Add eslint-disable-next-line for intentional future-scope _resource param in useAllowedRoutes.ts.
Each store now calls useAllowedRoutes/canCall and exposes a typed computed
getter (canRetry, canEdit, canDelete, canRestore, canManageNotifications,
canTestNotifications, canManageRedirects, canDeleteEndpointInstance,
canDeleteMonitoredEndpoint). Components bind to store.canX via storeToRefs;
the old can("...") calls and usePermissions imports are removed from all
migrated files. Test updated from PermissionsStore to AllowedRoutesStore.
@ramonsmits ramonsmits changed the base branch from authz-resource-scopes to authorization June 26, 2026 04:54
ServiceControl serializes its HTTP API in snake_case, so the manifest field is
url_template, not urlTemplate. Reading the camelCase name yielded undefined keys
so gating never matched against a live server. Tests now use the real wire shape.
@ramonsmits ramonsmits changed the base branch from authorization to authz-resource-scopes June 26, 2026 09:01
@dvdstelt dvdstelt marked this pull request as ready for review June 26, 2026 09:50
@dvdstelt dvdstelt merged commit 5ba11b5 into authz-resource-scopes Jun 26, 2026
5 checks passed
@dvdstelt dvdstelt deleted the route-based-ui-gating branch June 26, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants