Skip to content

fix(admin): implement mountEarly() so plugin-registered routes see adminContext/auth - #4

Merged
zumbrunn merged 2 commits into
mainfrom
fix/mount-early-admin-context
Sep 2, 2026
Merged

fix(admin): implement mountEarly() so plugin-registered routes see adminContext/auth#4
zumbrunn merged 2 commits into
mainfrom
fix/mount-early-admin-context

Conversation

@zumbrunn

@zumbrunn zumbrunn commented Sep 1, 2026

Copy link
Copy Markdown
Member

Problem

A third-party plugin's own mount()-registered route could never see ctx.state.adminContext or ctx.state.auth, no matter how the request was authenticated — withGuards() always treated it as unauthenticated.

Root cause

This plugin's own mount() always runs last (bootstrap() in @dune/core can't construct it until authz, the HMAC key, and the history engine are ready — well after every site-configured plugin already registered). Fresh 2 snapshots each route's middleware chain at the moment the route is registered, so any other plugin's own route was compiled before this plugin's ctx.state.adminContext = … and admin-auth middleware existed at all.

Fix

Implements the new DunePlugin.mountEarly() hook from duneorg/dune#17: mountDuneAdminEarly() registers just the two pieces of middleware other plugins' routes depend on — ctx.state.adminContext and the admin auth middleware (ctx.state.auth) — before any plugin's mount() runs.

The AdminContext object itself is still built inside mount(), unchanged — restructuring where it's built was out of scope and too risky for this fix. mountEarly()'s middleware reads it through a lazily-resolved getter closure that starts returning the real value once mount() finishes building it, well before the app can actually serve a request. Both new middleware registrations are guarded against double-registration (WeakSet, mirrors the existing registerPluginPublicRoutes() pattern in @dune/core) so headless-mode direct callers of mountDuneAdmin() — who never call mountEarly() — are unaffected.

Also discovered

A plugin's own guarded route must be registered under the admin path prefix (e.g. /admin/my-plugin/…) — the admin auth middleware that sets ctx.state.auth only runs for requests under that prefix, same as every built-in admin route. Documented in admin/guards.ts's example and in duneorg/dune-docs#4.

Requires

Testing

  • New regression test tests/admin/mount_early_test.ts — verified it fails (both sawAdminContext/sawAuth false, and a TS2724 against pre-fix mount.ts for the missing export) without the fix, passes with it.
  • Full suite: deno test -A — 233 passed, 0 failed.
  • deno lint src/ and deno check clean.

🤖 Generated with Claude Code

zumbrunn and others added 2 commits September 1, 2026 22:02
…minContext/auth

Third-party-plugin-guarded-route bug: a plugin's own mount()-registered
route could never see ctx.state.adminContext or ctx.state.auth, no
matter how the request was authenticated, because this plugin always
mounts last (bootstrap() can't construct it until authz/hmac-key/history
are ready) and Fresh 2 snapshots each route's middleware chain at
registration time.

Implements the new DunePlugin.mountEarly() hook (@dune/core@0.34.4):
mountDuneAdminEarly() registers just the ctx.state.adminContext and
admin-auth middleware early, guarded against double-registration
(WeakSet, mirrors the existing registerPluginPublicRoutes() pattern) so
headless-mode direct callers of mountDuneAdmin() are unaffected. The
AdminContext object itself is still built inside mount() (unchanged,
too large/risky to restructure here) — mountEarly()'s middleware reads
it through a lazily-resolved getter that starts returning the real
value once mount() finishes building it, well before the app can serve
a request.

Also discovered while writing the regression test: a plugin's guarded
route must be registered under the admin path prefix (e.g.
/admin/my-plugin/...) — the admin auth middleware that sets
ctx.state.auth only runs for requests under that prefix. Documented in
duneorg/dune-docs#4.

Requires @dune/core@^0.34.4 or later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…unePlugin.mountEarly()

Same class of bug as the 3.0.0 publish failure: PR #4's own deno.json
edit bumped @dune/core to ^0.34.4 by hand without updating this
generator's CORE_RANGE constant to match, so CI's check:core-imports
correctly caught the mismatch. Confirmed the fix with a real isolated
deno publish --dry-run.
@zumbrunn
zumbrunn merged commit 8055311 into main Sep 2, 2026
3 checks passed
@zumbrunn
zumbrunn deleted the fix/mount-early-admin-context branch September 2, 2026 08:56
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