From de50dfcb6fe3b6e5ff80fa3d9cbb2bae2f952ece Mon Sep 17 00:00:00 2001 From: Bereket Engida <86073083+Bekacru@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:43:25 +0300 Subject: [PATCH] fix: enforce override user info on oauth signin (#2294) --- .../better-auth/src/api/routes/callback.ts | 1 + .../better-auth/src/oauth2/link-account.ts | 3 +- .../src/social-providers/social.test.ts | 136 +++++++++++++++++- 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/packages/better-auth/src/api/routes/callback.ts b/packages/better-auth/src/api/routes/callback.ts index 905bf1591a..040e2867ef 100644 --- a/packages/better-auth/src/api/routes/callback.ts +++ b/packages/better-auth/src/api/routes/callback.ts @@ -189,6 +189,7 @@ export const callbackOAuth = createAuthEndpoint( disableSignUp: (provider.disableImplicitSignUp && !requestSignUp) || provider.options?.disableSignUp, + overrideUserInfo: provider.options?.overrideUserInfoOnSignIn, }); if (result.error) { c.context.logger.error(result.error.split(" ").join("_")); diff --git a/packages/better-auth/src/oauth2/link-account.ts b/packages/better-auth/src/oauth2/link-account.ts index eb0a9baab4..fc1198a219 100644 --- a/packages/better-auth/src/oauth2/link-account.ts +++ b/packages/better-auth/src/oauth2/link-account.ts @@ -105,9 +105,10 @@ export async function handleOAuthUserInfo( } } if (overrideUserInfo) { + const { id: _, ...restUserInfo } = userInfo; // update user info from the provider if overrideUserInfo is true await c.context.internalAdapter.updateUser(dbUser.user.id, { - ...userInfo, + ...restUserInfo, email: userInfo.email.toLowerCase(), emailVerified: userInfo.email.toLocaleLowerCase() === dbUser.user.email diff --git a/packages/better-auth/src/social-providers/social.test.ts b/packages/better-auth/src/social-providers/social.test.ts index 2995e9d6f6..5a9b758438 100644 --- a/packages/better-auth/src/social-providers/social.test.ts +++ b/packages/better-auth/src/social-providers/social.test.ts @@ -7,6 +7,8 @@ import { getOAuth2Tokens, refreshAccessToken } from "../oauth2"; import { signJWT } from "../crypto/jwt"; import { OAuth2Server } from "oauth2-mock-server"; import { betterFetch } from "@better-fetch/fetch"; +import Database from "better-sqlite3"; +import { getMigrations } from "../db"; let server = new OAuth2Server(); @@ -16,7 +18,33 @@ vi.mock("../oauth2", async (importOriginal) => { ...original, validateAuthorizationCode: vi .fn() - .mockImplementation(async (...args: any) => { + .mockImplementation(async (option: any) => { + if (option.options.overrideUserInfoOnSignIn) { + const data: GoogleProfile = { + email: "user@email.com", + email_verified: true, + name: "Updated User", + picture: "https://test.com/picture.png", + exp: 1234567890, + sub: "1234567890", + iat: 1234567890, + aud: "test", + azp: "test", + nbf: 1234567890, + iss: "test", + locale: "en", + jti: "test", + given_name: "Updated", + family_name: "User", + }; + const testIdToken = await signJWT(data, DEFAULT_SECRET); + const tokens = getOAuth2Tokens({ + access_token: "test", + refresh_token: "test", + id_token: testIdToken, + }); + return tokens; + } const data: GoogleProfile = { email: "user@email.com", email_verified: true, @@ -549,3 +577,109 @@ describe("Disable signup", async () => { }); }); }); + +describe("signin", async () => { + const database = new Database(":memory:"); + + beforeAll(async () => { + const migrations = await getMigrations({ + database, + }); + await migrations.runMigrations(); + }); + it("should allow user info override during sign in", async () => { + let state = ""; + const { client, cookieSetter } = await getTestInstance({ + database, + socialProviders: { + google: { + clientId: "test", + clientSecret: "test", + enabled: true, + }, + }, + }); + const signInRes = await client.signIn.social({ + provider: "google", + callbackURL: "/callback", + }); + expect(signInRes.data).toMatchObject({ + url: expect.stringContaining("google.com"), + redirect: true, + }); + state = new URL(signInRes.data!.url!).searchParams.get("state") || ""; + + const headers = new Headers(); + + await client.$fetch("/callback/google", { + query: { + state, + code: "test", + }, + method: "GET", + onError: (c) => { + cookieSetter(headers)(c as any); + }, + }); + + const session = await client.getSession({ + fetchOptions: { + headers, + }, + }); + expect(session.data?.user).toMatchObject({ + name: "First Last", + }); + }); + + it("should allow user info override during sign in", async () => { + let state = ""; + const { client, cookieSetter } = await getTestInstance( + { + database, + socialProviders: { + google: { + clientId: "test", + clientSecret: "test", + enabled: true, + overrideUserInfoOnSignIn: true, + }, + }, + }, + { + disableTestUser: true, + }, + ); + const signInRes = await client.signIn.social({ + provider: "google", + callbackURL: "/callback", + }); + expect(signInRes.data).toMatchObject({ + url: expect.stringContaining("google.com"), + redirect: true, + }); + state = new URL(signInRes.data!.url!).searchParams.get("state") || ""; + + const headers = new Headers(); + + await client.$fetch("/callback/google", { + query: { + state, + code: "test", + }, + method: "GET", + onError: (c) => { + cookieSetter(headers)(c as any); + }, + }); + + const session = await client.getSession({ + fetchOptions: { + headers, + }, + }); + expect(session.data?.user).toMatchObject({ + name: "Updated User", + }); + }); +});