🚸(frontend) redirect on current url tab after 401

When multiple tabs were opened and a 401 error occurred,
the user was redirected to the login page, then
after login, the user was redirected to the page
where the last 401 error occurred.
We improved this behavior by saving the url per tab,
and after login, the user is redirected to the
last url of the current tab.
This commit is contained in:
Anthony LC 2026-04-09 16:49:41 +02:00
parent d9334352bb
commit 7dc7320dac
No known key found for this signature in database
5 changed files with 85 additions and 23 deletions

View file

@ -8,6 +8,7 @@ and this project adheres to
### Fixed
- 🚸(frontend) redirect on current url tab after 401 #2197
- 🐛(frontend) abort check media status unmount #2194
- ✨(backend) order pinned documents by last updated at #2028

View file

@ -1,13 +1,6 @@
import crypto from 'crypto';
import { expect, test } from '@playwright/test';
import {
createDoc,
getCurrentConfig,
mockedDocument,
verifyDocName,
} from './utils-common';
import { createDoc, getCurrentConfig, verifyDocName } from './utils-common';
import { writeInEditor } from './utils-editor';
import { SignIn, expectLoginPage } from './utils-signin';
import { createRootSubPage } from './utils-sub-pages';
@ -119,13 +112,53 @@ test.describe('Doc Routing: Not logged', () => {
page,
browserName,
}) => {
const uuid = crypto.randomUUID();
await mockedDocument(page, { link_reach: 'public', id: uuid });
await page.goto(`/docs/${uuid}/`);
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
await page.getByRole('button', { name: 'Login' }).click();
await page.goto('/');
await SignIn(page, browserName);
const [docTitle1] = await createDoc(page, 'doc-login-1', browserName, 1);
await verifyDocName(page, docTitle1);
const page2 = await page.context().newPage();
await page2.goto('/');
const [docTitle2] = await createDoc(page2, 'doc-login-2', browserName, 1);
await verifyDocName(page2, docTitle2);
// Remove cookies `docs_sessionid` to simulate the user being logged out
await page2.context().clearCookies();
await page2.reload();
// Tab 2 - 401 triggered, user should be redirected to login page
await expect(
page2
.getByRole('main', { name: 'Main content' })
.getByRole('button', { name: 'Login' }),
).toBeVisible({
timeout: 10000,
});
// Tab 1 - 401 triggered, user should be redirected to login page
await page.reload();
await expect(
page
.getByRole('main', { name: 'Main content' })
.getByRole('button', { name: 'Login' }),
).toBeVisible({
timeout: 10000,
});
// Reconnected
await page
.getByRole('main', { name: 'Main content' })
.getByRole('button', { name: 'Login' })
.click();
await SignIn(page, browserName, false);
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
// Tab 1 - Should be on its doc
await verifyDocName(page, docTitle1);
// Tab 2 - Should be on its doc
await page2.reload();
await verifyDocName(page2, docTitle2);
});
// eslint-disable-next-line playwright/expect-expect

View file

@ -3,5 +3,5 @@ import { baseApiUrl } from '@/api';
export const HOME_URL = '/home/';
export const LOGIN_URL = `${baseApiUrl()}authenticate/`;
export const LOGOUT_URL = `${baseApiUrl()}logout/`;
export const PATH_AUTH_LOCAL_STORAGE = 'docs-path-auth';
export const PATH_AUTH_SESSION_STORAGE = 'docs-path-auth';
export const SILENT_LOGIN_RETRY = 'silent-login-retry';

View file

@ -1,35 +1,36 @@
import { terminateCrispSession } from '@/services/Crisp';
import { safeLocalStorage } from '@/utils/storages';
import { safeLocalStorage, safeSessionStorage } from '@/utils/storages';
import {
HOME_URL,
LOGIN_URL,
LOGOUT_URL,
PATH_AUTH_LOCAL_STORAGE,
PATH_AUTH_SESSION_STORAGE,
SILENT_LOGIN_RETRY,
} from './conf';
/**
* Get the stored auth URL from local storage
* Get the stored auth URL from session storage (per-tab)
*/
export const getAuthUrl = () => {
const path_auth = safeLocalStorage.getItem(PATH_AUTH_LOCAL_STORAGE);
const path_auth = safeSessionStorage.getItem(PATH_AUTH_SESSION_STORAGE);
if (path_auth) {
safeLocalStorage.removeItem(PATH_AUTH_LOCAL_STORAGE);
safeSessionStorage.removeItem(PATH_AUTH_SESSION_STORAGE);
return path_auth;
}
};
/**
* Store the current path in local storage if it's not the homepage or root
* so we can redirect the user to this path after login
* Store the current path in session storage (per-tab) if it's not the
* homepage or root, so we can redirect the user to this path after login.
* Using sessionStorage ensures each tab independently tracks its own URL.
*/
export const setAuthUrl = () => {
if (
window.location.pathname !== '/' &&
window.location.pathname !== `${HOME_URL}/`
) {
safeLocalStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.href);
safeSessionStorage.setItem(PATH_AUTH_SESSION_STORAGE, window.location.href);
}
};

View file

@ -50,3 +50,30 @@ export const safeLocalStorage: SyncStorage = {
localStorage.removeItem(key);
},
};
/**
* @namespace safeSessionStorage
* @description A utility for safely interacting with sessionStorage.
* sessionStorage is scoped to the current browser tab, making it suitable
* for per-tab state that should not be shared across tabs.
*/
export const safeSessionStorage: SyncStorage = {
getItem: (key: string): string | null => {
if (typeof window === 'undefined') {
return null;
}
return sessionStorage.getItem(key);
},
setItem: (key: string, value: string): void => {
if (typeof window === 'undefined') {
return;
}
sessionStorage.setItem(key, value);
},
removeItem: (key: string): void => {
if (typeof window === 'undefined') {
return;
}
sessionStorage.removeItem(key);
},
};