From 0d3b9f12a7db86d98125118d77010fef45672a83 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 20 May 2026 10:52:50 +0200 Subject: [PATCH 1/4] fix: force token refresh and clear cookies on failure to avoid logout loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch the token-refresh route from `auth.api.getAccessToken` (which only refreshes inside Better Auth's 5s threshold) to `auth.api.refreshToken` (unconditional refresh). This eliminates the 5–10s race window where the caller's near-expiry margin would redirect to the route but Better Auth would return 200 OK with no Set-Cookie, sending the browser back into a redirect loop ("page isn't redirecting properly"). On refresh failure, call `auth.api.signOut` and forward its Set-Cookie headers onto the /signin redirect so `session_token` and `account_data` are actually cleared — otherwise the stale account_data cookie keeps `isTokenNearExpiry` returning true and the user can fall back into the loop on the next navigation. Also export the handler as both GET and POST: Next.js `redirect()` uses 307 (method-preserving) outside Server Actions, so a redirect triggered from a Server Component render that follows a Server Action POST would otherwise hit this route as POST and 405. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/api/auth/token-refresh/route.test.ts | 120 ++++++++++++++++--- src/app/api/auth/token-refresh/route.ts | 88 +++++++++----- 2 files changed, 165 insertions(+), 43 deletions(-) diff --git a/src/app/api/auth/token-refresh/route.test.ts b/src/app/api/auth/token-refresh/route.test.ts index 4fb494b4..c800a9e6 100644 --- a/src/app/api/auth/token-refresh/route.test.ts +++ b/src/app/api/auth/token-refresh/route.test.ts @@ -1,8 +1,9 @@ import { NextRequest } from "next/server"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { GET } from "./route"; +import { GET, POST } from "./route"; -const mockGetAccessToken = vi.hoisted(() => vi.fn()); +const mockRefreshToken = vi.hoisted(() => vi.fn()); +const mockSignOut = vi.hoisted(() => vi.fn()); const mockHeaders = vi.hoisted(() => vi.fn()); vi.mock("next/headers", () => ({ @@ -11,7 +12,10 @@ vi.mock("next/headers", () => ({ vi.mock("@/lib/auth/auth", () => ({ auth: { - api: { getAccessToken: mockGetAccessToken }, + api: { + refreshToken: mockRefreshToken, + signOut: mockSignOut, + }, }, })); @@ -21,31 +25,42 @@ function makeRequest(path = INTERNAL_URL) { return new NextRequest(path); } -function mockTokenSuccess(cookies: string[] = []) { - mockGetAccessToken.mockResolvedValue({ +function mockRefreshSuccess(cookies: string[] = []) { + mockRefreshToken.mockResolvedValue({ ok: true, status: 200, headers: { getSetCookie: () => cookies }, }); } -function mockTokenFailure(status = 401) { - mockGetAccessToken.mockResolvedValue({ +function mockRefreshFailure(status = 400) { + mockRefreshToken.mockResolvedValue({ ok: false, status, headers: { getSetCookie: () => [] }, }); } +function mockSignOutSuccess(cookies: string[] = []) { + mockSignOut.mockResolvedValue({ + ok: true, + status: 200, + headers: { getSetCookie: () => cookies }, + }); +} + describe("GET /api/auth/token-refresh", () => { beforeEach(() => { vi.clearAllMocks(); mockHeaders.mockResolvedValue(new Headers()); + // Default: signOut succeeds with no cookies (covers tests that don't care + // about the cleanup path). + mockSignOutSuccess(); }); describe("redirect URL uses BASE_URL, not request.url", () => { it("redirects to BASE_URL/catalog on success (not 0.0.0.0)", async () => { - mockTokenSuccess(); + mockRefreshSuccess(); const response = await GET( makeRequest(`${INTERNAL_URL}?redirect=%2Fcatalog`), ); @@ -56,7 +71,7 @@ describe("GET /api/auth/token-refresh", () => { it("redirects to BASE_URL/signin on token refresh failure", async () => { vi.spyOn(console, "warn").mockImplementation(() => {}); - mockTokenFailure(); + mockRefreshFailure(); const response = await GET(makeRequest(INTERNAL_URL)); expect(response.headers.get("location")).toBe( "http://localhost:3000/signin", @@ -65,7 +80,7 @@ describe("GET /api/auth/token-refresh", () => { it("redirects to BASE_URL/signin on unexpected error", async () => { vi.spyOn(console, "error").mockImplementation(() => {}); - mockGetAccessToken.mockRejectedValue(new Error("Network error")); + mockRefreshToken.mockRejectedValue(new Error("Network error")); const response = await GET(makeRequest(INTERNAL_URL)); expect(response.headers.get("location")).toBe( "http://localhost:3000/signin", @@ -75,7 +90,7 @@ describe("GET /api/auth/token-refresh", () => { describe("open redirect protection", () => { it("falls back to /catalog when no redirect param", async () => { - mockTokenSuccess(); + mockRefreshSuccess(); const response = await GET(makeRequest(INTERNAL_URL)); expect(response.headers.get("location")).toBe( "http://localhost:3000/catalog", @@ -83,7 +98,7 @@ describe("GET /api/auth/token-refresh", () => { }); it("falls back to /catalog for redirect starting with //", async () => { - mockTokenSuccess(); + mockRefreshSuccess(); const response = await GET( makeRequest(`${INTERNAL_URL}?redirect=//evil.com`), ); @@ -93,7 +108,7 @@ describe("GET /api/auth/token-refresh", () => { }); it("falls back to /catalog for redirect to external origin", async () => { - mockTokenSuccess(); + mockRefreshSuccess(); const response = await GET( makeRequest( `${INTERNAL_URL}?redirect=${encodeURIComponent("https://evil.com/phishing")}`, @@ -105,7 +120,7 @@ describe("GET /api/auth/token-refresh", () => { }); it("accepts valid internal path with query string", async () => { - mockTokenSuccess(); + mockRefreshSuccess(); const response = await GET( makeRequest( `${INTERNAL_URL}?redirect=${encodeURIComponent("/catalog?page=2")}`, @@ -121,7 +136,7 @@ describe("GET /api/auth/token-refresh", () => { it("copies Set-Cookie headers from Better Auth response onto the redirect", async () => { const cookie = "__Secure-better-auth.account_data=newvalue; Path=/; HttpOnly; SameSite=Lax"; - mockTokenSuccess([cookie]); + mockRefreshSuccess([cookie]); const response = await GET( makeRequest(`${INTERNAL_URL}?redirect=%2Fcatalog`), ); @@ -133,7 +148,7 @@ describe("GET /api/auth/token-refresh", () => { "__Secure-better-auth.account_data=val1; Path=/; HttpOnly", "__Secure-better-auth.session_token=val2; Path=/; HttpOnly", ]; - mockTokenSuccess(cookies); + mockRefreshSuccess(cookies); const response = await GET( makeRequest(`${INTERNAL_URL}?redirect=%2Fcatalog`), ); @@ -143,4 +158,77 @@ describe("GET /api/auth/token-refresh", () => { expect(setCookieHeader[1]).toBe(cookies[1]); }); }); + + describe("cookie cleanup on failure", () => { + it("forwards signOut Set-Cookie headers when refresh fails", async () => { + vi.spyOn(console, "warn").mockImplementation(() => {}); + mockRefreshFailure(); + const signOutCookies = [ + "__Secure-better-auth.session_token=; Path=/; Max-Age=0", + "__Secure-better-auth.account_data=; Path=/; Max-Age=0", + ]; + mockSignOutSuccess(signOutCookies); + + const response = await GET(makeRequest(INTERNAL_URL)); + + expect(response.headers.get("location")).toBe( + "http://localhost:3000/signin", + ); + const setCookieHeader = response.headers.getSetCookie(); + expect(setCookieHeader).toHaveLength(2); + expect(setCookieHeader).toEqual(signOutCookies); + }); + + it("forwards signOut Set-Cookie headers when refresh throws", async () => { + vi.spyOn(console, "error").mockImplementation(() => {}); + mockRefreshToken.mockRejectedValue(new Error("Network error")); + const signOutCookies = [ + "__Secure-better-auth.session_token=; Path=/; Max-Age=0", + ]; + mockSignOutSuccess(signOutCookies); + + const response = await GET(makeRequest(INTERNAL_URL)); + + expect(response.headers.get("location")).toBe( + "http://localhost:3000/signin", + ); + expect(response.headers.get("set-cookie")).toBe(signOutCookies[0]); + }); + + it("still redirects to /signin when signOut itself fails", async () => { + vi.spyOn(console, "warn").mockImplementation(() => {}); + vi.spyOn(console, "error").mockImplementation(() => {}); + mockRefreshFailure(); + mockSignOut.mockRejectedValue(new Error("signOut boom")); + + const response = await GET(makeRequest(INTERNAL_URL)); + + expect(response.headers.get("location")).toBe( + "http://localhost:3000/signin", + ); + }); + + it("calls refreshToken with the configured providerId", async () => { + mockRefreshSuccess(); + await GET(makeRequest(`${INTERNAL_URL}?redirect=%2Fcatalog`)); + expect(mockRefreshToken).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ providerId: expect.any(String) }), + asResponse: true, + }), + ); + }); + }); + + describe("HTTP method parity", () => { + it("POST handler behaves identically to GET (no 405 on Server Action 307 redirects)", async () => { + mockRefreshSuccess(); + const response = await POST( + makeRequest(`${INTERNAL_URL}?redirect=%2Fcatalog`), + ); + expect(response.headers.get("location")).toBe( + "http://localhost:3000/catalog", + ); + }); + }); }); diff --git a/src/app/api/auth/token-refresh/route.ts b/src/app/api/auth/token-refresh/route.ts index 97ad3c97..b78befe5 100644 --- a/src/app/api/auth/token-refresh/route.ts +++ b/src/app/api/auth/token-refresh/route.ts @@ -12,16 +12,27 @@ const BASE_ORIGIN = new URL(BASE_URL).origin; * the rotated refresh token that Better Auth places in Set-Cookie headers * after a token refresh. This Route Handler acts as a proxy: * - * 1. Calls `auth.api.getAccessToken({ asResponse: true })` to trigger the refresh. - * 2. Copies the resulting Set-Cookie headers directly onto an HTTP redirect response. - * (Route Handlers CAN set cookies via the HTTP response, unlike Server Components.) + * 1. Calls `auth.api.refreshToken({ asResponse: true })` to FORCE a refresh. + * Unlike `getAccessToken`, this bypasses Better Auth's internal 5s threshold, + * which is what previously caused a redirect loop when the caller's near-expiry + * margin (e.g. 10s) was wider than Better Auth's refresh window: the route + * would be re-entered but no refresh (and no Set-Cookie) would occur. + * 2. Copies the resulting Set-Cookie headers onto the HTTP redirect response so + * the browser stores the rotated refresh token. (Route Handlers CAN set cookies + * via the HTTP response, unlike Server Components.) * 3. Redirects the browser back to the original page. * - * The browser follows the redirect, the `Set-Cookie` headers update the - * `account_data` cookie with the rotated refresh token (R2), and the page - * renders with a fresh, valid token. + * If the refresh fails (e.g. the refresh token has been revoked at the provider), + * the handler signs the user out via `auth.api.signOut` — which clears both + * `session_token` and `account_data` cookies — and redirects to /signin. Without + * this cleanup, the stale `account_data` cookie keeps `isTokenNearExpiry` returning + * true on every subsequent request, trapping the user in a refresh loop. + * + * Both GET and POST are exported: Next.js `redirect()` outside a Server Action uses + * 307 (method-preserving), so a redirect triggered from a Server Component render + * that follows a Server Action POST reaches this route as a POST. */ -export async function GET(request: NextRequest) { +async function handler(request: NextRequest) { // Validate redirect target to prevent open redirects. // Parse with new URL() and enforce same-origin; extract only pathname+search+hash. const redirectParam = request.nextUrl.searchParams.get("redirect"); @@ -40,18 +51,15 @@ export async function GET(request: NextRequest) { const requestHeaders = await headers(); try { - const tokenResponse = await auth.api.getAccessToken({ + const tokenResponse = await auth.api.refreshToken({ headers: requestHeaders, body: { providerId: OIDC_PROVIDER_ID }, asResponse: true, }); if (!tokenResponse.ok) { - console.warn( - "[TokenRefresh] getAccessToken failed:", - tokenResponse.status, - ); - return NextResponse.redirect(new URL("/signin", BASE_URL)); + console.warn("[TokenRefresh] refreshToken failed:", tokenResponse.status); + return await signOutAndRedirect(requestHeaders); } const redirectResponse = NextResponse.redirect( @@ -62,25 +70,51 @@ export async function GET(request: NextRequest) { // onto the HTTP redirect response. This is the correct mechanism to propagate // the rotated refresh token (R2) to the browser cookie — Route Handlers can // write cookies via the HTTP response, unlike Server Components. - const betterAuthHeaders = - tokenResponse.headers as typeof tokenResponse.headers & { - getSetCookie?: () => string[]; - }; - const setCookieHeaders = - typeof betterAuthHeaders.getSetCookie === "function" - ? betterAuthHeaders.getSetCookie() - : (() => { - const raw = tokenResponse.headers.get("set-cookie"); - return raw ? [raw] : []; - })(); - - for (const cookie of setCookieHeaders) { + for (const cookie of extractSetCookies(tokenResponse)) { redirectResponse.headers.append("set-cookie", cookie); } return redirectResponse; } catch (err) { console.error("[TokenRefresh] Unexpected error:", err); - return NextResponse.redirect(new URL("/signin", BASE_URL)); + return await signOutAndRedirect(requestHeaders); } } + +/** + * Signs the user out (clearing session + account_data cookies via Better Auth) + * and redirects to /signin. Used when the refresh attempt fails irrecoverably. + */ +async function signOutAndRedirect( + requestHeaders: Headers, +): Promise { + const response = NextResponse.redirect(new URL("/signin", BASE_URL)); + try { + const signOutResponse = await auth.api.signOut({ + headers: requestHeaders, + asResponse: true, + }); + for (const cookie of extractSetCookies(signOutResponse)) { + response.headers.append("set-cookie", cookie); + } + } catch (err) { + // signOut should not fail in practice — it clears cookies even with no + // active session. Log so we can spot it, but still redirect to /signin. + console.error("[TokenRefresh] signOut failed during cleanup:", err); + } + return response; +} + +function extractSetCookies(response: Response): string[] { + const headers = response.headers as Response["headers"] & { + getSetCookie?: () => string[]; + }; + if (typeof headers.getSetCookie === "function") { + return headers.getSetCookie(); + } + const raw = response.headers.get("set-cookie"); + return raw ? [raw] : []; +} + +export const GET = handler; +export const POST = handler; From 1fb59b64ea69aa5fc66b2b823be79fdce1d015e2 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 20 May 2026 10:56:39 +0200 Subject: [PATCH 2/4] chore: bump next + transitive deps to clear pnpm audit advisories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - next 16.2.3 → 16.2.6 (clears GHSA advisories for App Router middleware bypass, image-API DoS, SSRF, RSC cache poisoning, beforeInteractive XSS, etc.) - pnpm.overrides: hono ≥4.12.18 (cache middleware Vary leakage, CSS-in-style injection, JWT NumericDate validation) - pnpm.overrides: kysely ≥0.28.17 (JSON-path traversal) - pnpm.overrides: fast-uri ≥3.1.2 (path traversal + host confusion via ajv) - pnpm.overrides: ip-address ≥10.1.1 (XSS in Address6 HTML emitters via @modelcontextprotocol/sdk → express-rate-limit) `pnpm audit` now reports no known vulnerabilities. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 10 ++- pnpm-lock.yaml | 200 ++++++++++++++++++++++++------------------------- 2 files changed, 103 insertions(+), 107 deletions(-) diff --git a/package.json b/package.json index 1b440c27..d32ee77a 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "lucide-react": "^0.577.0", "msw": "^2.12.2", "nanoid": "^5.1.6", - "next": "16.2.3", + "next": "16.2.6", "next-themes": "^0.4.6", "nuqs": "^2.8.1", "pg": "^8.13.3", @@ -116,18 +116,20 @@ "express-rate-limit": ">=8.2.2", "lodash": ">=4.17.23", "lodash-es": ">=4.17.23", - "hono": ">=4.12.14", + "hono": ">=4.12.18", "@hono/node-server": "^1.19.14", "defu": "^6.1.5", "rollup": ">=4.59.0", "undici": "7.24.6", - "kysely": ">=0.28.14", + "kysely": ">=0.28.17", "picomatch": ">=4.0.4", "qs": ">=6.14.2", "yaml": ">=2.8.3", "path-to-regexp@>=8.0.0 <8.4.0": "8.4.0", "path-to-regexp@>=0.1.0 <0.1.13": "0.1.13", - "postcss": ">=8.5.10" + "postcss": ">=8.5.10", + "fast-uri": ">=3.1.2", + "ip-address": ">=10.1.1" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf4109b0..98f914ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,18 +8,20 @@ overrides: express-rate-limit: '>=8.2.2' lodash: '>=4.17.23' lodash-es: '>=4.17.23' - hono: '>=4.12.14' + hono: '>=4.12.18' '@hono/node-server': ^1.19.14 defu: ^6.1.5 rollup: '>=4.59.0' undici: 7.24.6 - kysely: '>=0.28.14' + kysely: '>=0.28.17' picomatch: '>=4.0.4' qs: '>=6.14.2' yaml: '>=2.8.3' path-to-regexp@>=8.0.0 <8.4.0: 8.4.0 path-to-regexp@>=0.1.0 <0.1.13: 0.1.13 postcss: '>=8.5.10' + fast-uri: '>=3.1.2' + ip-address: '>=10.1.1' importers: @@ -99,7 +101,7 @@ importers: version: 3.0.1(ajv@8.18.0) better-auth: specifier: 1.6.2 - version: 1.6.2(@opentelemetry/api@1.9.0)(mongodb@7.1.0)(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(jsdom@29.0.0(@noble/hashes@2.0.1))(msw@2.12.13(@types/node@24.12.0)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 1.6.2(@opentelemetry/api@1.9.0)(mongodb@7.1.0)(next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(jsdom@29.0.0(@noble/hashes@2.0.1))(msw@2.12.13(@types/node@24.12.0)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))) class-variance-authority: specifier: 0.7.1 version: 0.7.1 @@ -122,14 +124,14 @@ importers: specifier: ^5.1.6 version: 5.1.7 next: - specifier: 16.2.3 - version: 16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 16.2.6 + version: 16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nuqs: specifier: ^2.8.1 - version: 2.8.9(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 2.8.9(next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) pg: specifier: ^8.13.3 version: 8.20.0 @@ -342,7 +344,7 @@ packages: '@opentelemetry/api': ^1.9.0 better-call: 1.3.5 jose: ^6.1.0 - kysely: '>=0.28.14' + kysely: '>=0.28.17' nanostores: ^1.0.1 peerDependenciesMeta: '@cloudflare/workers-types': @@ -363,7 +365,7 @@ packages: peerDependencies: '@better-auth/core': ^1.6.2 '@better-auth/utils': 0.4.0 - kysely: '>=0.28.14' + kysely: '>=0.28.17' peerDependenciesMeta: kysely: optional: true @@ -510,9 +512,6 @@ packages: '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@emnapi/runtime@1.9.2': resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} @@ -728,7 +727,7 @@ packages: resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.12.14' + hono: '>=4.12.18' '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} @@ -988,57 +987,57 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@16.2.3': - resolution: {integrity: sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==} + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} - '@next/swc-darwin-arm64@16.2.3': - resolution: {integrity: sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==} + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.3': - resolution: {integrity: sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==} + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.3': - resolution: {integrity: sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==} + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.3': - resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==} + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.3': - resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==} + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.3': - resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==} + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.3': - resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==} + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.3': - resolution: {integrity: sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==} + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2545,8 +2544,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -2679,8 +2678,8 @@ packages: headers-polyfill@4.0.3: resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - hono@4.12.16: - resolution: {integrity: sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==} + hono@4.12.21: + resolution: {integrity: sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==} engines: {node: '>=16.9.0'} html-encoding-sniffer@6.0.0: @@ -2731,8 +2730,8 @@ packages: inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -2885,9 +2884,9 @@ packages: resolution: {integrity: sha512-2LOQnFKu3m0VxpE+5sb5+BRTSKrXmNxGgxVRiKwD9s5KQB1zID/FRXhtzeV7RT1L2GVpdEEAfVuclFOMGl1ikA==} engines: {node: '>= 18'} - kysely@0.28.14: - resolution: {integrity: sha512-SU3lgh0rPvq7upc6vvdVrCsSMUG1h3ChvHVOY7wJ2fw4C9QEB7X3d5eyYEyULUX7UQtxZJtZXGuT6U2US72UYA==} - engines: {node: '>=20.0.0'} + kysely@0.29.2: + resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==} + engines: {node: '>=22.0.0'} lightningcss-android-arm64@1.31.1: resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} @@ -3359,8 +3358,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.2.3: - resolution: {integrity: sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==} + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -4403,7 +4402,7 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1)': + '@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1)': dependencies: '@better-auth/utils': 0.4.0 '@better-fetch/fetch': 1.1.21 @@ -4412,42 +4411,42 @@ snapshots: '@standard-schema/spec': 1.1.0 better-call: 1.3.5(zod@4.3.6) jose: 6.2.0 - kysely: 0.28.14 + kysely: 0.29.2 nanostores: 1.1.1 zod: 4.3.6 - '@better-auth/drizzle-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': + '@better-auth/drizzle-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 - '@better-auth/kysely-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.28.14)': + '@better-auth/kysely-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.29.2)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 optionalDependencies: - kysely: 0.28.14 + kysely: 0.29.2 - '@better-auth/memory-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': + '@better-auth/memory-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 - '@better-auth/mongo-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0)': + '@better-auth/mongo-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 optionalDependencies: mongodb: 7.1.0 - '@better-auth/prisma-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': + '@better-auth/prisma-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 - '@better-auth/telemetry@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)': + '@better-auth/telemetry@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 '@better-fetch/fetch': 1.1.21 @@ -4526,11 +4525,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.8.1': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.9.2': dependencies: tslib: 2.8.1 @@ -4689,9 +4683,9 @@ snapshots: '@hey-api/types@0.1.4': {} - '@hono/node-server@1.19.14(hono@4.12.16)': + '@hono/node-server@1.19.14(hono@4.12.21)': dependencies: - hono: 4.12.16 + hono: 4.12.21 '@img/colour@1.0.0': optional: true @@ -4778,7 +4772,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.8.1 + '@emnapi/runtime': 1.9.2 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -4863,7 +4857,7 @@ snapshots: '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.16) + '@hono/node-server': 1.19.14(hono@4.12.21) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 @@ -4873,7 +4867,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.3.1(express@5.2.1) - hono: 4.12.16 + hono: 4.12.21 jose: 6.2.0 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -4912,30 +4906,30 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.2.3': {} + '@next/env@16.2.6': {} - '@next/swc-darwin-arm64@16.2.3': + '@next/swc-darwin-arm64@16.2.6': optional: true - '@next/swc-darwin-x64@16.2.3': + '@next/swc-darwin-x64@16.2.6': optional: true - '@next/swc-linux-arm64-gnu@16.2.3': + '@next/swc-linux-arm64-gnu@16.2.6': optional: true - '@next/swc-linux-arm64-musl@16.2.3': + '@next/swc-linux-arm64-musl@16.2.6': optional: true - '@next/swc-linux-x64-gnu@16.2.3': + '@next/swc-linux-x64-gnu@16.2.6': optional: true - '@next/swc-linux-x64-musl@16.2.3': + '@next/swc-linux-x64-musl@16.2.6': optional: true - '@next/swc-win32-arm64-msvc@16.2.3': + '@next/swc-win32-arm64-msvc@16.2.6': optional: true - '@next/swc-win32-x64-msvc@16.2.3': + '@next/swc-win32-x64-msvc@16.2.6': optional: true '@noble/ciphers@2.1.1': {} @@ -5852,7 +5846,7 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -5908,15 +5902,15 @@ snapshots: baseline-browser-mapping@2.10.7: {} - better-auth@1.6.2(@opentelemetry/api@1.9.0)(mongodb@7.1.0)(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(jsdom@29.0.0(@noble/hashes@2.0.1))(msw@2.12.13(@types/node@24.12.0)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))): + better-auth@1.6.2(@opentelemetry/api@1.9.0)(mongodb@7.1.0)(next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(jsdom@29.0.0(@noble/hashes@2.0.1))(msw@2.12.13(@types/node@24.12.0)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))): dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1) - '@better-auth/drizzle-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0) - '@better-auth/kysely-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.28.14) - '@better-auth/memory-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0) - '@better-auth/mongo-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0) - '@better-auth/prisma-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0) - '@better-auth/telemetry': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) + '@better-auth/drizzle-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0) + '@better-auth/kysely-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.29.2) + '@better-auth/memory-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0) + '@better-auth/mongo-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0) + '@better-auth/prisma-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0) + '@better-auth/telemetry': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21) '@better-auth/utils': 0.4.0 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.1.1 @@ -5924,12 +5918,12 @@ snapshots: better-call: 1.3.5(zod@4.3.6) defu: 6.1.7 jose: 6.2.0 - kysely: 0.28.14 + kysely: 0.29.2 nanostores: 1.1.1 zod: 4.3.6 optionalDependencies: mongodb: 7.1.0 - next: 16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) pg: 8.20.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -6305,7 +6299,7 @@ snapshots: express-rate-limit@8.3.1(express@5.2.1): dependencies: express: 5.2.1 - ip-address: 10.1.0 + ip-address: 10.2.0 express@4.22.1: dependencies: @@ -6382,7 +6376,7 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-uri@3.1.0: {} + fast-uri@3.1.2: {} fdir@6.5.0(picomatch@4.0.4): optionalDependencies: @@ -6579,7 +6573,7 @@ snapshots: headers-polyfill@4.0.3: {} - hono@4.12.16: {} + hono@4.12.21: {} html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1): dependencies: @@ -6630,7 +6624,7 @@ snapshots: inline-style-parser@0.2.7: {} - ip-address@10.1.0: {} + ip-address@10.2.0: {} ipaddr.js@1.9.1: {} @@ -6789,7 +6783,7 @@ snapshots: type-is: 2.0.1 vary: 1.1.2 - kysely@0.28.14: {} + kysely@0.29.2: {} lightningcss-android-arm64@1.31.1: optional: true @@ -7420,9 +7414,9 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - '@next/env': 16.2.3 + '@next/env': 16.2.6 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.7 caniuse-lite: 1.0.30001778 @@ -7431,14 +7425,14 @@ snapshots: react-dom: 19.2.4(react@19.2.4) styled-jsx: 5.1.6(react@19.2.4) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.3 - '@next/swc-darwin-x64': 16.2.3 - '@next/swc-linux-arm64-gnu': 16.2.3 - '@next/swc-linux-arm64-musl': 16.2.3 - '@next/swc-linux-x64-gnu': 16.2.3 - '@next/swc-linux-x64-musl': 16.2.3 - '@next/swc-win32-arm64-msvc': 16.2.3 - '@next/swc-win32-x64-msvc': 16.2.3 + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.58.2 babel-plugin-react-compiler: 1.0.0 @@ -7449,12 +7443,12 @@ snapshots: node-fetch-native@1.6.7: {} - nuqs@2.8.9(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): + nuqs@2.8.9(next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.4 optionalDependencies: - next: 16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nypm@0.6.4: dependencies: From 9385954adacf34e51fccdb8b0bffa2a7dfa64a48 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 20 May 2026 11:02:56 +0200 Subject: [PATCH 3/4] fix: pin kysely override to 0.28.x to satisfy better-auth adapter kysely@0.29 dropped the root re-exports of `DEFAULT_MIGRATION_LOCK_TABLE` and `DEFAULT_MIGRATION_TABLE` (moved under `kysely/migration`), which @better-auth/kysely-adapter@1.6.2 still imports from the root entry. The prior `kysely >=0.28.17` override therefore floated to 0.29.2 and broke the Turbopack build with 12 module-export errors (this also cascaded into the Playwright E2E job, which builds before running). Pin to `>=0.28.17 <0.29.0`: still covers the JSON-path traversal CVE (advisory GHSA, patched in 0.28.17) without bumping past the adapter's compatibility line. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 2 +- pnpm-lock.yaml | 60 +++++++++++++++++++++++++------------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index d32ee77a..eb65a59e 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "defu": "^6.1.5", "rollup": ">=4.59.0", "undici": "7.24.6", - "kysely": ">=0.28.17", + "kysely": ">=0.28.17 <0.29.0", "picomatch": ">=4.0.4", "qs": ">=6.14.2", "yaml": ">=2.8.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98f914ae..06d8e88b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ overrides: defu: ^6.1.5 rollup: '>=4.59.0' undici: 7.24.6 - kysely: '>=0.28.17' + kysely: '>=0.28.17 <0.29.0' picomatch: '>=4.0.4' qs: '>=6.14.2' yaml: '>=2.8.3' @@ -344,7 +344,7 @@ packages: '@opentelemetry/api': ^1.9.0 better-call: 1.3.5 jose: ^6.1.0 - kysely: '>=0.28.17' + kysely: '>=0.28.17 <0.29.0' nanostores: ^1.0.1 peerDependenciesMeta: '@cloudflare/workers-types': @@ -365,7 +365,7 @@ packages: peerDependencies: '@better-auth/core': ^1.6.2 '@better-auth/utils': 0.4.0 - kysely: '>=0.28.17' + kysely: '>=0.28.17 <0.29.0' peerDependenciesMeta: kysely: optional: true @@ -2884,9 +2884,9 @@ packages: resolution: {integrity: sha512-2LOQnFKu3m0VxpE+5sb5+BRTSKrXmNxGgxVRiKwD9s5KQB1zID/FRXhtzeV7RT1L2GVpdEEAfVuclFOMGl1ikA==} engines: {node: '>= 18'} - kysely@0.29.2: - resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==} - engines: {node: '>=22.0.0'} + kysely@0.28.17: + resolution: {integrity: sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==} + engines: {node: '>=20.0.0'} lightningcss-android-arm64@1.31.1: resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} @@ -4402,7 +4402,7 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1)': + '@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1)': dependencies: '@better-auth/utils': 0.4.0 '@better-fetch/fetch': 1.1.21 @@ -4411,42 +4411,42 @@ snapshots: '@standard-schema/spec': 1.1.0 better-call: 1.3.5(zod@4.3.6) jose: 6.2.0 - kysely: 0.29.2 + kysely: 0.28.17 nanostores: 1.1.1 zod: 4.3.6 - '@better-auth/drizzle-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': + '@better-auth/drizzle-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 - '@better-auth/kysely-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.29.2)': + '@better-auth/kysely-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.28.17)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 optionalDependencies: - kysely: 0.29.2 + kysely: 0.28.17 - '@better-auth/memory-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': + '@better-auth/memory-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 - '@better-auth/mongo-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0)': + '@better-auth/mongo-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 optionalDependencies: mongodb: 7.1.0 - '@better-auth/prisma-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': + '@better-auth/prisma-adapter@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 - '@better-auth/telemetry@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)': + '@better-auth/telemetry@1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)': dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1) '@better-auth/utils': 0.4.0 '@better-fetch/fetch': 1.1.21 @@ -5904,13 +5904,13 @@ snapshots: better-auth@1.6.2(@opentelemetry/api@1.9.0)(mongodb@7.1.0)(next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(jsdom@29.0.0(@noble/hashes@2.0.1))(msw@2.12.13(@types/node@24.12.0)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))): dependencies: - '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1) - '@better-auth/drizzle-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0) - '@better-auth/kysely-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.29.2) - '@better-auth/memory-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0) - '@better-auth/mongo-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0) - '@better-auth/prisma-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0) - '@better-auth/telemetry': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.29.2)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21) + '@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1) + '@better-auth/drizzle-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0) + '@better-auth/kysely-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(kysely@0.28.17) + '@better-auth/memory-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0) + '@better-auth/mongo-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(mongodb@7.1.0) + '@better-auth/prisma-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0) + '@better-auth/telemetry': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.3.6))(jose@6.2.0)(kysely@0.28.17)(nanostores@1.1.1))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21) '@better-auth/utils': 0.4.0 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.1.1 @@ -5918,7 +5918,7 @@ snapshots: better-call: 1.3.5(zod@4.3.6) defu: 6.1.7 jose: 6.2.0 - kysely: 0.29.2 + kysely: 0.28.17 nanostores: 1.1.1 zod: 4.3.6 optionalDependencies: @@ -6783,7 +6783,7 @@ snapshots: type-is: 2.0.1 vary: 1.1.2 - kysely@0.29.2: {} + kysely@0.28.17: {} lightningcss-android-arm64@1.31.1: optional: true From d588b65b3f1e96a148e6808b072f4403a6d5a4aa Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 20 May 2026 11:10:32 +0200 Subject: [PATCH 4/4] fix: use 303 See Other for outbound redirects to avoid 405 on POST flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `NextResponse.redirect()` defaults to 307 (method-preserving). Now that this route also accepts POST (added in the previous commit), a refresh that arrived via POST would have its outbound redirect to /catalog or /signin sent as POST too — both of which only handle GET, producing 405. Switch both the success and failure redirects to 303 See Other, which unconditionally instructs the browser to follow with GET. Caught by Copilot review on the PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/api/auth/token-refresh/route.test.ts | 19 +++++++++++++++++++ src/app/api/auth/token-refresh/route.ts | 9 +++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/app/api/auth/token-refresh/route.test.ts b/src/app/api/auth/token-refresh/route.test.ts index c800a9e6..3db81dd7 100644 --- a/src/app/api/auth/token-refresh/route.test.ts +++ b/src/app/api/auth/token-refresh/route.test.ts @@ -231,4 +231,23 @@ describe("GET /api/auth/token-refresh", () => { ); }); }); + + describe("redirect uses 303 See Other (forces GET on follow-up)", () => { + it("success redirect to safeRedirect is 303", async () => { + mockRefreshSuccess(); + const response = await GET( + makeRequest(`${INTERNAL_URL}?redirect=%2Fcatalog`), + ); + // 303 prevents a POST that arrived at this route (via 307 from a Server + // Action) from being replayed as a POST against /catalog. + expect(response.status).toBe(303); + }); + + it("failure redirect to /signin is 303", async () => { + vi.spyOn(console, "warn").mockImplementation(() => {}); + mockRefreshFailure(); + const response = await GET(makeRequest(INTERNAL_URL)); + expect(response.status).toBe(303); + }); + }); }); diff --git a/src/app/api/auth/token-refresh/route.ts b/src/app/api/auth/token-refresh/route.ts index b78befe5..a956cf93 100644 --- a/src/app/api/auth/token-refresh/route.ts +++ b/src/app/api/auth/token-refresh/route.ts @@ -30,7 +30,9 @@ const BASE_ORIGIN = new URL(BASE_URL).origin; * * Both GET and POST are exported: Next.js `redirect()` outside a Server Action uses * 307 (method-preserving), so a redirect triggered from a Server Component render - * that follows a Server Action POST reaches this route as a POST. + * that follows a Server Action POST reaches this route as a POST. For the same + * reason, the outbound redirects below use 303 See Other (forces the browser to + * follow with GET) — a default 307 would re-POST `/catalog` or `/signin` and 405. */ async function handler(request: NextRequest) { // Validate redirect target to prevent open redirects. @@ -64,6 +66,7 @@ async function handler(request: NextRequest) { const redirectResponse = NextResponse.redirect( new URL(safeRedirect, BASE_URL), + { status: 303 }, ); // Copy Set-Cookie headers from Better Auth's internal response directly @@ -88,7 +91,9 @@ async function handler(request: NextRequest) { async function signOutAndRedirect( requestHeaders: Headers, ): Promise { - const response = NextResponse.redirect(new URL("/signin", BASE_URL)); + const response = NextResponse.redirect(new URL("/signin", BASE_URL), { + status: 303, + }); try { const signOutResponse = await auth.api.signOut({ headers: requestHeaders,