Skip to content

Commit e2c3727

Browse files
authored
Centralize Authentication (#9593)
* Introduce centralized authentication context to manage auth state and replace scattered authentication logic in the frontend. * Refactor `useUser` and `ConfigProvider` hooks to use `useCallback` and `useAPI`, streamlining state management and simplifying configuration retrieval logic. * Consolidate authentication status and logic into `authContext`, remove redundant `status.ts`, and rename `RequireAuth` to `RequiresAuth` for consistency. * Refactor authentication logic by consolidating `markAuthenticated` and `markUnauthenticated` into `setAuthStatus`, simplifying auth state management. * Refactor login flow to use `RedirectIfAuthenticated` component, centralizing auth redirection logic and simplifying `login.tsx`. * Refactor `RedirectIfAuthenticated` to use a functional component and simplify child rendering logic. * Refactor `authContext` to use functional components, arrow functions, and improve readability of authentication logic. * Refactor `RequiresAuth` to use `React.FC` and arrow function for consistency with functional component style. * Refactor `authContext` to reorder `AuthContext` and `AuthContextType` definitions for improved readability and organization. * Refactor login flow to handle redirect authentication with `useEffect` and `setAuthStatus`, simplifying and centralizing auth logic. * Refactor `login.tsx` to simplify `next` variable assignment by removing redundant type assertions. * Refactor `login.tsx` to reorder conditional logic for readability and maintain consistency in authentication flow. * Refactor `login.tsx` to replace `useEffect` with direct conditional navigation logic and simplify SSO handling flow. * Refactor `login.tsx` to remove unused `useEffect` import. * Refactor `login.tsx` to remove redundant type assertions in `next` variable assignment. * Refactor `login.tsx` to remove unused authenticated redirect logic for cleaner and simplified authentication flow. * Refactor `login.tsx` to remove unused imports and redundant `useAuth` destructuring for cleaner code. * Refactor authentication flow in `login.tsx` and `requiresAuth.tsx` to centralize redirect logic and ensure consistent handling of authenticated and unauthenticated states. * Refactor `login.tsx` to simplify `next` variable assignment by removing redundant parentheses. * Refactor `AuthProvider` to use `useEffect` for dynamic authentication status and simplify `login.tsx` by removing unused redirect logic. * Refactor `api.jsx` to reset request state on authentication error for consistent error handling and cleaner unmount logic. * Refactor `requiresAuth.tsx` to simplify authentication flow by removing unused API call and redundant state handling. * Refactor `authContext.tsx` to streamline `AuthProvider`, remove `useEffect`, and simplify initial auth status handling. * Refactor `navbar.jsx` to simplify logout flow by removing conditional navigation logic. * Refactor `navbar.jsx` and `requiresAuth.tsx` to clean up unused imports and simplify `next` variable assignment with hash handling. * Refactor `index.jsx` to adjust route nesting by relocating `index` route within `RequiresAuth` for cleaner route structure. * Refactor `index.jsx` to relocate fallback route for improved route structure and eliminate redundancy. * Refactor `layout.tsx` to remove unnecessary `useEffect` and streamline RBAC alert dismissal logic. * Refactor `api.jsx` to improve unmount logic by adding `isMounted` checks and streamline request state handling on authentication errors. * Refactor `authContext.tsx`, `api.jsx`, and `user.jsx` to introduce `UNKNOWN` auth status, improve auth status synchronization, and streamline user fetching logic. * Refactor `authContext.tsx` to simplify `AuthProvider` by replacing `readInitialStatus` with direct `UNKNOWN` initialization. * Refactor `user.jsx` to handle `UNKNOWN` auth status, improve user-fetching logic, and optimize auth state synchronization. * Refactor `navbar.jsx`, `login.tsx`, `api.jsx`, and `user.jsx` to improve logout flow by leveraging `sessionStorage` for state management and ensuring clean API handling during logout. * Simplify logout logic in `navbar.jsx` by replacing hardcoded URL with `logoutUrl` variable. * Remove `sessionStorage`-based logout flow logic from `navbar.jsx`, `login.tsx`, `api.jsx`, and `user.jsx` for simplification and cleanup. * Remove unused `useEffect` import from `login.tsx`. * Improve logout flow by finalizing `sessionStorage` logic in `navbar.jsx`, `login.tsx`, `api.jsx`, and `user.jsx` to ensure clean state management and prevent race conditions. * Simplify logout logic in `navbar.jsx` by using `window.location.replace` instead of `window.location` for better URL handling. * Remove `sessionStorage`-based logout flow logic from `navbar.jsx`, `login.tsx`, `api.jsx`, and `user.jsx` to simplify and streamline state management. * Remove unused `auth` and `useAuth` imports from `navbar.jsx` and remove commented-out legacy `useUser` logic from `user.jsx` for cleanup. * Cleanup `api.jsx` by simplifying `useEffect` cleanup logic and consistent formatting adjustments. * Add missing newline at end of `api.jsx` for consistency. * Fix inconsistent object formatting in `api.jsx` within `setRequest` function. * Refactor `login.tsx` to streamline URL query handling and `ConfigProvider` for improved memoization efficiency. * Refactor `useUser` to simplify authentication status logic and update logout flow in `navbar.jsx` to clear user state via `auth.clearCurrentUser`. * Refactor `useUser` to enhance authentication status handling by improving `fetcher` logic and ensuring accurate status updates. * Refactor `useUser` to optimize `fetcher` logic and streamline authentication status updates. * Update `RequiresAuth` to display loading state when user authentication is in progress * Refactor `RequiresAuth` and `useUser` to improve user authentication flow by optimizing loading state handling and revalidation logic. * Refactor `useUser` to simplify `fetcher` logic by removing unnecessary dependencies and improving readability. * Refactor `useUser` to handle unauthenticated status explicitly in `fetcher` logic and update dependencies for improved accuracy. * add changes * Fix inconsistent formatting in `onClick` handler within `navbar.jsx`. * Remove unused `logged` prop from `Layout` in `create-user-with-password.jsx`. * Add `logged` prop to `Layout` in `create-user-with-password.jsx` to ensure proper rendering * changes * Refactor login redirection logic to ensure `state` resets correctly and simplify unauthenticated error handling in API hook. * Remove unused `status` destructure in `useAPI` hook to clean up code. * Refactor login redirection logic and simplify unauthenticated error handling in `useAPI` hook. * Fix typo in `Logout` label text within `navbar.jsx`. * Fix typo in `Logout` label and add missing return statement in `useAPI` hook * Refactor `RequiresAuth` to use `AUTH_STATUS` and centralize authentication state handling * Simplify unauthenticated user check in `RequiresAuth` component. * Refactor `useUser` hook to improve cache validation and enhance loading state tracking in `RequiresAuth`. * Add fallback to `getCurrentUser` in login flow to handle edge cases * Refactor `useUser` hook and login flow to streamline `getCurrentUser` handling and improve state management. * Enhance `useUser` hook with `pageshow` handling and optimize authentication state updates. Simplify `RequiresAuth` loading logic. * Refactor `useUser` hook to remove unused `status`, streamline `fetcher` logic, and enhance `loading` state tracking in `RequiresAuth`. * Remove redundant `getCurrentUser` call in login flow to streamline authentication handling. * Refactor authentication flow to standardize `AUTH_STATUS` usage and improve state management in `RequiresAuth` and `useUser`. * Simplify logout flow by removing redundant authentication state updates. * Remove unused `setAuthStatus` and clean up imports in `navbar.jsx`. * Update logout flow to set `AUTH_STATUS.UNAUTHENTICATED` before redirecting to logout URL. * Simplify logout flow by removing `setAuthStatus`, cleaning up imports, and directly handling user state reset. * Set `AUTH_STATUS.UNAUTHENTICATED` in logout flow and clean up imports in `navbar.jsx`. * Add `setAuthStatus` to `navbar.jsx` and update imports for authentication state management. * Simplify logout flow by replacing `onClick` logic with direct `href` to `logoutUrl`. * Update logout item to replace URL in history before redirect * Remove redundant comment in `RequiresAuth` component. * Refactor authentication flow to check session cookies before making user state transitions. * Update logout flow to set `AUTH_STATUS.UNAUTHENTICATED` and use `window.location.replace` for redirect. * Comment out unused `auth` import in `navbar.jsx`. * Reset user state on logout by calling `auth.clearCurrentUser`. * Refactor auth flow by simplifying cookie checks, consolidating logout logic, and streamlining user state updates. * Update logout flow: clear user state and set `AUTH_STATUS.UNAUTHENTICATED` in `navbar.jsx`. * Simplify logout item by replacing `onClick` logic with direct URL handling and timestamp parameter. * Remove unused `auth` and `AUTH_STATUS` imports in `navbar.jsx`. * Refactor authentication flow: streamline session cookie checks, consolidate auth logic, and enhance user redirection handling. * Remove unused `auth` and `AUTH_STATUS` imports from `navbar.jsx`. * Remove unused `useEffect` and `pageshow` listener for session cookie checks in `requiresAuth.tsx`. * Remove unused `useEffect` import from `requiresAuth.tsx`. * Update logout flow: clear user state, set `AUTH_STATUS.UNAUTHENTICATED`, and redirect to logout URL in `navbar.jsx`. * Refactor authentication flow: unify session cookie checks, simplify state management, and remove redundant logic in `navbar.jsx`, `requiresAuth.tsx`, and `user.jsx`. * Remove unused `auth` import from `navbar.jsx`. * Refactor logout flow: extract `handleLogout` function, update redirection logic, and clean up state management in `navbar.jsx`. * Remove unused `happy-dom` router import and consolidate `LOGIN_COOKIE_NAMES` definition. * Refactor logout logic: replace router navigation with `window.history.replaceState`, clean up `auth.clearCurrentUser` call. * Refactor logout and authentication flow: simplify `handleLogout` logic in `navbar.jsx`, add cleanup for `unload` event in `requiresAuth.tsx`. * Remove unused `auth` import and redundant `as="button"` prop from `navbar.jsx`. * Add BFCache handling to `requiresAuth.tsx` and update session cookie check logic. * Refactor authentication flow: simplify session handling, remove redundant session cookie checks, and clean up `useUser` and `requiresAuth.tsx` logic. * Add BFCache and session cookie handling logic to `requiresAuth.tsx` to ensure proper authentication redirection. * Add eslint-disable comment for unused `__lakefsBFGuard` declaration in `requiresAuth.tsx`. * Remove eslint-disable comment for unused `__lakefsBFGuard` declaration and format global interface in `requiresAuth.tsx`. * Update `onPageShow` type in `requiresAuth.tsx` for better type safety * Remove unused BFCache and session cookie handling logic from `requiresAuth.tsx` to simplify authentication flow. * Refactor logout logic in `navbar.jsx`: add `auth.clearCurrentUser` call before resetting auth status. * Enhance authentication redirection in `useAPI`: add 401 error handling with navigation to login page and simplify `useUser` logic. * Refactor authentication redirection in `useAPI`: consolidate public auth route checks and enhance `next` state handling. * Refactor `useAPI` and `authContext`: consolidate unauthorized handling logic into `onUnauthorized` callback for improved reuse and clarity. * Remove unused `AUTH_STATUS` and `useNavigate` imports from `api.jsx` for cleaner codebase. * Refactor `useUser` and `requiresAuth`: remove `useLocation` dependency and simplify auth status handling logic. * Refactor `requiresAuth`: replace `useAuth` with `useUser` for simplified and consistent authentication handling. * Comment out redundant `setRequest` and `return` logic in 401 error handling within `useAPI`. * Remove commented-out redundant `setRequest` and `return` logic in `useAPI` 401 error handling * Enhance login redirection: persist `next` state in sessionStorage and improve fallback login URL handling. * Refactor login redirection: consolidate `next` state handling and streamline sessionStorage usage. * Refactor login redirection: streamline `redirected` handling, normalize URLs, and enhance `next` state persistence logic. * Refactor login redirection: simplify `next` state handling and remove unnecessary try-catch block. * Refactor login redirection: consolidate rendering logic, centralize auth checks, and simplify `next` state handling. * Refactor login redirection: remove redundant `typeof` check for `next` state. * Refactor login redirection: extract `withNext` function, enhance `next` state handling, and improve redirection cleanup logic. * Enhance login redirection: add `useEffect` to handle `post_login_next` navigation from sessionStorage. * Refactor login flow: introduce `DEFAULT_LOGIN_CONFIG`, streamline login config handling, and consolidate rendering logic. * Refactor login flow: streamline rendering logic, remove `DEFAULT_LOGIN_CONFIG`, and enhance redirection handling with `Navigate`. * Refactor login redirection: simplify loading state, enhance `setup` redirection logic, and improve query string handling. * Refactor login flow: extract `getLoginIntent`, introduce `DoNavigate` and `ExternalRedirect` components, and enhance `next` state handling. * Refactor navbar and login route: remove unused auth context references and simplify redirection logic. * Refactor authentication handling: rename `setAuthStatus` to `setStatus`, replace `AUTH_STATUS.UNKNOWN` with `AUTH_STATUS.PENDING`, and centralize utility functions for redirection and route management. * Refactor authentication flow: replace `buildNextFromWindow` with `getCurrentRelativeUrl`, unify `lakefs_post_login_next` handling, and centralize redirection utilities for consistency. * Refactor authentication flow: remove `useUser` hook, centralize user management in `authContext`, and update components accordingly. * commit * Enhance authContext: refresh user data on `pageshow` for better session consistency after page restores. * Refactor authentication handling: rename `onUnauthorized` to `onUnauthenticated`, improve user refresh logic, and streamline login flow by removing unused utilities and centralizing route management. * Refactor authContext and navbar: remove redundant dependencies, disable cache for user refresh, and improve logout redirection logic. * Format imports in `authContext.tsx` for consistent styling. * commit * changes
1 parent f10b322 commit e2c3727

File tree

15 files changed

+353
-219
lines changed

15 files changed

+353
-219
lines changed

webui/src/lib/auth/authContext.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import React, { createContext, useContext, useMemo, useState, ReactNode, useCallback, useEffect } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { auth } from "../api";
4+
import { getCurrentRelativeUrl, isPublicAuthRoute, ROUTES } from "../utils";
5+
6+
export const LAKEFS_POST_LOGIN_NEXT = "lakefs_post_login_next";
7+
export const AUTH_STATUS = {
8+
AUTHENTICATED: "authenticated",
9+
UNAUTHENTICATED: "unauthenticated",
10+
PENDING: "pending",
11+
} as const;
12+
13+
export type AuthStatus = typeof AUTH_STATUS[keyof typeof AUTH_STATUS];
14+
15+
type User = { id?: string } | null;
16+
17+
type AuthContextType = {
18+
status: AuthStatus;
19+
user: User;
20+
refreshUser: (opts?: { useCache?: boolean }) => Promise<void>;
21+
setStatus: (s: AuthStatus) => void;
22+
onUnauthenticated: () => void;
23+
};
24+
25+
const AuthContext = createContext<AuthContextType | null>(null);
26+
27+
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
28+
const [status, setStatus] = useState<AuthStatus>(AUTH_STATUS.PENDING);
29+
const [user, setUser] = useState<User>(null);
30+
const navigate = useNavigate();
31+
32+
const refreshUser = useCallback(
33+
async ({ useCache = true }: { useCache?: boolean } = {}) => {
34+
if (!useCache && status !== AUTH_STATUS.AUTHENTICATED) setStatus(AUTH_STATUS.PENDING);
35+
try {
36+
const u = useCache
37+
? await auth.getCurrentUserWithCache()
38+
: await auth.getCurrentUser();
39+
const ok = Boolean(u?.id);
40+
setUser(ok ? u : null);
41+
setStatus(ok ? AUTH_STATUS.AUTHENTICATED : AUTH_STATUS.UNAUTHENTICATED);
42+
} catch {
43+
setUser(null);
44+
setStatus(AUTH_STATUS.UNAUTHENTICATED);
45+
}
46+
},
47+
[]
48+
);
49+
50+
// When we get a 401, clear local auth and navigate to the login page.
51+
// We set `redirected: true` and pass `next` (the current URL) so the login page
52+
// knows this is an auth-driven redirect: it should apply the configured login
53+
// strategy (e.g. auto-redirect to SSO) and, after successful auth, return to `next`.
54+
const onUnauthenticated = useCallback(() => {
55+
auth.clearCurrentUser();
56+
setUser(null);
57+
setStatus(AUTH_STATUS.UNAUTHENTICATED);
58+
59+
if (isPublicAuthRoute(window.location.pathname)) return;
60+
61+
navigate(ROUTES.LOGIN, {
62+
replace: true,
63+
state: { redirected: true, next: getCurrentRelativeUrl() },
64+
});
65+
}, [navigate]);
66+
67+
useEffect(() => { void refreshUser({ useCache: false }); }, []);
68+
69+
useEffect(() => {
70+
const onPageShow = (e: PageTransitionEvent) => {
71+
if ((e).persisted) {
72+
void refreshUser({ useCache: false });
73+
}
74+
};
75+
window.addEventListener('pageshow', onPageShow);
76+
return () => window.removeEventListener('pageshow', onPageShow);
77+
}, [refreshUser]);
78+
79+
useEffect(() => {
80+
if (status === AUTH_STATUS.AUTHENTICATED) {
81+
const postLoginNext = window.sessionStorage.getItem(LAKEFS_POST_LOGIN_NEXT);
82+
if (postLoginNext && postLoginNext.startsWith("/")) {
83+
window.sessionStorage.removeItem(LAKEFS_POST_LOGIN_NEXT);
84+
const next = getCurrentRelativeUrl();
85+
if (next !== postLoginNext) {
86+
navigate(postLoginNext, { replace: true });
87+
}
88+
}
89+
}
90+
}, [status, navigate]);
91+
92+
const value = useMemo<AuthContextType>(
93+
() => ({ status, user, refreshUser, setStatus, onUnauthenticated }),
94+
[status, user, refreshUser, onUnauthenticated]
95+
);
96+
97+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
98+
};
99+
100+
export const useAuth = (): AuthContextType => {
101+
const ctx = useContext(AuthContext);
102+
if (!ctx) throw new Error("useAuth must be used within <AuthProvider>");
103+
return ctx;
104+
};

webui/src/lib/components/auth/layout.tsx

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect, useState} from "react";
1+
import React, {useState} from "react";
22
import {Outlet, useOutletContext} from "react-router-dom";
33
import Container from "react-bootstrap/Container";
44
import Row from "react-bootstrap/Row";
@@ -8,8 +8,6 @@ import Card from "react-bootstrap/Card";
88

99
import {Link} from "../nav";
1010
import {useLoginConfigContext} from "../../hooks/conf";
11-
import {useLayoutOutletContext} from "../layout";
12-
import {useRouter} from "../../hooks/router";
1311
import Alert from "react-bootstrap/Alert";
1412
import {InfoIcon} from "@primer/octicons-react";
1513

@@ -20,22 +18,6 @@ export const AuthLayout = () => {
2018
const [showRBACAlert, setShowRBACAlert] = useState(!window.localStorage.getItem(rbacDismissedKey));
2119
const [activeTab, setActiveTab] = useState("credentials");
2220
const {RBAC: rbac} = useLoginConfigContext();
23-
const [isLogged] = useLayoutOutletContext();
24-
const router = useRouter();
25-
26-
useEffect(() => {
27-
if (!isLogged) {
28-
// Redirect to the login page here, instead of in Layout where isLogged is set, because Layout also wraps
29-
// routes that don't require authentication, and redirecting would be incorrect.
30-
router.push({
31-
pathname: '/auth/login',
32-
params: {},
33-
query: { next: '/', redirected: 'true' },
34-
});
35-
}
36-
}, [isLogged, router]);
37-
38-
if (!isLogged) return null;
3921

4022
return (
4123
<Container fluid="xl">
Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,30 @@
1-
import React, { FC, useContext, useState, useEffect } from "react";
2-
import { Outlet, useOutletContext } from "react-router-dom";
1+
import React, { FC, useContext, useEffect } from "react";
2+
import { Outlet } from "react-router-dom";
33
import { ConfigProvider } from "../hooks/configProvider";
44
import TopNav from './navbar';
55
import { AppContext } from "../hooks/appContext";
6-
import useUser from "../hooks/user";
6+
import {AUTH_STATUS, useAuth} from "../auth/authContext";
77

8-
type LayoutOutletContext = [boolean];
8+
const Layout: FC = () => {
9+
const { status } = useAuth();
10+
const showTopNav = status === AUTH_STATUS.AUTHENTICATED;
911

10-
const Layout: FC<{logged: boolean}> = ({logged}) => {
11-
const [isLogged, setIsLogged] = useState(logged ?? true);
12-
const { user, loading, error } = useUser();
13-
const userWithId = user as { id?: string } | null;
14-
15-
// Update isLogged state based on actual authentication status
12+
const { state } = useContext(AppContext);
1613
useEffect(() => {
17-
if (!loading) {
18-
// If there's a user and no error, show authenticated (full) navbar
19-
setIsLogged(!!userWithId?.id && !error);
20-
}
21-
}, [userWithId, loading, error]);
22-
23-
// handle global dark mode here
24-
const {state} = useContext(AppContext);
25-
document.documentElement.setAttribute('data-bs-theme', state.settings.darkMode ? 'dark' : 'light')
14+
document.documentElement.setAttribute(
15+
"data-bs-theme",
16+
state.settings.darkMode ? "dark" : "light"
17+
);
18+
}, [state.settings.darkMode]);
2619

2720
return (
2821
<ConfigProvider>
29-
{!loading && <TopNav logged={isLogged}/>}
22+
{showTopNav && <TopNav/>}
3023
<div className="main-app">
31-
<Outlet context={[isLogged] satisfies LayoutOutletContext}/>
24+
<Outlet />
3225
</div>
3326
</ConfigProvider>
3427
);
3528
};
3629

37-
export function useLayoutOutletContext() {
38-
return useOutletContext<LayoutOutletContext>();
39-
}
40-
41-
export default Layout;
30+
export default Layout;

webui/src/lib/components/navbar.jsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import React from "react";
2-
import useUser from '../hooks/user'
3-
import {auth} from "../api";
42
import {useRouter} from "../hooks/router";
53
import {Link} from "./nav";
64
import DarkModeToggle from "./darkModeToggle";
@@ -9,15 +7,19 @@ import Container from "react-bootstrap/Container";
97
import {useLoginConfigContext} from "../hooks/conf";
108
import {FeedPersonIcon} from "@primer/octicons-react";
119
import {useConfigContext} from "../hooks/configProvider";
10+
import {auth} from "../api";
11+
import {AUTH_STATUS, LAKEFS_POST_LOGIN_NEXT, useAuth} from "../auth/authContext";
12+
import {ROUTES} from "../utils";
1213

1314
const NavUserInfo = () => {
14-
const { user, loading: userLoading, error } = useUser();
15+
const { user, status } = useAuth();
16+
const userLoading = status === AUTH_STATUS.PENDING;
1517
const logoutUrl = useLoginConfigContext()?.logout_url || "/logout"
1618
const {config, error: versionError, loading: versionLoading} = useConfigContext();
1719
const versionConfig = config?.versionConfig || {};
1820

1921
if (userLoading || versionLoading) return <Navbar.Text>Loading...</Navbar.Text>;
20-
if (!user || !!error) return (<></>);
22+
if (!user) return (<></>);
2123
const notifyNewVersion = !versionLoading && !versionError && versionConfig.upgrade_recommended
2224
const NavBarTitle = () => {
2325
return (
@@ -37,17 +39,19 @@ const NavUserInfo = () => {
3739
</>
3840
</NavDropdown.Item><NavDropdown.Divider/></>}
3941
<NavDropdown.Item
40-
onClick={()=> {
42+
onClick={() => {
4143
auth.clearCurrentUser();
42-
window.location = logoutUrl;
44+
window.sessionStorage.removeItem(LAKEFS_POST_LOGIN_NEXT);
45+
window.history.replaceState(null, "", `${ROUTES.LOGIN}?redirected=true`);
46+
window.location.replace(logoutUrl);
4347
}}>
4448
Logout
4549
</NavDropdown.Item>
4650
<NavDropdown.Divider/>
4751
{!versionLoading && !versionError && <>
48-
<NavDropdown.Item disabled={true}>
49-
{`${versionConfig.version_context} ${versionConfig.version}`}
50-
</NavDropdown.Item></>}
52+
<NavDropdown.Item disabled={true}>
53+
{`${versionConfig.version_context} ${versionConfig.version}`}
54+
</NavDropdown.Item></>}
5155
</NavDropdown>
5256
);
5357
};
@@ -63,8 +67,8 @@ const TopNavLink = ({ href, children }) => {
6367
);
6468
};
6569

66-
const TopNav = ({logged = true}) => {
67-
return logged && (
70+
const TopNav = () => {
71+
return (
6872
<Navbar variant="dark" bg="dark" expand="md" className="border-bottom">
6973
<Container fluid={true}>
7074
<Link component={Navbar.Brand} href="/">
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
import {Navigate, Outlet} from "react-router-dom";
3+
import {Loading} from "./controls";
4+
import {ROUTES} from "../utils";
5+
import {getCurrentRelativeUrl} from "../utils";
6+
import {AUTH_STATUS, useAuth} from "../auth/authContext";
7+
8+
const RequiresAuth: React.FC = () => {
9+
const { user, status } = useAuth();
10+
11+
if (status === AUTH_STATUS.PENDING) return <Loading />;
12+
if (!user) {
13+
const next = getCurrentRelativeUrl();
14+
const params = new URLSearchParams({ redirected: "true", next });
15+
return <Navigate to={{ pathname: ROUTES.LOGIN, search: `?${params.toString()}` }} replace state={{ redirected: true, next }}/>;
16+
}
17+
18+
return <Outlet/>;
19+
};
20+
21+
export default RequiresAuth;

webui/src/lib/hooks/api.jsx

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useEffect, useState} from 'react';
22
import {AuthenticationError} from "../api";
3-
import {useRouter} from "./router";
3+
import {useAuth} from "../auth/authContext";
44

55
const initialPaginationState = {
66
loading: true,
@@ -50,46 +50,23 @@ const initialAPIState = {
5050
};
5151

5252
export const useAPI = (promise, deps = []) => {
53-
const router = useRouter();
5453
const [request, setRequest] = useState(initialAPIState);
55-
const [needToLogin, setNeedToLogin] = useState(false);
56-
57-
useEffect(() => {
58-
if (needToLogin) {
59-
const loginPathname = '/auth/login';
60-
if (router.route === loginPathname) {
61-
return;
62-
}
63-
// If the user is not logged in and attempts to access a lakeFS endpoint other than '/auth/login',
64-
// they are first redirected to the '/auth/login' endpoint. For users logging in via lakeFS
65-
// (not via SSO), after successful authentication they will be redirected back to the original endpoint
66-
// they attempted to access. The redirected flag is set here so it can later be used to properly
67-
// handle SSO redirection when login via SSO is configured.
68-
router.push({
69-
pathname: loginPathname,
70-
query: {next: router.route, redirected: true},
71-
});
72-
setNeedToLogin(false);
73-
}
74-
}, [needToLogin, router])
54+
const { onUnauthenticated } = useAuth();
7555

7656
useEffect(() => {
7757
let isMounted = true;
7858
setRequest(initialAPIState);
7959
const execute = async () => {
8060
try {
8161
const response = await promise();
82-
setRequest({
83-
loading: false,
84-
error: null,
85-
response,
86-
});
62+
if (!isMounted) return;
63+
setRequest({ loading: false, error: null, response });
8764
} catch (error) {
88-
if (error instanceof AuthenticationError) {
89-
if (isMounted) {
90-
setNeedToLogin(true);
91-
}
92-
return;
65+
if (!isMounted) return;
66+
// On 401 we delegate to onUnauthenticated(), which redirects to /auth/login
67+
// with { redirected: true, next } so the login page can apply SSO and return.
68+
if (error instanceof AuthenticationError && error.status === 401) {
69+
onUnauthenticated();
9370
}
9471
setRequest({
9572
loading: false,

webui/src/lib/hooks/configProvider.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React, { createContext, FC, useContext, useEffect, useState, } from "react";
1+
import React, {createContext, FC, useContext, useEffect, useMemo} from "react";
22

33
import { config } from "../api";
4-
import useUser from "./user";
54
import { usePluginManager } from "../../extendable/plugins/pluginsContext";
5+
import {useAPI} from "./api";
6+
import {useAuth} from "../auth/authContext";
67

78
type ConfigContextType = {
89
error: Error | null;
@@ -57,21 +58,23 @@ const useConfigContext = () => useContext(configContext);
5758

5859
const ConfigProvider: FC<{children: React.ReactNode}> = ({children}) => {
5960
const pluginManager = usePluginManager();
60-
const {user} = useUser();
61-
const [storageConfig, setConfig] = useState<ConfigContextType>(configInitialState);
61+
const {user} = useAuth();
62+
const { response, loading, error } = useAPI(() => config.getConfig(), [user]);
6263

6364
useEffect(() => {
64-
config.getConfig()
65-
.then(configData => {
66-
pluginManager.customObjectRenderers?.init(configData);
67-
setConfig({config: configData, loading: false, error: null});
68-
})
69-
.catch((error) =>
70-
setConfig({config: null, loading: false, error}));
71-
}, [user]);
65+
if (response) {
66+
pluginManager.customObjectRenderers?.init(response);
67+
}
68+
}, [response, pluginManager]);
69+
70+
const value = useMemo(
71+
() => (
72+
{ config: response ?? null, loading, error } satisfies ConfigContextType
73+
),
74+
[response, loading, error]);
7275

7376
return (
74-
<configContext.Provider value={storageConfig}>
77+
<configContext.Provider value={value}>
7578
{children}
7679
</configContext.Provider>
7780
);

0 commit comments

Comments
 (0)