Skip to content

fix(core): make the dev-bypass token idempotent by name#1584

Merged
ascorbic merged 1 commit into
mainfrom
fix/dev-bypass-token-idempotent
Jun 22, 2026
Merged

fix(core): make the dev-bypass token idempotent by name#1584
ascorbic merged 1 commit into
mainfrom
fix/dev-bypass-token-idempotent

Conversation

@ascorbic

Copy link
Copy Markdown
Collaborator

What does this PR do?

The dev setup-bypass endpoint (GET /_emdash/api/setup/dev-bypass?token=1) minted a fresh dev-bypass-token PAT on every call. Because the token's raw value is only available at creation, the e2e harness (global-setup and refresh-server-pat after a dev reset) re-runs it to obtain a usable token — so on the Cloudflare lane, where D1 persists across tests within a shard, duplicate dev-bypass-token rows accumulated. The API-tokens settings test then tripped Playwright strict mode, since its text=dev-bypass-token locator matched more than one row:

strict mode violation: locator('text=dev-bypass-token') resolved to 2 elements

This makes the dev-bypass token idempotent by name: it deletes any existing dev-bypass-token for the user before creating a new one, so exactly one row exists while callers still receive a usable raw token. Adds a small deleteApiTokensByName handler for the delete.

Surfaced while reviewing #1378 (object cache); the failure is unrelated to that work — its merge of main simply reshuffled e2e sharding and exposed this pre-existing isolation bug.

Closes #

Type of change

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

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change) — new api-tokens.test.ts unit tests, plus the existing api-tokens.spec.ts e2e
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). n/a — no admin UI strings; dev-only endpoint.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion n/a — bug fix, not a feature.

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 (Claude Code)

Screenshots / test output

api-tokens.test.ts — 2 passed (idempotent-by-name + scoped delete)
pnpm lint — 0 diagnostics; pnpm typecheck — clean

The dev setup-bypass endpoint (`?token=1`) created a fresh `dev-bypass-token`
PAT on every call, so re-running it after a dev reset left duplicate rows. The
e2e API-tokens page then tripped Playwright strict mode (the unscoped
`dev-bypass-token` locator matched more than one). The endpoint now deletes any
existing token of that name before minting a new one, via a new
`deleteApiTokensByName` helper, keeping exactly one while still returning a
usable raw token.
@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9e39e2d

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

This PR includes changesets to release 16 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@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 Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@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

@github-actions github-actions Bot added area/core size/M review/needs-review No maintainer or bot review yet labels Jun 22, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 9e39e2d Jun 22 2026, 06:16 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 9e39e2d Jun 22 2026, 06:17 PM

@pkg-pr-new

pkg-pr-new Bot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 9e39e2d

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 9e39e2d Jun 22 2026, 06:18 PM

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approach

This is the right change at the right layer. The dev-only GET /_emdash/api/setup/dev-bypass?token=1 endpoint minted a fresh dev-bypass-token PAT on every call. Because the e2e harness legitimately re-runs it — global-setup calls it once at startup, and refresh-server-pat calls it again after a dev reset — and on the Cloudflare lane D1 persists across tests within a shard, duplicate dev-bypass-token rows accumulated and tripped Playwright strict mode on text=dev-bypass-token (resolved to 2 elements). Making the endpoint idempotent by name (delete any existing dev-bypass-token for the user, then mint a fresh one) fixes the root cause inside the endpoint rather than coupling the harness to token internals. It fits EmDash's handler-layer conventions and is correctly scoped to the ?token=1 path (the devBypassAuth fixture calls dev-bypass without ?token=1, so it's unaffected). A flawless implementation of the right thing.

What I checked

  • SQL safety: deleteApiTokensByName uses parameterized Kysely (where("user_id", "=", userId), where("name", "=", name)) — no sql.raw, no interpolation. Scoped by both user_id and name, so it cannot touch other users' tokens.
  • Return-value handling: Number(result.numDeletedRows ?? 0n) correctly converts the bigint, with a defensive null guard. The executeTakeFirst() + result.numDeletedRows pattern matches the existing handleApiTokenRevoke in the same file.
  • Idempotency logic: delete-then-create is the correct shape — the raw token is only available at creation and only the hash is stored, so you cannot reuse an old row; you must create fresh. After delete+create there is exactly one dev-bypass-token row, which resolves the strict-mode violation. The non-atomic delete+create is non-impactful here: the endpoint is dev-only, the old token's raw value was already unrecoverable so deleting it loses no usable secret, a create failure surfaces loudly (caller throws on the missing token), and the harness polls/retries so it self-heals. Not worth flagging.
  • Tests: Two real reproducing tests — scoped bulk delete (removes only the matching name for that user, leaves keepme) and the idempotent re-issue loop (exactly one dev-bypass-token after two delete+create cycles). The users insert matches the migrated schema (avatar_url, email_verified, disabled, updated_at from migrations 008/009), so the FK on _emdash_api_tokens is satisfied and the test is valid. Satisfies AGENTS.md's "failing test → fix → verify."
  • Conventions: imports ordered, tabs, no admin UI strings (Lingui/RTL n/a), dev-only endpoint so no authorization requirement, no content-table/locale concerns, changeset present and well-written ("emdash": patch, leads with a present-tense verb, describes the observable effect).

Conclusion

Clean fix. No findings.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels Jun 22, 2026
@ascorbic ascorbic merged commit 707edee into main Jun 22, 2026
48 checks passed
@ascorbic ascorbic deleted the fix/dev-bypass-token-idempotent branch June 22, 2026 18:38
@emdashbot emdashbot Bot mentioned this pull request Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core review/approved Approved; no new commits since size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant