From b6d636755f955125811b7b267372c3f2c021acec Mon Sep 17 00:00:00 2001 From: Roy Miloh Date: Mon, 26 Jan 2026 09:41:04 -0300 Subject: [PATCH 1/4] clear http only cookies on logout --- src/modules/auth.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/modules/auth.ts b/src/modules/auth.ts index 50f0e0e..7123507 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -75,29 +75,29 @@ export function createAuthModule( }, // Logout the current user - // Removes the token from localStorage and optionally redirects to a URL or reloads the page logout(redirectUrl?: string) { - // Remove token from axios headers + // Remove token from axios headers (always do this) delete axios.defaults.headers.common["Authorization"]; - // Remove token from localStorage - if (typeof window !== "undefined" && window.localStorage) { - try { - window.localStorage.removeItem("base44_access_token"); - // Remove "token" that is set by the built-in SDK of platform version 2 - window.localStorage.removeItem("token"); - } catch (e) { - console.error("Failed to remove token from localStorage:", e); - } - } - - // Redirect if a URL is provided + // Only do the rest if in a browser environment if (typeof window !== "undefined") { - if (redirectUrl) { - window.location.href = redirectUrl; - } else { - window.location.reload(); + // Remove token from localStorage + if (window.localStorage) { + try { + window.localStorage.removeItem("base44_access_token"); + // Remove "token" that is set by the built-in SDK of platform version 2 + window.localStorage.removeItem("token"); + } catch (e) { + console.error("Failed to remove token from localStorage:", e); + } } + + // Determine the from_url parameter + const fromUrl = redirectUrl || window.location.href; + + // Redirect to server-side logout endpoint to clear HTTP-only cookies + const logoutUrl = `${options.serverUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent(fromUrl)}`; + window.location.href = logoutUrl; } }, From 89aeee3e09a42635d9dd2c04604d4268cb9eca77 Mon Sep 17 00:00:00 2001 From: Roy Miloh Date: Mon, 26 Jan 2026 10:57:14 -0300 Subject: [PATCH 2/4] test --- tests/unit/auth.test.js | 52 ++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/tests/unit/auth.test.js b/tests/unit/auth.test.js index 9ed354e..945c780 100644 --- a/tests/unit/auth.test.js +++ b/tests/unit/auth.test.js @@ -7,17 +7,31 @@ describe('Auth Module', () => { let scope; const appId = 'test-app-id'; const serverUrl = 'https://api.base44.com'; - + beforeEach(() => { + // Mock window.addEventListener and document for analytics module + if (typeof window !== 'undefined') { + if (!window.addEventListener) { + window.addEventListener = vi.fn(); + window.removeEventListener = vi.fn(); + } + } + if (typeof document === 'undefined') { + global.document = { + referrer: '', + visibilityState: 'visible' + }; + } + // Create a new client for each test base44 = createClient({ serverUrl, appId, }); - + // Create a nock scope for mocking API calls scope = nock(serverUrl); - + // Enable request debugging for Nock nock.disableNetConnect(); nock.emitter.on('no match', (req) => { @@ -316,33 +330,33 @@ describe('Auth Module', () => { global.window = { location: mockLocation }; - + const redirectUrl = 'https://example.com/logout-success'; base44.auth.logout(redirectUrl); - - // Verify redirect - expect(mockLocation.href).toBe(redirectUrl); - + + // Verify redirect to server-side logout endpoint with from_url parameter + const expectedUrl = `${serverUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent(redirectUrl)}`; + expect(mockLocation.href).toBe(expectedUrl); + // Restore window global.window = originalWindow; }); - test('should reload page when no redirect URL is provided', async () => { - // Mock window object with reload function - const mockReload = vi.fn(); + test('should redirect to logout endpoint when no redirect URL is provided', async () => { + // Mock window object + const mockLocation = { href: 'https://example.com/current-page' }; const originalWindow = global.window; global.window = { - location: { - reload: mockReload - } + location: mockLocation }; - + // Call logout without redirect URL base44.auth.logout(); - - // Verify page reload was called - expect(mockReload).toHaveBeenCalledTimes(1); - + + // Verify redirect to server-side logout endpoint with current page as from_url + const expectedUrl = `${serverUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent('https://example.com/current-page')}`; + expect(mockLocation.href).toBe(expectedUrl); + // Restore window global.window = originalWindow; }); From 50facc98524cf34d4e2e2e7157dcc648174d81a5 Mon Sep 17 00:00:00 2001 From: Roy Miloh Date: Wed, 28 Jan 2026 16:18:31 -0300 Subject: [PATCH 3/4] appbaseurl --- src/modules/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/auth.ts b/src/modules/auth.ts index 7123507..8232fa2 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -65,7 +65,7 @@ export function createAuthModule( // Build the provider login URL (google is the default, so no provider path needed) const providerPath = provider === "google" ? "" : `/${provider}`; const loginUrl = `${ - options.serverUrl + options.appBaseUrl }/api/apps/auth${providerPath}/login?app_id=${appId}&from_url=${encodeURIComponent( redirectUrl )}`; @@ -96,7 +96,7 @@ export function createAuthModule( const fromUrl = redirectUrl || window.location.href; // Redirect to server-side logout endpoint to clear HTTP-only cookies - const logoutUrl = `${options.serverUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent(fromUrl)}`; + const logoutUrl = `${options.appBaseUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent(fromUrl)}`; window.location.href = logoutUrl; } }, From 9b9c35e356c73fe4a3404a47bc74dcd136aab1c2 Mon Sep 17 00:00:00 2001 From: Roy Miloh Date: Wed, 28 Jan 2026 16:39:31 -0300 Subject: [PATCH 4/4] test --- tests/unit/auth.test.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/unit/auth.test.js b/tests/unit/auth.test.js index 945c780..033962f 100644 --- a/tests/unit/auth.test.js +++ b/tests/unit/auth.test.js @@ -7,6 +7,7 @@ describe('Auth Module', () => { let scope; const appId = 'test-app-id'; const serverUrl = 'https://api.base44.com'; + const appBaseUrl = 'https://api.base44.com'; beforeEach(() => { // Mock window.addEventListener and document for analytics module @@ -27,6 +28,7 @@ describe('Auth Module', () => { base44 = createClient({ serverUrl, appId, + appBaseUrl, }); // Create a nock scope for mocking API calls @@ -157,15 +159,15 @@ describe('Auth Module', () => { global.window = { location: mockLocation }; - + const nextUrl = 'https://example.com/dashboard'; base44.auth.redirectToLogin(nextUrl); - + // Verify the redirect URL was set correctly expect(mockLocation.href).toBe( - `/login?from_url=${encodeURIComponent(nextUrl)}` + `${appBaseUrl}/login?from_url=${encodeURIComponent(nextUrl)}` ); - + // Restore window global.window = originalWindow; }); @@ -183,7 +185,7 @@ describe('Auth Module', () => { // Verify the redirect URL uses current URL expect(mockLocation.href).toBe( - `/login?from_url=${encodeURIComponent(currentUrl)}` + `${appBaseUrl}/login?from_url=${encodeURIComponent(currentUrl)}` ); // Restore window @@ -218,6 +220,12 @@ describe('Auth Module', () => { }); test('should use relative URL for login redirect when appBaseUrl is not provided', () => { + // Create a client without appBaseUrl + const clientWithoutAppBaseUrl = createClient({ + serverUrl, + appId, + }); + // Mock window.location const originalWindow = global.window; const mockLocation = { href: '', origin: 'https://current-app.com' }; @@ -226,7 +234,7 @@ describe('Auth Module', () => { }; const nextUrl = 'https://example.com/dashboard'; - base44.auth.redirectToLogin(nextUrl); + clientWithoutAppBaseUrl.auth.redirectToLogin(nextUrl); // Verify the redirect URL uses a relative path (no appBaseUrl prefix) expect(mockLocation.href).toBe( @@ -335,7 +343,7 @@ describe('Auth Module', () => { base44.auth.logout(redirectUrl); // Verify redirect to server-side logout endpoint with from_url parameter - const expectedUrl = `${serverUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent(redirectUrl)}`; + const expectedUrl = `${appBaseUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent(redirectUrl)}`; expect(mockLocation.href).toBe(expectedUrl); // Restore window @@ -354,7 +362,7 @@ describe('Auth Module', () => { base44.auth.logout(); // Verify redirect to server-side logout endpoint with current page as from_url - const expectedUrl = `${serverUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent('https://example.com/current-page')}`; + const expectedUrl = `${appBaseUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent('https://example.com/current-page')}`; expect(mockLocation.href).toBe(expectedUrl); // Restore window