Remove "auth required flag" from session storage
With 12ef012e4b this has no benefit anymore, because the app now always checks if authentication is enabled by the server first before any other request can be sent.
This commit is contained in:
12
src/App.tsx
12
src/App.tsx
@@ -24,13 +24,13 @@ import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
||||
import { LoginPage } from '@/features/authentication/screens/LoginPage.tsx';
|
||||
import { AuthGuard } from '@/features/authentication/components/AuthGuard.tsx';
|
||||
import { SearchParam } from '@/base/Base.types.ts';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||
import { GlobalDialog } from '@/base/global-dialog/GlobalDialog.tsx';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
|
||||
const { Browse } = loadable(() => import('@/features/browse/screens/Browse.tsx'), lazyLoadFallback);
|
||||
const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
|
||||
@@ -103,8 +103,8 @@ const ScrollToTop = () => {
|
||||
};
|
||||
|
||||
const InitialBackgroundRequests = () => {
|
||||
const { isAuthRequired, accessToken } = useSessionContext();
|
||||
const skipConnection = isAuthRequired == null || (!!isAuthRequired && !accessToken);
|
||||
const { isAuthRequired, accessToken } = AuthManager.useSession();
|
||||
const skipConnection = isAuthRequired == null || (!isAuthRequired && !accessToken);
|
||||
|
||||
const [fetchExtensionList] = requestManager.useExtensionListFetch();
|
||||
|
||||
@@ -129,9 +129,9 @@ const InitialBackgroundRequests = () => {
|
||||
*/
|
||||
const BackgroundSubscriptions = () => {
|
||||
// Listen to session changes
|
||||
const { isAuthRequired, accessToken } = useSessionContext();
|
||||
const { isAuthRequired, accessToken } = AuthManager.useSession();
|
||||
|
||||
const skipConnection = isAuthRequired == null || (!!isAuthRequired && !accessToken);
|
||||
const skipConnection = isAuthRequired == null || (!isAuthRequired && !accessToken);
|
||||
|
||||
// Load the full download status once on startup to fill the cache
|
||||
requestManager.useGetDownloadStatus({ nextFetchPolicy: 'standby' });
|
||||
@@ -153,7 +153,7 @@ const ReactRouterSetter = () => {
|
||||
};
|
||||
|
||||
const PrivateRoutes = () => {
|
||||
const { isAuthRequired, accessToken, refreshToken } = useSessionContext();
|
||||
const { isAuthRequired, accessToken, refreshToken } = AuthManager.useSession();
|
||||
|
||||
const isAuthenticated = !isAuthRequired || (isAuthRequired && (accessToken || refreshToken));
|
||||
if (!isAuthenticated) {
|
||||
|
||||
@@ -18,7 +18,6 @@ import { SnackbarWithDescription } from '@/base/components/feedback/SnackbarWith
|
||||
import { AppPageHistoryContextProvider } from '@/base/contexts/AppPageHistoryContext.tsx';
|
||||
import { AppThemeContextProvider } from '@/features/theme/AppThemeContext.tsx';
|
||||
import { NavBarContextProvider } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||
import { SessionContextProvider } from '@/features/authentication/SessionContext.tsx';
|
||||
import { SubpathUtil } from '@/lib/utils/SubpathUtil.ts';
|
||||
|
||||
interface Props {
|
||||
@@ -26,31 +25,29 @@ interface Props {
|
||||
}
|
||||
|
||||
export const AppContext: React.FC<Props> = ({ children }) => (
|
||||
<SessionContextProvider>
|
||||
<Router basename={SubpathUtil.getRouterBasename()}>
|
||||
<StyledEngineProvider injectFirst>
|
||||
<AppThemeContextProvider>
|
||||
<QueryParamProvider adapter={ReactRouter6Adapter}>
|
||||
<NavBarContextProvider>
|
||||
<AppPageHistoryContextProvider>
|
||||
<ActiveDeviceContextProvider>
|
||||
<SnackbarProvider
|
||||
Components={{
|
||||
default: SnackbarWithDescription,
|
||||
info: SnackbarWithDescription,
|
||||
success: SnackbarWithDescription,
|
||||
warning: SnackbarWithDescription,
|
||||
error: SnackbarWithDescription,
|
||||
}}
|
||||
>
|
||||
<AppHotkeysProvider>{children}</AppHotkeysProvider>
|
||||
</SnackbarProvider>
|
||||
</ActiveDeviceContextProvider>
|
||||
</AppPageHistoryContextProvider>
|
||||
</NavBarContextProvider>
|
||||
</QueryParamProvider>
|
||||
</AppThemeContextProvider>
|
||||
</StyledEngineProvider>
|
||||
</Router>
|
||||
</SessionContextProvider>
|
||||
<Router basename={SubpathUtil.getRouterBasename()}>
|
||||
<StyledEngineProvider injectFirst>
|
||||
<AppThemeContextProvider>
|
||||
<QueryParamProvider adapter={ReactRouter6Adapter}>
|
||||
<NavBarContextProvider>
|
||||
<AppPageHistoryContextProvider>
|
||||
<ActiveDeviceContextProvider>
|
||||
<SnackbarProvider
|
||||
Components={{
|
||||
default: SnackbarWithDescription,
|
||||
info: SnackbarWithDescription,
|
||||
success: SnackbarWithDescription,
|
||||
warning: SnackbarWithDescription,
|
||||
error: SnackbarWithDescription,
|
||||
}}
|
||||
>
|
||||
<AppHotkeysProvider>{children}</AppHotkeysProvider>
|
||||
</SnackbarProvider>
|
||||
</ActiveDeviceContextProvider>
|
||||
</AppPageHistoryContextProvider>
|
||||
</NavBarContextProvider>
|
||||
</QueryParamProvider>
|
||||
</AppThemeContextProvider>
|
||||
</StyledEngineProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
@@ -6,34 +6,71 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useSyncExternalStore } from 'react';
|
||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
import { useSessionStorage } from '@/base/hooks/useStorage.tsx';
|
||||
|
||||
let notifierValue = 0;
|
||||
|
||||
export class AuthManager {
|
||||
static readonly AUTH_REQUIRED_KEY = 'auth-required';
|
||||
|
||||
static readonly REFRESH_TOKEN_KEY = 'auth-refresh-token';
|
||||
|
||||
static readonly REACT_SESSION_REFRESH_KEY = 'auth-react-session-refresh';
|
||||
private static subscribedCount: number = 0;
|
||||
|
||||
private static subscribers = new Map<number, () => void>();
|
||||
|
||||
private static accessToken: string | null = null;
|
||||
|
||||
private static authInitialized: boolean = false;
|
||||
|
||||
private static authRequired: boolean | null = null;
|
||||
|
||||
private static refreshingToken: boolean = false;
|
||||
|
||||
private static subscribe(callback: () => void): () => void {
|
||||
// eslint-disable-next-line no-plusplus
|
||||
const key = AuthManager.subscribedCount++;
|
||||
this.subscribers.set(key, callback);
|
||||
|
||||
return () => this.unsubscribe(key);
|
||||
}
|
||||
|
||||
private static unsubscribe(key: number): void {
|
||||
this.subscribers.delete(key);
|
||||
}
|
||||
|
||||
private static notify(): void {
|
||||
notifierValue = (notifierValue + 1) % Number.MAX_SAFE_INTEGER;
|
||||
this.subscribers.forEach((callback) => callback());
|
||||
}
|
||||
|
||||
static useSession(): {
|
||||
accessToken: typeof AuthManager.accessToken;
|
||||
refreshToken: ReturnType<typeof AuthManager.getRefreshToken>;
|
||||
isAuthRequired: typeof AuthManager.authRequired;
|
||||
isInitialized: typeof AuthManager.authInitialized;
|
||||
isRefreshingToken: typeof AuthManager.refreshingToken;
|
||||
} {
|
||||
useSyncExternalStore(AuthManager.subscribe.bind(AuthManager), () => notifierValue);
|
||||
|
||||
return {
|
||||
accessToken: AuthManager.accessToken,
|
||||
refreshToken: AuthManager.getRefreshToken(),
|
||||
isAuthRequired: AuthManager.authRequired,
|
||||
isInitialized: AuthManager.authInitialized,
|
||||
isRefreshingToken: AuthManager.refreshingToken,
|
||||
};
|
||||
}
|
||||
|
||||
static isAuthInitialized(): boolean {
|
||||
return AuthManager.authInitialized;
|
||||
}
|
||||
|
||||
static isAuthRequired(): boolean | null {
|
||||
return AppStorage.session.getItemParsed(AuthManager.AUTH_REQUIRED_KEY, null);
|
||||
return AuthManager.authRequired;
|
||||
}
|
||||
|
||||
static setAuthRequired(value: boolean | null): void {
|
||||
AppStorage.session.setItem(AuthManager.AUTH_REQUIRED_KEY, value);
|
||||
}
|
||||
|
||||
static useIsAuthRequired(): boolean | null {
|
||||
const [value] = useSessionStorage(AuthManager.AUTH_REQUIRED_KEY, null);
|
||||
|
||||
return value;
|
||||
static isRefreshingToken(): boolean {
|
||||
return AuthManager.refreshingToken;
|
||||
}
|
||||
|
||||
static getAccessToken(): string | null {
|
||||
@@ -51,14 +88,29 @@ export class AuthManager {
|
||||
};
|
||||
}
|
||||
|
||||
static setAuthInitialized(value: boolean): void {
|
||||
AuthManager.authInitialized = value;
|
||||
AuthManager.notify();
|
||||
}
|
||||
|
||||
static setAuthRequired(value: boolean | null): void {
|
||||
AuthManager.authRequired = value;
|
||||
AuthManager.notify();
|
||||
}
|
||||
|
||||
static setIsRefreshingToken(value: boolean): void {
|
||||
AuthManager.refreshingToken = value;
|
||||
AuthManager.notify();
|
||||
}
|
||||
|
||||
static setAccessToken(token: string): void {
|
||||
AuthManager.accessToken = token;
|
||||
AuthManager.refreshReactSessionContext();
|
||||
AuthManager.notify();
|
||||
}
|
||||
|
||||
static setRefreshToken(token: string): void {
|
||||
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, token);
|
||||
AuthManager.refreshReactSessionContext();
|
||||
AuthManager.notify();
|
||||
}
|
||||
|
||||
static setTokens(accessToken: string, refreshToken: string): void {
|
||||
@@ -68,12 +120,12 @@ export class AuthManager {
|
||||
|
||||
static removeAccessToken(): void {
|
||||
AuthManager.accessToken = null;
|
||||
AuthManager.refreshReactSessionContext();
|
||||
AuthManager.notify();
|
||||
}
|
||||
|
||||
static removeRefreshToken(): void {
|
||||
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, undefined);
|
||||
AuthManager.refreshReactSessionContext();
|
||||
AuthManager.notify();
|
||||
}
|
||||
|
||||
static removeTokens(): void {
|
||||
@@ -81,36 +133,6 @@ export class AuthManager {
|
||||
AuthManager.removeRefreshToken();
|
||||
}
|
||||
|
||||
private static getNextReactSessionContextId(): number {
|
||||
const id = AppStorage.session.getItemParsed(AuthManager.REACT_SESSION_REFRESH_KEY, 0);
|
||||
|
||||
return (id + 1) % Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
static refreshReactSessionContext(): void {
|
||||
AppStorage.session.setItem(AuthManager.REACT_SESSION_REFRESH_KEY, AuthManager.getNextReactSessionContextId());
|
||||
}
|
||||
|
||||
static useListenToReactSessionContextRefreshEvent(): void {
|
||||
useSessionStorage(AuthManager.REACT_SESSION_REFRESH_KEY, 0);
|
||||
}
|
||||
|
||||
static isAuthInitialized(): boolean {
|
||||
return AuthManager.authInitialized;
|
||||
}
|
||||
|
||||
static setAuthInitialized(value: boolean): void {
|
||||
AuthManager.authInitialized = value;
|
||||
}
|
||||
|
||||
static isRefreshingToken(): boolean {
|
||||
return AuthManager.refreshingToken;
|
||||
}
|
||||
|
||||
static setIsRefreshingToken(value: boolean): void {
|
||||
AuthManager.refreshingToken = value;
|
||||
}
|
||||
|
||||
static shouldQueueRequests(): boolean {
|
||||
const isLoginRequired = AuthManager.isAuthRequired() === true && AuthManager.getAccessToken() === null;
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { createContext, ReactNode, useContext, useMemo } from 'react';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
|
||||
interface TSessionContext {
|
||||
isAuthRequired: boolean | null;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
}
|
||||
|
||||
const SessionContext = createContext<TSessionContext>({
|
||||
isAuthRequired: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
});
|
||||
|
||||
export const SessionContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
AuthManager.useListenToReactSessionContextRefreshEvent();
|
||||
|
||||
const isAuthRequired = AuthManager.useIsAuthRequired();
|
||||
const { accessToken, refreshToken } = AuthManager.getTokens();
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
isAuthRequired,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
}),
|
||||
[isAuthRequired, accessToken, refreshToken],
|
||||
);
|
||||
|
||||
return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>;
|
||||
};
|
||||
|
||||
export const useSessionContext = () => useContext(SessionContext);
|
||||
@@ -7,15 +7,12 @@
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
||||
import { SplashScreen } from '@/features/authentication/components/SplashScreen.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
|
||||
AuthManager.setAuthRequired(null);
|
||||
|
||||
export const AuthGuard = ({ children }: { children: ReactNode }) => {
|
||||
const { isAuthRequired } = useSessionContext();
|
||||
const { isAuthRequired } = AuthManager.useSession();
|
||||
|
||||
requestManager.useGetAbout({
|
||||
skip: isAuthRequired !== null,
|
||||
|
||||
@@ -19,7 +19,6 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||
import { SearchParam } from '@/base/Base.types.ts';
|
||||
@@ -31,7 +30,7 @@ export const LoginPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { setOverride } = useNavBarContext();
|
||||
const navigate = useNavigate();
|
||||
const { isAuthRequired, accessToken, refreshToken } = useSessionContext();
|
||||
const { isAuthRequired, accessToken, refreshToken } = AuthManager.useSession();
|
||||
|
||||
const [redirect] = useQueryParam(SearchParam.REDIRECT, StringParam);
|
||||
const [loginUser, { loading: isLoading }] = requestManager.useLoginUser();
|
||||
@@ -39,7 +38,7 @@ export const LoginPage = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const isAuthenticated = !isAuthRequired || (!!isAuthRequired && !!accessToken && !!refreshToken);
|
||||
const isAuthenticated = !isAuthRequired || (isAuthRequired && !!accessToken && !!refreshToken);
|
||||
|
||||
const doLogin = async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user