Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions src/modules/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
)}`;
Expand All @@ -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.appBaseUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent(fromUrl)}`;
window.location.href = logoutUrl;
}
},

Expand Down
72 changes: 47 additions & 25 deletions tests/unit/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,33 @@ 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
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,
appBaseUrl,
});

// Create a nock scope for mocking API calls
scope = nock(serverUrl);

// Enable request debugging for Nock
nock.disableNetConnect();
nock.emitter.on('no match', (req) => {
Expand Down Expand Up @@ -143,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;
});
Expand All @@ -169,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
Expand Down Expand Up @@ -204,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' };
Expand All @@ -212,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(
Expand Down Expand Up @@ -316,33 +338,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 = `${appBaseUrl}/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 = `${appBaseUrl}/api/apps/${appId}/auth/logout?from_url=${encodeURIComponent('https://example.com/current-page')}`;
expect(mockLocation.href).toBe(expectedUrl);

// Restore window
global.window = originalWindow;
});
Expand Down