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.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

This PR has been inactive for 14 days. It will be closed automatically in 7 days if there is no further activity.

If you're still working on this, please push an update or leave a comment.

@github-actions github-actions Bot added stale and removed stale labels Aug 4, 2026
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