feat(plugins): let a plugin register its own authz-schema actions - #19
Merged
Conversation
DunePlugin.authzActions lets a plugin declare a new admin-permission action (e.g. "billing.manage") and gate a route behind it the same correct way every built-in admin route is (authz.check()), instead of reusing an existing, semantically-mismatched permission or hand-rolling a check outside the authz system entirely. Relation-only — a plugin can require existing relations (member/admin/ editor/author/owner), not define a new relation type, which keeps the scope to the actual motivating case without opening the harder question of arbitrary plugin-defined relation semantics. src/auth/authz-schema.ts's module-level duneAuthzSchema constant becomes buildDuneAuthzSchema()'s zero-argument output (built-ins only, DUNE_BASE_ AUTHZ_ACTIONS exported for collision-checking) — bootstrap() now calls this once per site, after every plugin's setup() has run and their authzActions have been collected and de-duplicated (first declaration wins on a name collision, built-in or cross-plugin, dropped with a logged warning rather than silently merged/overwritten). BootstrapResult gains authzSchema (the site's actual merged schema) alongside authz, which is already built against it via createDuneAuthSystem()'s new optional schema config field. roleHasPermission() gains an optional third parameter (a site's actual actionToRelations map) so the one synchronous permission-check path (ResponseTransformContext.auth .hasPermission(), threaded through response-transforms.ts's new actionToRelations option) resolves a plugin-contributed action correctly instead of only ever seeing the built-ins. Multisite-safe by construction: the schema lives on BootstrapResult, not a process-wide singleton, so one site's plugin action can't leak into another's schema. Companion @dune/plugin-admin change needed for a plugin author to actually pass a custom action through withGuards()/requirePermission() without a type-level workaround (AdminPermission is currently a closed union) — separate PR. 3 new tests (real bootstrap() + real authz.check() end to end, including both collision cases) plus unit coverage for buildDuneAuthzSchema()/ roleHasPermission()'s new parameter. Folded into the still-unpublished 0.34.4 CHANGELOG entry, not a new version bump — 0.34.4 has not published to JSR yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
DunePlugin.authzActionslets a plugin declare a new admin-permission action (e.g."billing.manage") and gate a route behind it the same correct way every built-in admin route is (authz.check()), instead of reusing an existing, semantically mismatched permission or hand-rolling a check outside the authz system entirely.Closes the "Plugin-extensible authz schema" backlog item.
Design
member/admin/editor/author/owner), not define a new relation type — covers the actual motivating case without opening the harder question of arbitrary plugin-defined relation semantics.src/auth/authz-schema.ts: the module-levelduneAuthzSchemaconstant is nowbuildDuneAuthzSchema()'s zero-argument output (built-ins only) — unaffected for any existing caller reading it directly (tests, headless usage).bootstrap()calls the same builder with plugin-contributed actions merged in, once per site.bootstrap.tscollects every plugin'sauthzActionsaftersetup()has run, before the authz system is created. A name colliding with a built-in action or another plugin's is dropped with a logged warning, not silently merged — first declaration wins in registration order.BootstrapResult.authzSchema, not a process-wide singleton, so one site's plugin action can't leak into another's schema.roleHasPermission()(the one synchronous permission-check path,ResponseTransformContext.auth.hasPermission()) gains an optional third parameter — a site's actualactionToRelationsmap — so it resolves a plugin-contributed action correctly instead of only ever seeing the built-ins.Companion change
Needs a matching
@dune/plugin-adminchange (wideningAdminPermissionfrom a closed union to accept any string) for a plugin author to actually pass a custom action throughwithGuards()/requirePermission()without a type-level workaround — separate PR: duneorg/plugin-admin#5.Testing
tests/auth/authz_plugin_actions_test.ts— realbootstrap()+ realauthz.check()end to end, including both collision cases (built-in and cross-plugin).tests/auth/authz_schema_test.tsforbuildDuneAuthzSchema()/roleHasPermission()'s new parameter.deno test -A tests/— 1815 passed, 0 failed.deno task check(lint + typecheck + dynamic-import-lint) clean.Note on versioning
This is folded into the existing, still-unpublished
## [0.34.4]CHANGELOG entry rather than a new version bump —deno.jsonstays at0.34.4since that version hasn't published to JSR yet.🤖 Generated with Claude Code