diff --git a/.changeset/fix-auth-js-absolute-basepath.md b/.changeset/fix-auth-js-absolute-basepath.md new file mode 100644 index 000000000..6adc3b6b9 --- /dev/null +++ b/.changeset/fix-auth-js-absolute-basepath.md @@ -0,0 +1,5 @@ +--- +'@hono/auth-js': patch +--- + +Handle absolute URL in basePath of AuthConfigManager.setConfig() diff --git a/packages/auth-js/src/client.test.ts b/packages/auth-js/src/client.test.ts new file mode 100644 index 000000000..a59e53803 --- /dev/null +++ b/packages/auth-js/src/client.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'vitest' +import { normalizeBasePath } from './client' + +describe('normalizeBasePath', () => { + it('should split absolute http URL into baseUrl and basePath', () => { + const result = normalizeBasePath({ baseUrl: '', basePath: 'http://localhost:8000/api/auth' }) + expect(result.baseUrl).toBe('http://localhost:8000') + expect(result.basePath).toBe('/api/auth') + expect(`${result.baseUrl}${result.basePath}/session`).toBe( + 'http://localhost:8000/api/auth/session' + ) + }) + + it('should split absolute https URL into baseUrl and basePath', () => { + const result = normalizeBasePath({ baseUrl: '', basePath: 'https://example.com/auth' }) + expect(result.baseUrl).toBe('https://example.com') + expect(result.basePath).toBe('/auth') + expect(`${result.baseUrl}${result.basePath}/session`).toBe('https://example.com/auth/session') + }) + + it('should not modify relative basePath', () => { + const result = normalizeBasePath({ baseUrl: 'http://localhost:3000', basePath: '/custom/auth' }) + expect(result.baseUrl).toBe('http://localhost:3000') + expect(result.basePath).toBe('/custom/auth') + expect(`${result.baseUrl}${result.basePath}/session`).toBe( + 'http://localhost:3000/custom/auth/session' + ) + }) + + it('should handle absolute URL without path', () => { + const result = normalizeBasePath({ baseUrl: '', basePath: 'http://localhost:8000' }) + expect(result.baseUrl).toBe('http://localhost:8000') + expect(result.basePath).toBe('') + expect(`${result.baseUrl}${result.basePath}/session`).toBe('http://localhost:8000/session') + }) + + it('should override baseUrl when both are absolute URLs', () => { + const result = normalizeBasePath({ + baseUrl: 'http://localhost:3000', + basePath: 'http://localhost:8000/api/auth', + }) + expect(result.baseUrl).toBe('http://localhost:8000') + expect(result.basePath).toBe('/api/auth') + expect(`${result.baseUrl}${result.basePath}/session`).toBe( + 'http://localhost:8000/api/auth/session' + ) + }) + + it('should handle absolute URL with trailing slash', () => { + const result = normalizeBasePath({ baseUrl: '', basePath: 'http://localhost:8000/api/auth/' }) + expect(result.baseUrl).toBe('http://localhost:8000') + expect(result.basePath).toBe('/api/auth') + expect(`${result.baseUrl}${result.basePath}/session`).toBe( + 'http://localhost:8000/api/auth/session' + ) + }) +}) diff --git a/packages/auth-js/src/client.ts b/packages/auth-js/src/client.ts index 313f7df9e..48e25428c 100644 --- a/packages/auth-js/src/client.ts +++ b/packages/auth-js/src/client.ts @@ -173,6 +173,18 @@ interface ParsedUrl { toString: () => string } +export function normalizeBasePath(config: T): T { + if (config.basePath && /^https?:\/\//.test(config.basePath)) { + const url = new URL(config.basePath) + return { + ...config, + baseUrl: url.origin, + basePath: url.pathname.replace(/\/$/, ''), + } + } + return config +} + export function parseUrl(url?: string): ParsedUrl { const defaultUrl = 'http://localhost:3000/api/auth' const parsedUrl = new URL(url ? (url.startsWith('http') ? url : `https://${url}`) : defaultUrl) diff --git a/packages/auth-js/src/react.tsx b/packages/auth-js/src/react.tsx index 0ee40f5eb..dacfa69da 100644 --- a/packages/auth-js/src/react.tsx +++ b/packages/auth-js/src/react.tsx @@ -2,7 +2,14 @@ import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/p import type { LoggerInstance, Session } from '@auth/core/types' import * as React from 'react' import { useCallback, useContext, useEffect, useMemo, useState } from 'react' -import { ClientSessionError, fetchData, now, parseUrl, useOnline } from './client' +import { + ClientSessionError, + fetchData, + normalizeBasePath, + now, + parseUrl, + useOnline, +} from './client' import type { WindowProps, AuthState, @@ -53,7 +60,8 @@ class AuthConfigManager { } setConfig(userConfig: Partial): void { - this.config = { ...this.config, ...userConfig } + const normalized = normalizeBasePath(userConfig) + this.config = { ...this.config, ...normalized } } getConfig(): AuthClientConfig {