@@ -34,6 +34,7 @@ import { ssoRedirectForEmail } from "~/services/ssoAutoDiscovery.server";
3434import { logger , tryCatch } from "@trigger.dev/core/v3" ;
3535import { env } from "~/env.server" ;
3636import { extractClientIp } from "~/utils/extractClientIp.server" ;
37+ import { magicLinkEmailCookie } from "./magicLinkEmailCookie.server" ;
3738
3839export const meta : MetaFunction = ( { matches } ) => {
3940 const parentMeta = matches
@@ -73,14 +74,12 @@ export async function loader({ request }: LoaderFunctionArgs) {
7374 // confirmation renders the flashed error as magicLinkError below.
7475 const url = new URL ( request . url ) ;
7576 const sanitized = sanitizeRedirectPath ( url . searchParams . get ( "redirectTo" ) ) ;
76- // The email-link strategy stores the submitted address in the session
77- // (`auth:email`) alongside the magic-link key, so read it from there to name
78- // the confirmation — no address in the URL, and no separate cookie to leak
79- // into the client bundle. Validate before echoing it back.
80- const emailValue = session . get ( "auth:email" ) ;
77+ // The submitted address is carried in a short-lived cookie (not the URL) so
78+ // the confirmation can name it. Validate before echoing it back.
79+ const emailCookie = await magicLinkEmailCookie . parse ( request . headers . get ( "Cookie" ) ) ;
8180 const email =
82- typeof emailValue === "string" && z . string ( ) . email ( ) . safeParse ( emailValue ) . success
83- ? emailValue
81+ typeof emailCookie === "string" && z . string ( ) . email ( ) . safeParse ( emailCookie ) . success
82+ ? emailCookie
8483 : null ;
8584 if ( ! session . has ( "triggerdotdev:magiclink" ) ) {
8685 // Throw (not return) so the redirect doesn't widen the loader's return
@@ -216,13 +215,20 @@ export async function action({ request }: ActionFunctionArgs) {
216215 return redirect ( ssoRedirect ) ;
217216 }
218217
219- // The email-link strategy stores the address in the session (`auth:email`)
220- // and throws its own redirect Response (with the committed session cookie),
221- // so return it directly — the confirmation reads the email from the session.
222- return await authenticator . authenticate ( "email-link" , request , {
223- successRedirect : "/login/magic" ,
224- failureRedirect : "/login" ,
225- } ) ;
218+ // authenticator.authenticate throws its redirect Response; attach the
219+ // sent-to email as a short-lived cookie so the confirmation can name it
220+ // without putting the address in the URL.
221+ try {
222+ return await authenticator . authenticate ( "email-link" , request , {
223+ successRedirect : "/login/magic" ,
224+ failureRedirect : "/login" ,
225+ } ) ;
226+ } catch ( thrown ) {
227+ if ( thrown instanceof Response ) {
228+ thrown . headers . append ( "Set-Cookie" , await magicLinkEmailCookie . serialize ( email ) ) ;
229+ }
230+ throw thrown ;
231+ }
226232 }
227233 case "reset" :
228234 default : {
@@ -254,7 +260,7 @@ export default function LoginMagicLinkPage() {
254260 </ Header1 >
255261 < Fieldset className = "flex w-full flex-col items-center gap-y-2" >
256262 < InboxArrowDownIcon className = "mb-4 h-12 w-12 text-indigo-500" />
257- < Paragraph className = "mb-6 text-center" >
263+ < Paragraph className = "mb-6 text-center [text-wrap:balance] " >
258264 { email ? (
259265 < >
260266 We emailed a magic link to < span className = "text-text-bright" > { email } </ span > to
0 commit comments