Skip to content

Commit 7121f07

Browse files
refactor: remove legacy user handling (#4013)
Remove legacy state handling, use RTK Query to get the currently logged in user in all v2 components. --------- Co-authored-by: Lorenzo Cavazzi <43481553+lorenzo-cavazzi@users.noreply.github.com>
1 parent 477cd4e commit 7121f07

40 files changed

Lines changed: 469 additions & 367 deletions

client/src/App.jsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { Helmet } from "react-helmet";
2121
import { Route, Routes, useLocation } from "react-router";
2222
import { ToastContainer } from "react-toastify";
2323

24-
import { LoginHelper } from "./authentication";
2524
import { Loader } from "./components/Loader";
2625
import LazyAdminPage from "./features/admin/LazyAdminPage";
2726
import Cookie from "./features/cookie/Cookie";
@@ -37,20 +36,18 @@ import LegacyProjectView from "./features/legacy/LegacyProjectView";
3736
import LegacyRoot from "./features/legacy/LegacyRoot";
3837
import LegacyShowDataset from "./features/legacy/LegacyShowDataset";
3938
import LoggedOutPrompt from "./features/loginHandler/LoggedOutPrompt";
40-
import LoginHandler from "./features/loginHandler/LoginHandler";
4139
import { Unavailable } from "./features/maintenance/Maintenance";
4240
import LazyRootV2 from "./features/rootV2/LazyRootV2";
43-
import {
44-
useGetUserQuery,
45-
useGetUserQueryState,
46-
} from "./features/usersV2/api/users.api";
41+
import { useGetUserQueryState } from "./features/usersV2/api/users.api";
4742
import NotificationsManager from "./notifications/NotificationsManager";
4843
import AppContext from "./utils/context/appContext";
4944
import { setupWebSocket } from "./websocket";
5045

5146
import "react-toastify/dist/ReactToastify.css";
5247
import "./App.css";
5348

49+
import { useTriggerNotifications } from "./authentication/useTriggerNotifications.hook";
50+
5451
export const ContainerWrap = ({ children, fullSize = false }) => {
5552
const classContainer = !fullSize
5653
? "container-xxl py-4 mt-2 renku-container"
@@ -109,6 +106,8 @@ export default function App(props) {
109106
const [, setWebSocket] = useState(null);
110107
const [notifications, setNotifications] = useState(null);
111108

109+
const triggerNotifications = useTriggerNotifications();
110+
112111
useEffect(() => {
113112
locationRef.current = location;
114113
}, [location]);
@@ -122,8 +121,7 @@ export default function App(props) {
122121
setNotifications(notificationManager);
123122

124123
// Setup authentication listeners and notifications
125-
LoginHelper.setupListener();
126-
LoginHelper.triggerNotifications(notificationManager);
124+
triggerNotifications(notificationManager);
127125

128126
// Setup WebSocket channel
129127
let webSocketUrl = props.client.uiserverUrl + "/ws";
@@ -142,9 +140,8 @@ export default function App(props) {
142140
// ! Ignoring the rule of hooks creates issues, we should refactor this hook
143141
}, []); // eslint-disable-line react-hooks/exhaustive-deps
144142

145-
//? Subscribe to the user endpoint: all children components can use the query state from RTK Query.
146-
const { error, isLoading } = useGetUserQuery();
147143
// Avoid rendering the application while authenticating the user
144+
const { error, isLoading } = useGetUserQueryState();
148145
if (isLoading) {
149146
return (
150147
<section className="py-5">
@@ -175,7 +172,6 @@ export default function App(props) {
175172
<RenkuNavBar />
176173
<CentralContentContainer />
177174
<FooterNavbar />
178-
<LoginHandler />
179175
<Cookie />
180176
</AppContext.Provider>
181177
<ToastContainer />

client/src/authentication/Authentication.container.js

Lines changed: 0 additions & 123 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*!
2+
* Copyright 2026 - Swiss Data Science Center (SDSC)
3+
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4+
* Eidgenössische Technische Hochschule Zürich (ETHZ).
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
export const RENKU_QUERY_PARAMS = {
20+
login: "renku_login",
21+
logout: "renku_logout",
22+
loginValue: "1",
23+
} as const;
24+
25+
export const RENKU_USER_SIGNED_IN_COOKIE = "renku_user_signed_in";
26+
27+
export const LOGOUT_EVENT_TIMEOUT = 5_000; // 5 seconds
28+
29+
export const RENKU_USER_SIGNED_IN_COOKIE_TTL = 365 * 24 * 60 * 60 * 1000; // 1 year
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*!
2+
* Copyright 2026 - Swiss Data Science Center (SDSC)
3+
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4+
* Eidgenössische Technische Hochschule Zürich (ETHZ).
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
import type { Location, NavigateFunction } from "react-router";
20+
21+
import { NOTIFICATION_TOPICS } from "~/notifications/Notifications.constants";
22+
import type { NotificationsManager } from "~/notifications/notifications.types";
23+
import {
24+
LOGOUT_EVENT_TIMEOUT,
25+
RENKU_QUERY_PARAMS,
26+
RENKU_USER_SIGNED_IN_COOKIE,
27+
RENKU_USER_SIGNED_IN_COOKIE_TTL,
28+
} from "./authentication.constants";
29+
30+
interface HandleLoginParamsArgs {
31+
location: Location<unknown>;
32+
navigate: NavigateFunction;
33+
}
34+
35+
/**
36+
* Manages communication of login/logout events between tabs. It uses localStorage to communicate
37+
* the events between tabs, and uses sessionStorage to remember an event after a refresh within a tab.
38+
*
39+
* Remove renku login parameters and set localStorage object
40+
*/
41+
export function handleLoginParams({
42+
location,
43+
navigate,
44+
}: HandleLoginParamsArgs): void {
45+
// Check if user has just logged in
46+
const queryParams = new URLSearchParams(location.search);
47+
if (
48+
queryParams.get(RENKU_QUERY_PARAMS.login) === RENKU_QUERY_PARAMS.loginValue
49+
) {
50+
// delete the login param
51+
queryParams.delete(RENKU_QUERY_PARAMS.login);
52+
navigate({ search: queryParams.toString() }, { replace: true });
53+
54+
// save the login time to localStorage to allow other tabs to handle the event
55+
localStorage.setItem(RENKU_QUERY_PARAMS.login, Date.now().toString());
56+
57+
// save the login state in a client-side cookie
58+
if (window.cookieStore) {
59+
window.cookieStore.set({
60+
name: RENKU_USER_SIGNED_IN_COOKIE,
61+
value: "1",
62+
expires: Date.now() + RENKU_USER_SIGNED_IN_COOKIE_TTL,
63+
sameSite: "strict",
64+
});
65+
}
66+
}
67+
}
68+
69+
/**
70+
* Set up event listener fol localStorage authentication events. There should be only one listener per browser tab.
71+
*
72+
* Returns a cleanup function.
73+
*/
74+
export function setupListener(): () => void {
75+
function listener(event: StorageEvent): void {
76+
if (event.key === RENKU_QUERY_PARAMS.logout) {
77+
setTimeout(() => {
78+
sessionStorage.setItem(
79+
RENKU_QUERY_PARAMS.logout,
80+
Date.now().toString()
81+
);
82+
window.location.reload();
83+
}, LOGOUT_EVENT_TIMEOUT);
84+
} else if (event.key === RENKU_QUERY_PARAMS.login) {
85+
sessionStorage.setItem(RENKU_QUERY_PARAMS.login, Date.now().toString());
86+
window.location.reload();
87+
}
88+
}
89+
90+
window.addEventListener("storage", listener);
91+
return () => {
92+
window.removeEventListener("storage", listener);
93+
};
94+
}
95+
96+
/**
97+
* Set up event listener fol localStorage authentication events. This should be called once per browser tab.
98+
*
99+
* @param {object} notifications - notification manager object.
100+
*
101+
* Returns a cleanup function.
102+
*/
103+
export function triggerNotifications(notifications: NotificationsManager) {
104+
// Check login
105+
const login = sessionStorage.getItem(RENKU_QUERY_PARAMS.login);
106+
if (login) {
107+
sessionStorage.removeItem(RENKU_QUERY_PARAMS.login);
108+
notifications.addSuccess(
109+
NOTIFICATION_TOPICS.AUTHENTICATION,
110+
"The page was refreshed because you recently logged in on a different tab."
111+
);
112+
}
113+
114+
// Check logout
115+
const logout = sessionStorage.getItem(RENKU_QUERY_PARAMS.logout);
116+
if (logout) {
117+
sessionStorage.removeItem(RENKU_QUERY_PARAMS.logout);
118+
notifications.addWarning(
119+
NOTIFICATION_TOPICS.AUTHENTICATION,
120+
"The page was refreshed because you recently logged out on a different tab."
121+
);
122+
}
123+
}
124+
/**
125+
* Invoke whenever then logout process has been triggered.
126+
*/
127+
export function notifyLogout(): void {
128+
localStorage.setItem(RENKU_QUERY_PARAMS.logout, Date.now().toString());
129+
// Manual logout: remove the `renku_user_signed_in` cookie
130+
if (window.cookieStore) {
131+
window.cookieStore.delete(RENKU_USER_SIGNED_IN_COOKIE);
132+
}
133+
}

0 commit comments

Comments
 (0)