Skip to content

fix(cli): unwrap API response envelope in device flow login#72

Open
travisbreaks wants to merge 3 commits intoemdash-cms:mainfrom
travisbreaks:fix/cli-login-device-flow
Open

fix(cli): unwrap API response envelope in device flow login#72
travisbreaks wants to merge 3 commits intoemdash-cms:mainfrom
travisbreaks:fix/cli-login-device-flow

Conversation

@travisbreaks
Copy link
Copy Markdown

@travisbreaks travisbreaks commented Apr 2, 2026

Summary

Fixes #54. Two bugs in emdash login against remote (Cloudflare-deployed) instances:

  • Response envelope mismatch: Server-side API wraps successful responses in { data: { ... } } via apiSuccess(), but the CLI casts the raw response body directly as DeviceCodeResponse / TokenResponse. All fields resolve to undefined.
  • Missing scope: Device code request omits scope: "admin", causing issued tokens to have no scopes. Subsequent CLI commands fail with INSUFFICIENT_SCOPE.

Changes

  • Unwrap .data from device code response
  • Unwrap .data from token polling success response (error responses are already flat, no change needed)
  • Add scope: "admin" to device code request body

Test plan

  • emdash login against a remote Cloudflare-deployed instance completes successfully
  • Token includes admin scope (or clamped scope for non-admin users)
  • emdash whoami succeeds after login

Co-Authored-By: Claude Opus 4.6 (1M context) tadao@travisfixes.com

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 2, 2026

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

emdash

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

create-emdash

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

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

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 5db20d3

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 2, 2026

🦋 Changeset detected

Latest commit: 5db20d3

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

This PR includes changesets to release 9 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/plugin-ai-moderation Patch
@emdash-cms/plugin-atproto Patch
@emdash-cms/plugin-audit-log Patch
@emdash-cms/plugin-color Patch
@emdash-cms/plugin-embeds Patch
@emdash-cms/plugin-forms Patch
@emdash-cms/plugin-webhook-notifier 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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@travisbreaks travisbreaks force-pushed the fix/cli-login-device-flow branch from 4b3ebb6 to 2300bee Compare April 5, 2026 01:20
@travisbreaks
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Apr 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

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.

@ascorbic
Copy link
Copy Markdown
Collaborator

ascorbic commented Apr 6, 2026

Can you rebase this on main

travisbreaks and others added 2 commits April 6, 2026 23:35
…ow login

Fixes emdash-cms#54. The server wraps successful responses in { data: { ... } }
via apiSuccess(), but the CLI was casting the raw response body directly
as DeviceCodeResponse / TokenResponse, causing all fields to resolve to
undefined. Also adds scope: "admin" to the device code request so tokens
receive proper scopes.

Co-Authored-By: Claude Opus 4.6 (1M context) <tadao@travisfixes.com>
Co-Authored-By: Tadao <tadao@travisfixes.com>
@travisbreaks travisbreaks force-pushed the fix/cli-login-device-flow branch from 75cf358 to ff50368 Compare April 7, 2026 05:38
etewiah added a commit to RealEstateWebTools/emdash_property_web_builder that referenced this pull request Apr 7, 2026
Without it the server issues a token with no scopes and all subsequent
CLI commands fail. Mirrors the second fix in upstream emdash-cms/emdash#72.
Also updates patch docs to cover both fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@BenjaminPrice
Copy link
Copy Markdown
Contributor

BenjaminPrice commented Apr 8, 2026

I am unable to replicate this bug with my Cloudflare deployed (via DashHost) EmDash instance. When I login via the CLI, it works fine.

emdash whoami returns as expected

ℹ Email: REDACTED
ℹ Name:  Ben Price 
ℹ Role:  admin
ℹ Auth:  stored
ℹ URL:   https://REDACTED.dashhost.cc  

@travisbreaks
Copy link
Copy Markdown
Author

Thanks for testing, @BenjaminPrice. A few questions to narrow down the discrepancy:

Were you testing via DashHost or a self-deployed Cloudflare instance?

This bug is specific to self-deployed instances (direct wrangler deploy to your own Cloudflare account). DashHost manages its own deployment pipeline and infrastructure, which may include middleware or a different API layer that strips the response envelope before it reaches the CLI.

The API contract is documented:

AGENTS.md explicitly states that handlers return ApiResponse<T> wrapping data in { success, data }. The admin client already unwraps this via parseApiResponse. The CLI was the only consumer that skipped this step, casting the raw response body directly as DeviceCodeResponse.

Quick verification you can run:

curl -s -X POST https://YOUR_INSTANCE/_emdash/api/oauth/device/code \
  -H "Content-Type: application/json" \
  -d '{"client_id": "emdash-cli"}' | python3 -m json.tool

If the response looks like {"success": true, "data": {"device_code": "...", "user_code": "...", ...}}, then the envelope exists and the CLI needs to unwrap .data. If it returns the fields at the top level without wrapping, that would explain why you can't reproduce.

On the scope fix:

Independent of the envelope issue, could you check your stored token? Look at ~/.config/emdash/auth.json and see if it includes scope: "admin". Without the scope parameter in the device code request, tokens should lack admin permissions (which manifests as 403 errors on subsequent CLI commands like emdash whoami).

Happy to provide more reproduction detail if needed.

@BenjaminPrice
Copy link
Copy Markdown
Contributor

@travisbreaks I tested on DashHost.

As the creator of DashHost, I can attest that there's no middleware or anything that strips away anything from the request/response. The infrastructure deploys via wrangler using Workers for Platforms for each tenant.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes emdash login against remote (Cloudflare-deployed) instances by aligning the CLI’s device-flow parsing and request parameters with the server’s OAuth device-flow implementation.

Changes:

  • Unwrap { data: ... } API success envelopes for device-code and token polling responses.
  • Add a scope parameter to the device-code request to ensure issued tokens carry required scopes.
  • Add a changeset to publish the CLI fix as a patch release.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/core/src/cli/commands/login.ts Unwraps API success envelopes in device flow and adds requested scope to device-code request.
.changeset/fix-cli-login.md Declares a patch release for the CLI login fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

},
body: JSON.stringify({
client_id: "emdash-cli",
scope: "admin",
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scope: "admin" requests only the admin scope. On the server, device-flow authorization clamps scopes by intersection (see packages/auth/src/rbac.ts), so non-admin users will end up with zero effective scopes and the device authorization step will fail with INSUFFICIENT_ROLE. If the intent is "admin for admins, otherwise clamped", request the full default scope set plus admin (space-separated) so non-admin roles still retain permitted scopes.

Suggested change
scope: "admin",
scope: "read write admin",

Copilot uses AI. Check for mistakes.
Comment on lines 94 to 99
if (res.ok) {
return (await res.json()) as TokenResponse;
const body = (await res.json()) as { data: TokenResponse };
return body.data;
}

const body = (await res.json()) as { error?: string; interval?: number };
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the non-ok branch, the token endpoint can return standard API errors shaped like { error: { code, message } } (e.g. invalid device_code / unsupported grant_type), but this code parses { error?: string }. That leads to unhelpful messages like Token exchange failed: [object Object] and skips the server-provided message. Consider handling both shapes (RFC 8628 flat { error: string } and the standard { error: { message } }) when building the thrown Error.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI emdash login fails against remote instances

4 participants