Skip to content

fix: repair invite flow — URL prefix + registration UI#81

Closed
SrikanthAlva wants to merge 9 commits intoemdash-cms:mainfrom
SrikanthAlva:fix/invite-flow-67
Closed

fix: repair invite flow — URL prefix + registration UI#81
SrikanthAlva wants to merge 9 commits intoemdash-cms:mainfrom
SrikanthAlva:fix/invite-flow-67

Conversation

@SrikanthAlva
Copy link
Copy Markdown

@SrikanthAlva SrikanthAlva commented Apr 2, 2026

Summary

Fixes the two compounding bugs in the user invite system that made it completely non-functional (Issue #67):

  • Bug 1 — Invite URL drops /_emdash/ prefix: new URL("/api/auth/invite/accept", baseUrl) treats the leading / as an absolute path, discarding the /_emdash path from baseUrl. Fixed by using string concatenation and pointing the invite URL directly to the admin UI page.
  • Bug 2 — No UI to complete registration: Added an InviteAcceptPage in the admin SPA that validates the invite token, displays the user's email and assigned role, and drives passkey registration via the existing /auth/invite/complete endpoint.

Files changed

File Change
packages/auth/src/invite.ts Fix URL construction to preserve baseUrl path prefix; point invite URL to admin UI page
packages/core/tests/unit/auth/invite.test.ts Add regression test for baseUrl with path prefix (/_emdash)
packages/admin/src/components/InviteAcceptPage.tsx New invite acceptance page (modeled after SignupPage.tsx)
packages/admin/src/lib/api/users.ts Add validateInviteToken and completeInviteSignup client functions
packages/admin/src/lib/api/index.ts Re-export new functions and type
packages/admin/src/router.tsx Register /invite/accept route

Test plan

  • New unit test should preserve baseUrl path prefix in invite URL passes
  • All 17 invite unit tests pass
  • All 2096 core package tests pass
  • All 41 auth package tests pass
  • pnpm typecheck passes (all packages)
  • pnpm --silent lint:quick returns 0 diagnostics
  • pnpm format clean
  • Manual: create an invite in the admin UI → verify the generated URL includes /_emdash/admin/invite/accept
  • Manual: visit the invite URL → verify the InviteAcceptPage loads with email/role displayed
  • Manual: complete passkey registration → verify user is created and redirected to admin dashboard

Closes #67

Made with Cursor

The invite system had two compounding bugs making it completely
non-functional:

1. The invite URL dropped the /_emdash/ prefix because
   `new URL("/api/auth/invite/accept", baseUrl)` treats the leading
   slash as an absolute path, discarding any path component in baseUrl.
   Fixed by using string concatenation: `new URL(`${baseUrl}/admin/invite/accept`)`.

2. There was no UI for invited users to complete registration. The
   invite link now points directly to a new admin SPA page
   (/_emdash/admin/invite/accept) that validates the token, shows the
   user's email and role, and drives passkey registration via the
   existing /auth/invite/complete endpoint.

3. The auth middleware blocked unauthenticated access to the invite
   accept page. Added /_emdash/admin/invite/accept (and /signup) to the
   public admin route bypass list so invited users can reach the page
   without an existing session.

Changes:
- packages/auth/src/invite.ts: fix URL construction, point to admin UI
- packages/core/src/astro/middleware/auth.ts: allow unauthenticated
  access to invite accept and signup admin pages
- packages/core/tests/unit/auth/invite.test.ts: add regression test
  for baseUrl with path prefix
- packages/admin/src/components/InviteAcceptPage.tsx: new invite
  acceptance page (modeled after SignupPage)
- packages/admin/src/lib/api/users.ts: add validateInviteToken and
  completeInviteSignup client functions
- packages/admin/src/lib/api/index.ts: re-export new functions
- packages/admin/src/router.tsx: register /invite/accept route

Closes emdash-cms#67

Made-with: Cursor
The invite page was incorrectly using the setup/admin endpoint to
generate passkey registration options, which fails with "Setup already
complete" after initial setup. This adds a dedicated
/api/auth/invite/register-options endpoint that validates the invite
token and generates passkey options using a temporary user identity.

Made-with: Cursor
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 3, 2026

⚠️ No Changeset found

Latest commit: 16ee57c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

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

@SrikanthAlva
Copy link
Copy Markdown
Author

I have read the CLA document, and I hereby sign the CLA

@jcheese1
Copy link
Copy Markdown

jcheese1 commented Apr 4, 2026

bump

@jcheese1
Copy link
Copy Markdown

jcheese1 commented Apr 4, 2026

can we get this in, @ascorbic ? (sorry for the tag) its real blocker right now 😓

@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.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 5, 2026

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

emdash

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

create-emdash

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

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

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 1031fed


const isLoginRoute = url.pathname.startsWith("/_emdash/admin/login");
const isPublicAdminRoute =
isLoginRoute ||
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.

As isLoginRoute is no longer used anywhere but here, the above line can be consolidated into here and reduce the number of variables.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated this change

<div className="w-full max-w-md">
{/* Header */}
<div className="text-center mb-8">
<div className="text-4xl font-bold mb-2">— EmDash</div>
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.

This is visually different from the Login page (see:

<LogoLockup className="h-10 mx-auto mb-2" />
) and the Setup Wizard (see:
<LogoLockup className="h-10 mx-auto mb-2" />
)

Could this structure be separated into a shared component?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Included the LogoLockup component in the InviteAcceptPage

Copy link
Copy Markdown
Contributor

@BenjaminPrice BenjaminPrice left a comment

Choose a reason for hiding this comment

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

Bugs

  • From the PR Description:

(1) new URL("/api/auth/invite/accept", baseUrl) discarded the /_emdash path prefix because the leading / made it absolute

I'm unable to reproduce this locally. When I generate an invite locally it has the /_emdash path prefix.

What I liked

  • Generally looks good. I was able to reproduce the new registration page after accepting an invite. A few other small comments have been added.

@BenjaminPrice
Copy link
Copy Markdown
Contributor

Sorry, one more thing. This new Invitation flow could really benefit from some E2E tests being added.

@SrikanthAlva
Copy link
Copy Markdown
Author

I have read the CLA document, and I hereby sign the CLA

@SrikanthAlva
Copy link
Copy Markdown
Author

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

@github-actions github-actions bot added size/XL and removed size/L labels Apr 7, 2026
Copy link
Copy Markdown
Contributor

@BenjaminPrice BenjaminPrice left a comment

Choose a reason for hiding this comment

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

Bugs

  • The unit test createInviteToken > should create a token and return url + email fails because the generated invite URL now uses /admin/invite/accept?token= instead of the expected /_emdash/api/auth/invite/accept?token=. Either the test assertion or the URL construction needs to be updated so they agree on the correct path.

Copy link
Copy Markdown
Contributor

@BenjaminPrice BenjaminPrice left a comment

Choose a reason for hiding this comment

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

See comment about failing test

@jcheese1
Copy link
Copy Markdown

Need this in

@ascorbic
Copy link
Copy Markdown
Collaborator

@SrikanthAlva it would be great if we could get this in. Are you able to get the tests passing?

mohamedmostafa58 added a commit to mohamedmostafa58/emdash that referenced this pull request Apr 13, 2026
…test

Fixes the two compounding bugs in the user invite system (Issue emdash-cms#67):

- Bug 1: Invite URL drops /_emdash/ prefix. Fixed by pointing to admin UI page.
- Bug 2: No UI to complete registration. Added InviteAcceptPage with passkey registration.
- Bug 3 (from PR emdash-cms#81 review): Fixed failing test assertion to match new URL path.

Based on PR emdash-cms#81 by @SrikanthAlva with the requested test fix applied.

Closes emdash-cms#67
@ascorbic
Copy link
Copy Markdown
Collaborator

Superseded by #542

@ascorbic ascorbic closed this Apr 14, 2026
ascorbic added a commit that referenced this pull request Apr 14, 2026
)

* fix: repair invite flow — URL prefix + registration UI + fix failing test

Fixes the two compounding bugs in the user invite system (Issue #67):

- Bug 1: Invite URL drops /_emdash/ prefix. Fixed by pointing to admin UI page.
- Bug 2: No UI to complete registration. Added InviteAcceptPage with passkey registration.
- Bug 3 (from PR #81 review): Fixed failing test assertion to match new URL path.

Based on PR #81 by @SrikanthAlva with the requested test fix applied.

Closes #67

* fix: address review comments — remove unused code, use ULID, useSearch, fix duplicate button, simplify email store

* fix(auth): remove signup from public admin routes

Making /_emdash/admin/signup a public server-side route allowed
unauthenticated direct access. The signup page is only intended
to be reached via client-side navigation from the login page.

Made-with: Cursor

---------

Co-authored-by: Matt Kane <mkane@cloudflare.com>
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.

User invite system: wrong URL (missing /_emdash/ prefix) + no UI to complete registration

4 participants