diff --git a/packages/expo/src/index.ts b/packages/expo/src/index.ts index 1e2b334531..41ce046644 100644 --- a/packages/expo/src/index.ts +++ b/packages/expo/src/index.ts @@ -66,12 +66,19 @@ export const expo = (options?: ExpoOptions | undefined) => { if (isProxyURL) { return; } - const trustedOrigins = ctx.context.trustedOrigins.filter( - (origin: string) => !origin.startsWith("http"), - ); - const isTrustedOrigin = trustedOrigins.some((origin: string) => - location?.startsWith(origin), - ); + let redirectURL: URL; + try { + redirectURL = new URL(location); + } catch { + return; + } + const isHttpRedirect = + redirectURL.protocol === "http:" || + redirectURL.protocol === "https:"; + if (isHttpRedirect) { + return; + } + const isTrustedOrigin = ctx.context.isTrustedOrigin(location); if (!isTrustedOrigin) { return; } @@ -79,9 +86,8 @@ export const expo = (options?: ExpoOptions | undefined) => { if (!cookie) { return; } - const url = new URL(location); - url.searchParams.set("cookie", cookie); - ctx.setHeader("location", url.toString()); + redirectURL.searchParams.set("cookie", cookie); + ctx.setHeader("location", redirectURL.toString()); }), }, ], diff --git a/packages/expo/test/expo.test.ts b/packages/expo/test/expo.test.ts index 784cf15caa..537db33fc2 100644 --- a/packages/expo/test/expo.test.ts +++ b/packages/expo/test/expo.test.ts @@ -789,6 +789,7 @@ describe("expo deep link cookie injection", async () => { callbackURL: "myapp:///dashboard", }); + let redirectHandled = false; const { error } = await client.magicLink.verify({ query: { token: magicLinkToken, @@ -796,6 +797,7 @@ describe("expo deep link cookie injection", async () => { }, fetchOptions: { onError(context) { + redirectHandled = true; expect(context.response.status).toBe(302); const location = context.response.headers.get("location"); expect(location).toContain("myapp://"); @@ -807,7 +809,73 @@ describe("expo deep link cookie injection", async () => { }, }, }); - expect(error).toBeDefined(); + expect(redirectHandled).toBe(true); + expect(error?.status).toBe(302); + }); +}); + +/** + * @see https://github.com/better-auth/better-auth/issues/6810 + */ +describe("expo deep link cookie injection with wildcard trustedOrigins", async () => { + let magicLinkToken = ""; + const storage = new Map(); + + const { client } = await getTestInstance( + { + plugins: [ + expo(), + magicLink({ + async sendMagicLink({ token }) { + magicLinkToken = token; + }, + }), + ], + trustedOrigins: ["myapp://*"], + }, + { + clientOptions: { + plugins: [ + expoClient({ + storage: { + getItem: (key) => storage.get(key) || null, + setItem: async (key, value) => storage.set(key, value), + }, + }), + magicLinkClient(), + ], + }, + }, + ); + + it("should inject cookie into deep link for wildcard trusted origin", async () => { + await client.signIn.magicLink({ + email: "wildcard-test@example.com", + callbackURL: "myapp:///dashboard", + }); + + let redirectHandled = false; + const { error } = await client.magicLink.verify({ + query: { + token: magicLinkToken, + callbackURL: "myapp:///dashboard", + }, + fetchOptions: { + onError(context) { + redirectHandled = true; + expect(context.response.status).toBe(302); + const location = context.response.headers.get("location"); + expect(location).toContain("myapp://"); + + const url = new URL(location!); + const cookie = url.searchParams.get("cookie"); + expect(cookie).toBeDefined(); + expect(cookie).toContain("better-auth.session_token"); + }, + }, + }); + expect(redirectHandled).toBe(true); + expect(error?.status).toBe(302); }); }); @@ -853,6 +921,7 @@ describe("expo deep link cookie injection for verify-email", async () => { expect(verificationToken).toBeTruthy(); + let redirectHandled = false; const { error } = await client.verifyEmail( { query: { @@ -862,6 +931,7 @@ describe("expo deep link cookie injection for verify-email", async () => { }, { onError(context) { + redirectHandled = true; expect(context.response.status).toBe(302); const location = context.response.headers.get("location"); expect(location).toContain("myapp://"); @@ -873,7 +943,8 @@ describe("expo deep link cookie injection for verify-email", async () => { }, }, ); - expect(error).toBeDefined(); + expect(redirectHandled).toBe(true); + expect(error?.status).toBe(302); }); });