fix(auth): make DISABLE_AUTH work in web app (#3297)

Return an anonymous session using the same response envelope as Better Auth's get-session endpoint, and make the session provider tolerant to both wrapped and raw session payloads.

Fixes #2524
This commit is contained in:
Jay Prajapati
2026-02-24 23:22:44 +05:30
committed by GitHub
parent d824ce5b07
commit 9e817bc5b0
6 changed files with 152 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ import { createContext, useCallback, useEffect, useMemo, useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import posthog from 'posthog-js'
import { client } from '@/lib/auth/auth-client'
import { extractSessionDataFromAuthClientResult } from '@/lib/auth/session-response'
export type AppSession = {
user: {
@@ -45,7 +46,8 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
const res = bypassCache
? await client.getSession({ query: { disableCookieCache: true } })
: await client.getSession()
setData(res?.data ?? null)
const session = extractSessionDataFromAuthClientResult(res) as AppSession
setData(session)
} catch (e) {
setError(e instanceof Error ? e : new Error('Failed to fetch session'))
} finally {

View File

@@ -0,0 +1,93 @@
/**
* @vitest-environment node
*/
import { createMockRequest, setupCommonApiMocks } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'
const handlerMocks = vi.hoisted(() => ({
betterAuthGET: vi.fn(),
betterAuthPOST: vi.fn(),
ensureAnonymousUserExists: vi.fn(),
createAnonymousGetSessionResponse: vi.fn(() => ({
data: {
user: { id: 'anon' },
session: { id: 'anon-session' },
},
})),
}))
vi.mock('better-auth/next-js', () => ({
toNextJsHandler: () => ({
GET: handlerMocks.betterAuthGET,
POST: handlerMocks.betterAuthPOST,
}),
}))
vi.mock('@/lib/auth', () => ({
auth: { handler: {} },
}))
vi.mock('@/lib/auth/anonymous', () => ({
ensureAnonymousUserExists: handlerMocks.ensureAnonymousUserExists,
createAnonymousGetSessionResponse: handlerMocks.createAnonymousGetSessionResponse,
}))
describe('auth catch-all route (DISABLE_AUTH get-session)', () => {
beforeEach(() => {
vi.resetModules()
setupCommonApiMocks()
handlerMocks.betterAuthGET.mockReset()
handlerMocks.betterAuthPOST.mockReset()
handlerMocks.ensureAnonymousUserExists.mockReset()
handlerMocks.createAnonymousGetSessionResponse.mockClear()
})
it('returns anonymous session in better-auth response envelope when auth is disabled', async () => {
vi.doMock('@/lib/core/config/feature-flags', () => ({ isAuthDisabled: true }))
const req = createMockRequest(
'GET',
undefined,
{},
'http://localhost:3000/api/auth/get-session'
)
const { GET } = await import('@/app/api/auth/[...all]/route')
const res = await GET(req as any)
const json = await res.json()
expect(handlerMocks.ensureAnonymousUserExists).toHaveBeenCalledTimes(1)
expect(handlerMocks.betterAuthGET).not.toHaveBeenCalled()
expect(json).toEqual({
data: {
user: { id: 'anon' },
session: { id: 'anon-session' },
},
})
})
it('delegates to better-auth handler when auth is enabled', async () => {
vi.doMock('@/lib/core/config/feature-flags', () => ({ isAuthDisabled: false }))
handlerMocks.betterAuthGET.mockResolvedValueOnce(
new (await import('next/server')).NextResponse(JSON.stringify({ data: { ok: true } }), {
headers: { 'content-type': 'application/json' },
}) as any
)
const req = createMockRequest(
'GET',
undefined,
{},
'http://localhost:3000/api/auth/get-session'
)
const { GET } = await import('@/app/api/auth/[...all]/route')
const res = await GET(req as any)
const json = await res.json()
expect(handlerMocks.ensureAnonymousUserExists).not.toHaveBeenCalled()
expect(handlerMocks.betterAuthGET).toHaveBeenCalledTimes(1)
expect(json).toEqual({ data: { ok: true } })
})
})

View File

@@ -1,7 +1,7 @@
import { toNextJsHandler } from 'better-auth/next-js'
import { type NextRequest, NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import { createAnonymousSession, ensureAnonymousUserExists } from '@/lib/auth/anonymous'
import { createAnonymousGetSessionResponse, ensureAnonymousUserExists } from '@/lib/auth/anonymous'
import { isAuthDisabled } from '@/lib/core/config/feature-flags'
export const dynamic = 'force-dynamic'
@@ -14,7 +14,7 @@ export async function GET(request: NextRequest) {
if (path === 'get-session' && isAuthDisabled) {
await ensureAnonymousUserExists()
return NextResponse.json(createAnonymousSession())
return NextResponse.json(createAnonymousGetSessionResponse())
}
return betterAuthGET(request)

View File

@@ -102,3 +102,7 @@ export function createAnonymousSession(): AnonymousSession {
},
}
}
export function createAnonymousGetSessionResponse(): { data: AnonymousSession } {
return { data: createAnonymousSession() }
}

View File

@@ -0,0 +1,31 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { extractSessionDataFromAuthClientResult } from '@/lib/auth/session-response'
describe('extractSessionDataFromAuthClientResult', () => {
it('returns null for non-objects', () => {
expect(extractSessionDataFromAuthClientResult(null)).toBeNull()
expect(extractSessionDataFromAuthClientResult(undefined)).toBeNull()
expect(extractSessionDataFromAuthClientResult('nope')).toBeNull()
expect(extractSessionDataFromAuthClientResult(123)).toBeNull()
})
it('prefers .data when present', () => {
expect(extractSessionDataFromAuthClientResult({ data: null })).toBeNull()
const session = { user: { id: 'u1' }, session: { id: 's1' } }
expect(extractSessionDataFromAuthClientResult({ data: session })).toEqual(session)
})
it('falls back to raw session payload shape', () => {
const raw = { user: { id: 'u1' }, session: { id: 's1' } }
expect(extractSessionDataFromAuthClientResult(raw)).toEqual(raw)
})
it('returns null for unknown object shapes', () => {
expect(extractSessionDataFromAuthClientResult({})).toBeNull()
expect(extractSessionDataFromAuthClientResult({ ok: true })).toBeNull()
})
})

View File

@@ -0,0 +1,19 @@
export function extractSessionDataFromAuthClientResult(result: unknown): unknown | null {
if (!result || typeof result !== 'object') {
return null
}
const record = result as Record<string, unknown>
// Expected shape from better-auth client: { data: <session> }
if ('data' in record) {
return (record as { data?: unknown }).data ?? null
}
// Fallback for raw session payloads: { user, session }
if ('user' in record) {
return record
}
return null
}