From 5a23e0df8fc588dd4f4ec438ee4358ee28417eed Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 4 Nov 2026 00:45:42 +0100 Subject: [PATCH] fix(auth): remove the unused Expo server plugin and custom-scheme trusted origins The installed app signs in through /mobile-auth and a PKCE-bound callback code; nothing calls the Expo authorization proxy or sends expo-origin. The plugin's callback hook would copy the session cookie into any redirect to a trusted custom scheme, so openmapx:// and the development exp:// patterns leave trustedOrigins with it. --- apps/api/package.json | 1 - apps/api/src/auth-oauth-provider.test.ts | 17 ++++++++++++++++- apps/api/src/auth.ts | 15 ++++----------- pnpm-lock.yaml | 22 ---------------------- pnpm-workspace.yaml | 12 +++--------- 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 17badee51..c3f482d7f 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,7 +21,6 @@ "dependencies": { "@archiver/archiver": "0.3.0", "@better-auth/core": "1.7.16", - "@better-auth/expo": "1.7.16", "@better-auth/i18n": "1.7.16", "@better-auth/oauth-provider": "1.7.16", "@better-auth/passkey": "1.7.16", diff --git a/apps/api/src/auth-oauth-provider.test.ts b/apps/api/src/auth-oauth-provider.test.ts index 8e9c9d706..508dfdaad 100644 --- a/apps/api/src/auth-oauth-provider.test.ts +++ b/apps/api/src/auth-oauth-provider.test.ts @@ -69,7 +69,6 @@ describe("managed OAuth provider policy", () => { const coreLock = packageLock.split("\n packages/core:")[1]?.split("\n packages/")[0] ?? ""; const apiPackages = [ "@better-auth/core", - "@better-auth/expo", "@better-auth/i18n", "@better-auth/oauth-provider", "@better-auth/passkey", @@ -224,4 +223,20 @@ describe("managed OAuth provider policy", () => { pluginIds?.indexOf("custom-session") ?? -1, ); }); + + // The installed app signs in through `/mobile-auth` and a PKCE-bound code. + // Expo's server plugin would add an authorization proxy and copy the session + // cookie into redirects to any trusted custom scheme. + it("serves no Expo authorization proxy and trusts only web origins", async () => { + const { auth } = await import("./auth"); + const pluginIds = auth.options.plugins?.map((plugin) => plugin.id); + + expect(pluginIds).not.toContain("expo"); + expect(auth.api).not.toHaveProperty("expoAuthorizationProxy"); + expect(auth.options.trustedOrigins).not.toHaveLength(0); + for (const origin of auth.options.trustedOrigins as string[]) { + expect(new URL(origin).origin).toBe(origin); + expect(["http:", "https:"]).toContain(new URL(origin).protocol); + } + }); }); diff --git a/apps/api/src/auth.ts b/apps/api/src/auth.ts index 4614789dc..f714af756 100644 --- a/apps/api/src/auth.ts +++ b/apps/api/src/auth.ts @@ -1,4 +1,3 @@ -import { expo } from "@better-auth/expo"; import { i18n } from "@better-auth/i18n"; import { oauthProvider } from "@better-auth/oauth-provider"; import { passkey } from "@better-auth/passkey"; @@ -98,13 +97,10 @@ const authOptions = { }), baseURL: envString("BETTER_AUTH_URL", "http://localhost:3001"), secret, - trustedOrigins: [ - ...configuredTrustedWebOrigins(), - "openmapx://", - ...(process.env.NODE_ENV === "development" - ? ["exp://", "exp://**", "exp://192.168.*.*:*/**"] - : []), - ], + // Web origins only. The installed app never receives a Better Auth redirect: + // `/mobile-auth` returns to the app's scheme itself, with a PKCE-bound code + // instead of a session, so no custom scheme belongs in this list. + trustedOrigins: configuredTrustedWebOrigins(), appName: "OpenMapX", user: { deleteUser: { @@ -278,9 +274,6 @@ const authOptions = { // only legitimate holder redeems it immediately. Stored hashed so a database // read never yields a usable token. oneTimeToken({ expiresIn: 2, storeToken: "hashed" }), - // Kept for server-side compatibility with Expo's redirect handling. No Expo - // native auth client is initialised: native never holds a session. - expo(), emailHarmony({ allowNormalizedSignin: true }), passkey({ rpID: envString("PASSKEY_RP_ID", "localhost"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6568527e9..f688ed907 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,11 +195,6 @@ overrides: jest-expo>jest-watch-typeahead: ^3.0.1 expo-asset>expo-constants: 57.0.20 react-native: 0.86.3 - '@better-auth/expo>expo-constants': '-' - '@better-auth/expo>expo-linking': '-' - '@better-auth/expo>expo-network': '-' - '@better-auth/expo>expo-secure-store': '-' - '@better-auth/expo>expo-web-browser': '-' better-auth>drizzle-kit: '-' better-auth>next: '-' better-auth>vitest: '-' @@ -282,9 +277,6 @@ importers: '@better-auth/core': specifier: 1.7.16 version: 1.7.16(@better-auth/utils@0.5.1)(@better-fetch/fetch@1.3.3)(@opentelemetry/api@1.10.0)(better-call@1.4.5(zod@4.6.7))(jose@6.2.13)(kysely@0.29.7)(nanostores@1.5.5) - '@better-auth/expo': - specifier: 1.7.16 - version: 1.7.16(@better-auth/core@1.7.16(@better-auth/utils@0.5.1)(@better-fetch/fetch@1.3.3)(@opentelemetry/api@1.10.0)(better-call@1.4.5(zod@4.6.7))(jose@6.2.13)(kysely@0.29.7)(nanostores@1.5.5))(better-auth@1.7.16(@opentelemetry/api@1.10.0)(drizzle-orm@0.45.4(@opentelemetry/api@1.10.0)(kysely@0.29.7)(postgres@3.4.9))(react-dom@19.3.1(react@19.3.1))(react@19.3.1)) '@better-auth/i18n': specifier: 1.7.16 version: 1.7.16(@better-auth/core@1.7.16(@better-auth/utils@0.5.1)(@better-fetch/fetch@1.3.3)(@opentelemetry/api@1.10.0)(better-call@1.4.5(zod@4.6.7))(jose@6.2.13)(kysely@0.29.7)(nanostores@1.5.5))(better-auth@1.7.16(@opentelemetry/api@1.10.0)(drizzle-orm@0.45.4(@opentelemetry/api@1.10.0)(kysely@0.29.7)(postgres@3.4.9))(react-dom@19.3.1(react@19.3.1))(react@19.3.1)) @@ -3676,12 +3668,6 @@ packages: drizzle-orm: optional: true - '@better-auth/expo@1.7.16': - resolution: {integrity: sha512-kThN34FJzXaF/bKNel5oXAP5DbuDp9DkiTe3h5Vm9LeJa7rN3kujaSC71NmMJpzQ+Y5ypeV2o5unRVVPzqucAw==} - peerDependencies: - '@better-auth/core': ^1.7.16 - better-auth: ^1.7.16 - '@better-auth/i18n@1.7.16': resolution: {integrity: sha512-cY/S6oq8QT4xcnDzTLH4JF/w1+GNbj8YBsFmMmNvt6u7A+PH7ROaIjJm2ZA19H8IVC6Vx1dvO1ji1u4Xv+RlLQ==} peerDependencies: @@ -13016,14 +13002,6 @@ snapshots: optionalDependencies: drizzle-orm: 0.45.4(@opentelemetry/api@1.10.0)(kysely@0.29.7)(postgres@3.4.9) - '@better-auth/expo@1.7.16(@better-auth/core@1.7.16(@better-auth/utils@0.5.1)(@better-fetch/fetch@1.3.3)(@opentelemetry/api@1.10.0)(better-call@1.4.5(zod@4.6.7))(jose@6.2.13)(kysely@0.29.7)(nanostores@1.5.5))(better-auth@1.7.16(@opentelemetry/api@1.10.0)(drizzle-orm@0.45.4(@opentelemetry/api@1.10.0)(kysely@0.29.7)(postgres@3.4.9))(react-dom@19.3.1(react@19.3.1))(react@19.3.1))': - dependencies: - '@better-auth/core': 1.7.16(@better-auth/utils@0.5.1)(@better-fetch/fetch@1.3.3)(@opentelemetry/api@1.10.0)(better-call@1.4.5(zod@4.6.7))(jose@6.2.13)(kysely@0.29.7)(nanostores@1.5.5) - '@better-fetch/fetch': 1.3.3 - better-auth: 1.7.16(@opentelemetry/api@1.10.0)(drizzle-orm@0.45.4(@opentelemetry/api@1.10.0)(kysely@0.29.7)(postgres@3.4.9))(react-dom@19.3.1(react@19.3.1))(react@19.3.1) - better-call: 1.4.5(zod@4.6.7) - zod: 4.6.7 - '@better-auth/i18n@1.7.16(@better-auth/core@1.7.16(@better-auth/utils@0.5.1)(@better-fetch/fetch@1.3.3)(@opentelemetry/api@1.10.0)(better-call@1.4.5(zod@4.6.7))(jose@6.2.13)(kysely@0.29.7)(nanostores@1.5.5))(better-auth@1.7.16(@opentelemetry/api@1.10.0)(drizzle-orm@0.45.4(@opentelemetry/api@1.10.0)(kysely@0.29.7)(postgres@3.4.9))(react-dom@19.3.1(react@19.3.1))(react@19.3.1))': dependencies: '@better-auth/core': 1.7.16(@better-auth/utils@0.5.1)(@better-fetch/fetch@1.3.3)(@opentelemetry/api@1.10.0)(better-call@1.4.5(zod@4.6.7))(jose@6.2.13)(kysely@0.29.7)(nanostores@1.5.5) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4a70cb8ae..7fd5e7c89 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -41,7 +41,6 @@ minimumReleaseAgeExclude: - "better-auth@1.7.16" - "@better-auth/core@1.7.16" - "@better-auth/drizzle-adapter@1.7.16" - - "@better-auth/expo@1.7.16" - "@better-auth/i18n@1.7.16" - "@better-auth/kysely-adapter@1.7.16" - "@better-auth/memory-adapter@1.7.16" @@ -168,14 +167,9 @@ overrides: # installs it. These peers serve entry points nothing here imports, and the # API's production install carried them: Expo, React Native and Hermes # through drizzle-orm's `drizzle-orm/expo-sqlite` (only apps/mobile installs - # expo-sqlite, and it does not use drizzle-orm) and @better-auth/expo's - # `/client`; Next.js, Vue and Vitest through better-auth's `/next-js`, `/vue` - # and `/test`. Remove an entry before importing the entry point it serves. - "@better-auth/expo>expo-constants": "-" - "@better-auth/expo>expo-linking": "-" - "@better-auth/expo>expo-network": "-" - "@better-auth/expo>expo-secure-store": "-" - "@better-auth/expo>expo-web-browser": "-" + # expo-sqlite, and it does not use drizzle-orm); Next.js, Vue and Vitest + # through better-auth's `/next-js`, `/vue` and `/test`. Remove an entry + # before importing the entry point it serves. "better-auth>drizzle-kit": "-" "better-auth>next": "-" "better-auth>vitest": "-"