Skip to content

feat(plugins): expose authenticated caller to route handlers as ctx.user#1947

Open
swissky wants to merge 1 commit into
emdash-cms:mainfrom
swissky:feat/plugin-route-caller
Open

feat(plugins): expose authenticated caller to route handlers as ctx.user#1947
swissky wants to merge 1 commit into
emdash-cms:mainfrom
swissky:feat/plugin-route-caller

Conversation

@swissky

@swissky swissky commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

The plugin API catch-all resolves the authenticated user, uses it to enforce plugins:manage + scope + CSRF — and then drops it before invoking the plugin's handler. Plugins had no first-class way to know who is calling a private route, forcing bad workarounds (reading a user id from the request body is trivially spoofable).

This threads the validated caller through handlePluginApiRoute into the route context, exactly as proposed in #812:

  • Native format: ctx.user on RouteContext.
  • Standard (sandboxed) format: routeCtx.user — wired through the in-process adapter, the Cloudflare Worker Loader wrapper, and both workerd runners (SerializedRequest.user is structured-clone/JSON-safe).
  • The exposed shape is the existing read-only UserInfo ({ id, email, name, role, createdAt }) — the same shape ctx.users returns, with sensitive host-side fields (avatarUrl, data, …) stripped by toRouteCallerInfo().

Semantics (per the issue):

  • Private routes: ctx.user is the authenticated caller, validated by the existing requirePerm/requireScope checks one frame up.
  • Public routes: ctx.user is undefined — the catch-all only forwards the caller after private-route auth, so an ambient admin session browsing the site is never bound to a public route.
  • Machine tokens with no bound user: undefined.

Purely additive — no change for plugins that don't read ctx.user, no migration, no flag. Caller identity is intentionally not gated by the users:read capability (it's the caller's own identity, not a directory lookup).

Tests: route handler exposes/omits ctx.user (unit), catch-all forwards the caller only for private routes (unit, including the ambient-session case), sandbox adapter puts user on routeCtx and strips it from pluginCtx, toRouteCallerInfo mapping (Date→ISO, field stripping). Docs updated in the API-routes guide (new "The authenticated caller" section + route context reference) and the native-plugin guide.

Closes #812
Discussion: #1757

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Cursor + Fable 5

Screenshots / test output

tests/unit/plugins + tests/unit/astro/plugin-api-route-auth.test.ts
 Test Files  30 passed (30)
      Tests  629 passed (629)

tests/integration/plugins + tests/unit/astro
 Test Files  26 passed (26)
      Tests  276 passed (276)

@emdash-cms/sandbox-workerd: 76 passed · @emdash-cms/cloudflare: 222 passed

The plugin API catch-all resolves and authorizes the caller, then drops
it before dispatch, leaving plugins no safe way to know who is calling
a private route. Thread the validated user through handlePluginApiRoute
into RouteContext (native format) and routeCtx.user (standard format,
in-process and worker sandboxes alike) as the read-only UserInfo shape.

Public routes and machine tokens with no bound user receive undefined;
the catch-all only forwards the caller after private-route auth, so an
ambient admin session is never bound to a public route.

Fixes emdash-cms#812
@changeset-bot

changeset-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 872b71e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Minor
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Minor
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1947

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1947

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1947

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1947

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1947

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1947

emdash

npm i https://pkg.pr.new/emdash@1947

create-emdash

npm i https://pkg.pr.new/create-emdash@1947

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1947

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1947

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1947

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1947

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1947

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1947

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1947

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1947

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1947

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1947

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1947

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1947

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1947

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1947

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1947

commit: 872b71e

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/cloudflare area/core area/docs bot:review Trigger an emdashbot code review on this PR needs-rebase overlap review/needs-review No maintainer or bot review yet size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose authenticated user to plugin route handlers via RouteContext

1 participant