Skip to content

Email-verification signup cannot complete: the email links to the API route, and /_emdash/admin/signup is not public #3007

Description

@jakevis

Summary

Email-verification signup cannot be completed. Clicking the link in the verification email lands the user on a raw JSON response:

{"success":true,"data":{"success":true,"email":"someone@example.org","role":40,"roleName":"EDITOR"}}

Three defects compound, and the flow dead-ends regardless of which one you work around.

Affected: @emdash-cms/auth@0.32.0 / emdash@0.32.0, both verified unchanged in 0.37.0 / 0.36.0 (current latest).

1. The email links to the API route, not the admin page

packages/auth/src/signup.ts:94:

const url = new URL("/_emdash/api/auth/signup/verify", config.baseUrl);
url.searchParams.set("token", token);

/_emdash/api/auth/signup/verify is the endpoint the SPA calls; it returns JSON for the UI to render. Sending a human there shows them the JSON above.

The invite flow, in the same package, does it correctly — packages/auth/src/invite.ts:72:

const url = new URL(`${config.baseUrl}/admin/invite/accept`);

The signup page already handles a token from the URL, so it is the right destination:

// admin SignupPage
const urlToken = new URLSearchParams(window.location.search).get("token");
if (urlToken) { setToken(urlToken); verifyToken(urlToken); }

2. /_emdash/admin/signup is not a public route

packages/core/src/astro/middleware/auth.ts:331:

url.pathname.startsWith("/_emdash/admin/login") ||
url.pathname.startsWith("/_emdash/admin/invite/accept");

/_emdash/admin/signup is absent, so an unauthenticated visitor is redirected to login:

$ curl -sI 'https://example.com/_emdash/admin/signup?token=…'
HTTP/2 302
location: https://example.com/_emdash/admin/login?redirect=%2F_emdash%2Fadmin%2Fsignup

The page is reachable today only by client-side navigation from the login page ("Don't have an account? Sign up"), which is how a user requests signup in the first place — but never with a token in the URL.

3. The login redirect drops the query string

packages/core/src/astro/middleware/auth.ts:684:

loginUrl.searchParams.set("redirect", url.pathname);

Only pathname, so ?token=… is lost. Fixing (1) and (2) alone would still lose the token for anyone who hits the redirect.

Reproduction

  1. Enable email/password or magic-link signup.
  2. From /_emdash/admin/login, follow "Sign up" and submit an address.
  3. Open the verification email and click the link.
  4. Raw JSON. Editing the URL by hand to /_emdash/admin/signup?token=… redirects to login and drops the token.

Impact

Self-signup cannot complete on a fresh instance. The invite flow is unaffected — its page is public and its email links to it — so inviting users is the working path, but that is not obvious from the failure, which looks like a broken deployment rather than an unsupported flow.

Suggested fix

Point the email at the page, matching invite.ts:

const url = new URL(`${config.baseUrl}/admin/signup`);
url.searchParams.set("token", token);

Add the route to the public list:

url.pathname.startsWith("/_emdash/admin/login") ||
url.pathname.startsWith("/_emdash/admin/signup") ||
url.pathname.startsWith("/_emdash/admin/invite/accept");

And preserve the query string on the redirect:

loginUrl.searchParams.set("redirect", url.pathname + url.search);

The third is worth doing independently of the other two — any future token-bearing admin URL hits the same loss. Happy to open a PR.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions