From cf65b2cb60ca8c718c52aae161d4522863a15244 Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Mon, 11 Aug 2025 05:16:58 +0530 Subject: [PATCH] fix: remove deep array merge when merging hooks context (#3160) * Update to-auth-endpoints.ts fixed * biome error * first commit * fix(org): use correct slug lookup when setting active org * docs: adding reference to surrealdb adapter rewrite (#3754) Adding reference to surrealdb adapter rewrite, with passing tests and schema generation as well as surreal native parameters. * chore(demo): replace isLoading using `useTransition` (#3775) * fix: use `useTransition` for isLoading * fixup! fix: use `useTransition` for isLoading * fix(reddit): refresh access token should use `basic` auth (#3753) * reddit auth type * changesets * chore: remove changeset --------- Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> * docs: unify component implementations for consistency (#3799) * fix(docs): unify component implementations for consistency * update * fix: add missing team reference in teamMember schema (#3801) * fix(organization): team members should be cleaned up on delete team (#3833) * feat(stripe): create billing portal session (#3625) * feat(stripe) : add billing portal session endpoint * create billing portal session * update docs * chore: changeset * chore: fix stripe * chore: remove comment * chore: update docs --------- Co-authored-by: Bereket Engida Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> * fix(cli): prisma schema generate mismatch on custom plugin table names (#3021) * fix: generate mismatch * clean up * linkt * lint * clean up * clean up * lint * lint * test * clean up * clean up * lint * update * docs: update TOTP 2FA docs to state that Better-Auth implements a delay window (#3586) Better Auth uses a delay window for TOTP code verification to ensure that it is resilient to time delays * docs: remove unused import of 'auth' type in authClient setup (#3856) In the previous implementation, the import statement for 'auth' type from './auth' was included as part of the setup for the authClient, but it is no longer being used in the current code. This update removes the unnecessary import to clean up the code and prevent potential confusion. Additionally, the authClient setup remains intact with the use of 'inferAdditionalFields' plugin to define additional user fields (role type set to 'string'). - Removed unused import: `import type { auth } from "./auth"` - Kept the authClient initialization with 'inferAdditionalFields' plugin This resolves a minor issue and ensures that the code remains clean and maintainable. * docs: enhance trustedOrigins option with examples (#3857) * docs: enhance trustedOrigins option documentation * nit * docs: fix typo in SSR performance section The line was missing "the". Co-authored-by: KinfeMichael Tariku <65047246+Kinfe123@users.noreply.github.com> * chore: update api key docs (#3864) Closes #3724 * docs: fix google auth prompt (#3894) Google OAuth2 uses space delimiters instead of + https://developers.google.com/identity/protocols/oauth2/web-server * docs(hono): update RPC client to use init config instead of custom fetch (#3597) * add test * chore: lint --------- Co-authored-by: KinfeMichael Tariku <65047246+Kinfe123@users.noreply.github.com> Co-authored-by: Manuel Sanchez Co-authored-by: Alex Yang Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> Co-authored-by: dogus <147812199+rhitune2@users.noreply.github.com> Co-authored-by: Bereket Engida Co-authored-by: Hysterelius <71912794+Hysterelius@users.noreply.github.com> Co-authored-by: Jovan Lanutan Co-authored-by: Nate <37544626+ntedvs@users.noreply.github.com> Co-authored-by: Dagmawi Babi <44819176+dagmawibabi@users.noreply.github.com> Co-authored-by: Braden Wong <13159333+braden-w@users.noreply.github.com> --- examples/tanstack-example/app/lib/auth.ts | 8 +- .../tanstack-example/app/routes/__root.tsx | 428 +++++++++--------- examples/tanstack-example/biome.json | 42 -- .../src/api/to-auth-endpoints.test.ts | 33 ++ .../better-auth/src/api/to-auth-endpoints.ts | 13 +- .../better-auth/src/cookies/cookies.test.ts | 20 +- 6 files changed, 267 insertions(+), 277 deletions(-) delete mode 100644 examples/tanstack-example/biome.json diff --git a/examples/tanstack-example/app/lib/auth.ts b/examples/tanstack-example/app/lib/auth.ts index c3a01105d6..a10e59cfad 100644 --- a/examples/tanstack-example/app/lib/auth.ts +++ b/examples/tanstack-example/app/lib/auth.ts @@ -10,13 +10,13 @@ export const auth = betterAuth({ socialProviders: { discord: { enabled: true, - clientId: process.env.DISCORD_CLIENT_ID!, - clientSecret: process.env.DISCORD_CLIENT_SECRET!, + clientId: process.env.DISCORD_CLIENT_ID as string, + clientSecret: process.env.DISCORD_CLIENT_SECRET as string, }, github: { enabled: true, - clientId: process.env.GITHUB_CLIENT_ID!, - clientSecret: process.env.GITHUB_CLIENT_SECRET!, + clientId: process.env.GITHUB_CLIENT_ID as string, + clientSecret: process.env.GITHUB_CLIENT_SECRET as string, }, }, plugins: [twoFactor()], diff --git a/examples/tanstack-example/app/routes/__root.tsx b/examples/tanstack-example/app/routes/__root.tsx index f2ee26d4b8..57ab754d23 100644 --- a/examples/tanstack-example/app/routes/__root.tsx +++ b/examples/tanstack-example/app/routes/__root.tsx @@ -76,229 +76,227 @@ function RootComponent() { return ( - <> - - - + )} + + + + ); diff --git a/examples/tanstack-example/biome.json b/examples/tanstack-example/biome.json deleted file mode 100644 index 473ff277b5..0000000000 --- a/examples/tanstack-example/biome.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "root": false, - "$schema": "https://biomejs.dev/schemas/2.1.3/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "includes": [ - "**", - "!**/dist", - "!**/build", - "!**/.next", - "!**/.svelte-kit", - "!**/package.json", - "!**/.contentlayer", - "!**/.turbo", - "!**/.nuxt", - "!**/.source", - "!**/.expo", - "!**/.cache", - "!**/dev/cloudflare/drizzle" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, - "assist": { "actions": { "source": { "organizeImports": "on" } } }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - } -} diff --git a/packages/better-auth/src/api/to-auth-endpoints.test.ts b/packages/better-auth/src/api/to-auth-endpoints.test.ts index d600341d21..17f549b692 100644 --- a/packages/better-auth/src/api/to-auth-endpoints.test.ts +++ b/packages/better-auth/src/api/to-auth-endpoints.test.ts @@ -140,6 +140,39 @@ describe("before hook", async () => { }); expect(res).toMatchObject({ key: "value", name: "headers" }); }); + + it("should replace existing array when hook provides another array", async () => { + const endpoint = { + body: createAuthEndpoint( + "/body-array-replace", + { method: "POST", body: z.object({ tags: z.array(z.string()) }) }, + async (c) => c.body, + ), + }; + const authContext = init({ + hooks: { + before: createAuthMiddleware(async (c) => { + if (c.path === "/body-array-replace") { + return { + context: { + body: { + tags: ["a"], + }, + }, + }; + } + }), + }, + }); + const api = toAuthEndpoints(endpoint, authContext); + + const res = await api.body({ + body: { + tags: ["b", "c"], + }, + }); + expect(res.tags).toEqual(["a"]); + }); }); describe("response", async () => { diff --git a/packages/better-auth/src/api/to-auth-endpoints.ts b/packages/better-auth/src/api/to-auth-endpoints.ts index 6101427e59..0992b534f8 100644 --- a/packages/better-auth/src/api/to-auth-endpoints.ts +++ b/packages/better-auth/src/api/to-auth-endpoints.ts @@ -7,7 +7,7 @@ import { } from "better-call"; import type { AuthEndpoint, AuthMiddleware } from "./call"; import type { AuthContext, HookEndpointContext } from "../types"; -import defu from "defu"; +import { createDefu } from "defu"; type InternalContext = InputContext & EndpointContext & { @@ -17,6 +17,12 @@ type InternalContext = InputContext & responseHeaders?: Headers; }; }; +const defuReplaceArrays = createDefu((obj, key, value) => { + if (Array.isArray(obj[key]) && Array.isArray(value)) { + obj[key] = value; + return true; + } +}); export function toAuthEndpoints>( endpoints: E, @@ -70,7 +76,7 @@ export function toAuthEndpoints>( (internalContext.headers as Headers).set(key, value); }); } - internalContext = defu(rest, internalContext); + internalContext = defuReplaceArrays(rest, internalContext); } else if (before) { /* Return before hook response if it's anything other than a context return */ return before; @@ -154,7 +160,8 @@ async function runBeforeHooks( modifiedContext.headers = headers; } } - modifiedContext = defu(rest, modifiedContext); + modifiedContext = defuReplaceArrays(rest, modifiedContext); + continue; } return result; diff --git a/packages/better-auth/src/cookies/cookies.test.ts b/packages/better-auth/src/cookies/cookies.test.ts index baea6d3c1c..167741f6ce 100644 --- a/packages/better-auth/src/cookies/cookies.test.ts +++ b/packages/better-auth/src/cookies/cookies.test.ts @@ -242,7 +242,7 @@ describe("getSessionCookie", async () => { expect(cookies).not.toBeNull(); }); - it("should retun cookie cache", async () => { + it("should return cookie cache", async () => { const { client, testUser, cookieSetter } = await getTestInstance({ secret: "better-auth.secret", session: { @@ -251,7 +251,9 @@ describe("getSessionCookie", async () => { }, }, }); + const headers = new Headers(); + await client.signIn.email( { email: testUser.email, @@ -261,25 +263,17 @@ describe("getSessionCookie", async () => { onSuccess: cookieSetter(headers), }, ); + const request = new Request("https://example.com/api/auth/session", { headers, }); + const cache = await getCookieCache(request, { secret: "better-auth.secret", }); - console.log(cache); expect(cache).not.toBeNull(); - expect(cache).toMatchObject({ - user: { - id: expect.any(String), - email: expect.any(String), - emailVerified: expect.any(Boolean), - }, - session: { - expiresAt: expect.any(Date), - token: expect.any(String), - }, - }); + expect(cache?.user?.email).toEqual(testUser.email); + expect(cache?.session?.token).toEqual(expect.any(String)); }); it("should return null if the cookie is invalid", async () => {