Skip to content

refactor(auth)!: remove ROLE_PERMISSIONS, hasPermission() entirely (3.0.0) - #3

Merged
zumbrunn merged 7 commits into
mainfrom
refactor/drop-role-permissions-3.0.0
Aug 31, 2026
Merged

refactor(auth)!: remove ROLE_PERMISSIONS, hasPermission() entirely (3.0.0)#3
zumbrunn merged 7 commits into
mainfrom
refactor/drop-role-permissions-3.0.0

Conversation

@zumbrunn

Copy link
Copy Markdown
Member

dec-identity-unification Phase 5c/6, finally closed. This was supposed to be fully replaced by the polizy authz system when it was first introduced and instead survived as a parallel, hand-maintained table kept in sync with @dune/core's canonical actionToRelations schema by convention only, not by anything enforcing it.

  • Deleted ROLE_PERMISSIONS (types.ts) and AuthMiddleware.hasPermission() (auth/middleware.ts) entirely, including the public mod.ts re-export.
  • checkPermission()/requirePermission() (routes/api/_utils.ts) now fail closed (deny) when authz is undefined — an in-process object construction failing at startup, essentially never hit in practice — instead of silently degrading to the removed table.
  • routes/_layout.tsx's sidebar nav filtering, the one place that used ROLE_PERMISSIONS unconditionally rather than as a fallback, now reads a real permission set computed via authz.check() once per request (routes/_middleware.ts's new computeNavPermissions(), threaded through AdminState.permissions) instead of a table that could silently drift from what a route's own check would actually decide.

Blast-radius check, not just an assumption: searched @dune/plugin-inline-edit, @dune/plugin-meilisearch, @dune/plugin-orama, @dune/plugin-pdf for direct calls to the removed surface — none found. @dune/plugin-inline-edit's own auth.hasPermission(permission) call goes through @dune/core's published, synchronous HookContext.auth.hasPermission() hook API, whose contract is unchanged (companion @dune/core PR: it now sources its answer from roleHasPermission(), a synchronous read of @dune/core's own canonical schema, instead of this package's table). See duneorg/dune#15.

227 tests pass (5 new: computeNavPermissions()); deno check/deno lint clean.

Depends on / pairs with duneorg/dune#15 — merge that first or alongside this one.

….0.0)

dec-identity-unification Phase 5c/6, finally closed. This was supposed to
be fully replaced by the polizy authz system when it was first
introduced and instead survived as a parallel, hand-maintained table kept
in sync with @dune/core's canonical actionToRelations schema by
convention only, not by anything enforcing it.

- Deleted ROLE_PERMISSIONS (types.ts) and AuthMiddleware.hasPermission()
  (auth/middleware.ts) entirely, including the public mod.ts re-export.
- checkPermission()/requirePermission() (routes/api/_utils.ts) now fail
  closed (deny) when authz is undefined -- an in-process object
  construction failing at startup, essentially never hit in practice --
  instead of silently degrading to the removed table.
- routes/_layout.tsx's sidebar nav filtering, the one place that used
  ROLE_PERMISSIONS unconditionally rather than as a fallback, now reads a
  real permission set computed via authz.check() once per request
  (routes/_middleware.ts's new computeNavPermissions(), threaded through
  AdminState.permissions) instead of a table that could silently drift
  from what a route's own check would actually decide.

No first-party or third-party plugin found calling the removed surface
directly (checked @dune/plugin-inline-edit, @dune/plugin-meilisearch,
@dune/plugin-orama, @dune/plugin-pdf) -- @dune/plugin-inline-edit's own
auth.hasPermission(permission) call goes through @dune/core's published,
synchronous HookContext.auth.hasPermission() hook API, whose contract is
unchanged (companion @dune/core change: it now sources its answer from
roleHasPermission(), a synchronous read of @dune/core's own canonical
schema, instead of this package's table).

227 tests pass (5 new: computeNavPermissions()); deno check/lint clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zumbrunn and others added 6 commits August 30, 2026 23:22
…defined

The _middleware.ts gate skipped the access check when adminCtx.authz was
undefined (authz creation failed at startup), relying on every route's
own checkPermission()/requirePermission() call to fail closed. But the
PR claim is "authz.check() is the sole authority, no exceptions" — and
any route that relies on this gate alone would be exposed to any
authenticated user (of any role) during that state. The gate now returns
403 in that state, the same policy as every route-level check.

csp_test's fixture needed an always-allowing authz mock: without one the
authenticated render it tests never happens (403 before render).

Found during security audit of dune#15 / plugin-admin#3 (Finding 4).
role-utils.ts's ROLE_RANK/highestValidRole() reimplemented the identical
admin-tier rank table @dune/core's new highestAdminRole() (bdeb897, added
to fix roles[0] under-privileging ResponseTransformContext) already has --
exactly the "two tables kept in sync by convention" pattern this
release's ROLE_PERMISSIONS removal was about eliminating, just running in
the other direction. Now derives ROLE_RANK/highestValidRole() from
@dune/core's ADMIN_ROLE_RANK/highestAdminRole() (@dune/core/auth/authz-schema,
companion @dune/core change adds the export) instead of keeping a second
copy. VALID_ROLES/sanitizeRole()/withRole() unaffected. No behavior
change; 4 new tests lock in the delegation. 232 tests pass, deno check/lint
clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…llback

authz is still up when bootstrapAdminTuples() throws; the access gate
denies. The old warn told operators the panel still worked via a table
that no longer exists.

Co-authored-by: Cursor <cursoragent@cursor.com>
The name only existed on ROLE_PERMISSIONS. The schema action is "access"
on app:admin, already enforced by the middleware gate. Leaving it on the
union invited plugin pages that would 403 after the fallback was removed.

Co-authored-by: Cursor <cursoragent@cursor.com>
AdminContext.auth.hasPermission() is gone; missing authz denies.

Co-authored-by: Cursor <cursoragent@cursor.com>
role-utils.ts already imports ADMIN_ROLE_RANK from that subpath. Keep the
0.34 pin (per-minor); 0.34.1 on JSR does not have the export yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
@zumbrunn
zumbrunn merged commit f14e9a2 into main Aug 31, 2026
3 of 4 checks passed
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.

1 participant