diff --git a/.server-changes/fix-magic-link-login-confirmation.md b/.server-changes/fix-magic-link-login-confirmation.md new file mode 100644 index 00000000000..82f76e28023 --- /dev/null +++ b/.server-changes/fix-magic-link-login-confirmation.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: fix +--- + +Fixed submitting your email on the login page reloading back to an empty form instead of showing the magic link confirmation screen. diff --git a/apps/webapp/app/routes/login.magic/magicLinkEmailCookie.server.ts b/apps/webapp/app/routes/login.magic/magicLinkEmailCookie.server.ts deleted file mode 100644 index 8f1738d2aa3..00000000000 --- a/apps/webapp/app/routes/login.magic/magicLinkEmailCookie.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createCookie } from "@remix-run/node"; -import { env } from "~/env.server"; - -// Carries the submitted email to the confirmation screen in a short-lived, -// httpOnly cookie rather than the URL, so the address never lands in access -// logs, browser history, or error-tracker breadcrumbs. Lives in a .server -// module: it calls createCookie at import time using server-only env, which -// throws if it ever evaluates in the client bundle. -export const magicLinkEmailCookie = createCookie("magiclink-email", { - maxAge: 60 * 10, - httpOnly: true, - sameSite: "lax", - secure: env.NODE_ENV === "production", - path: "/", -}); diff --git a/apps/webapp/app/routes/login.magic/route.tsx b/apps/webapp/app/routes/login.magic/route.tsx index 0fc45261729..7ad40a04c62 100644 --- a/apps/webapp/app/routes/login.magic/route.tsx +++ b/apps/webapp/app/routes/login.magic/route.tsx @@ -34,7 +34,6 @@ import { ssoRedirectForEmail } from "~/services/ssoAutoDiscovery.server"; import { logger, tryCatch } from "@trigger.dev/core/v3"; import { env } from "~/env.server"; import { extractClientIp } from "~/utils/extractClientIp.server"; -import { magicLinkEmailCookie } from "./magicLinkEmailCookie.server"; export const meta: MetaFunction = ({ matches }) => { const parentMeta = matches @@ -74,12 +73,14 @@ export async function loader({ request }: LoaderFunctionArgs) { // confirmation renders the flashed error as magicLinkError below. const url = new URL(request.url); const sanitized = sanitizeRedirectPath(url.searchParams.get("redirectTo")); - // The submitted address is carried in a short-lived cookie (not the URL) so - // the confirmation can name it. Validate before echoing it back. - const emailCookie = await magicLinkEmailCookie.parse(request.headers.get("Cookie")); + // The email-link strategy stores the submitted address in the session + // (`auth:email`) alongside the magic-link key, so read it from there to name + // the confirmation — no address in the URL, and no separate cookie to leak + // into the client bundle. Validate before echoing it back. + const emailValue = session.get("auth:email"); const email = - typeof emailCookie === "string" && z.string().email().safeParse(emailCookie).success - ? emailCookie + typeof emailValue === "string" && z.string().email().safeParse(emailValue).success + ? emailValue : null; if (!session.has("triggerdotdev:magiclink")) { // Throw (not return) so the redirect doesn't widen the loader's return @@ -215,20 +216,13 @@ export async function action({ request }: ActionFunctionArgs) { return redirect(ssoRedirect); } - // authenticator.authenticate throws its redirect Response; attach the - // sent-to email as a short-lived cookie so the confirmation can name it - // without putting the address in the URL. - try { - return await authenticator.authenticate("email-link", request, { - successRedirect: "/login/magic", - failureRedirect: "/login", - }); - } catch (thrown) { - if (thrown instanceof Response) { - thrown.headers.append("Set-Cookie", await magicLinkEmailCookie.serialize(email)); - } - throw thrown; - } + // The email-link strategy stores the address in the session (`auth:email`) + // and throws its own redirect Response (with the committed session cookie), + // so return it directly — the confirmation reads the email from the session. + return await authenticator.authenticate("email-link", request, { + successRedirect: "/login/magic", + failureRedirect: "/login", + }); } case "reset": default: { @@ -260,7 +254,7 @@ export default function LoginMagicLinkPage() {