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 { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||||
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
||||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
|
||||||
import { LoginPage } from '@/features/authentication/screens/LoginPage.tsx';
|
import { LoginPage } from '@/features/authentication/screens/LoginPage.tsx';
|
||||||
import { AuthGuard } from '@/features/authentication/components/AuthGuard.tsx';
|
import { AuthGuard } from '@/features/authentication/components/AuthGuard.tsx';
|
||||||
import { SearchParam } from '@/base/Base.types.ts';
|
import { SearchParam } from '@/base/Base.types.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
import { GlobalDialog } from '@/base/global-dialog/GlobalDialog.tsx';
|
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 { Browse } = loadable(() => import('@/features/browse/screens/Browse.tsx'), lazyLoadFallback);
|
||||||
const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
|
const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
|
||||||
@@ -103,8 +103,8 @@ const ScrollToTop = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const InitialBackgroundRequests = () => {
|
const InitialBackgroundRequests = () => {
|
||||||
const { isAuthRequired, accessToken } = useSessionContext();
|
const { isAuthRequired, accessToken } = AuthManager.useSession();
|
||||||
const skipConnection = isAuthRequired == null || (!!isAuthRequired && !accessToken);
|
const skipConnection = isAuthRequired == null || (!isAuthRequired && !accessToken);
|
||||||
|
|
||||||
const [fetchExtensionList] = requestManager.useExtensionListFetch();
|
const [fetchExtensionList] = requestManager.useExtensionListFetch();
|
||||||
|
|
||||||
@@ -129,9 +129,9 @@ const InitialBackgroundRequests = () => {
|
|||||||
*/
|
*/
|
||||||
const BackgroundSubscriptions = () => {
|
const BackgroundSubscriptions = () => {
|
||||||
// Listen to session changes
|
// 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
|
// Load the full download status once on startup to fill the cache
|
||||||
requestManager.useGetDownloadStatus({ nextFetchPolicy: 'standby' });
|
requestManager.useGetDownloadStatus({ nextFetchPolicy: 'standby' });
|
||||||
@@ -153,7 +153,7 @@ const ReactRouterSetter = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PrivateRoutes = () => {
|
const PrivateRoutes = () => {
|
||||||
const { isAuthRequired, accessToken, refreshToken } = useSessionContext();
|
const { isAuthRequired, accessToken, refreshToken } = AuthManager.useSession();
|
||||||
|
|
||||||
const isAuthenticated = !isAuthRequired || (isAuthRequired && (accessToken || refreshToken));
|
const isAuthenticated = !isAuthRequired || (isAuthRequired && (accessToken || refreshToken));
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { SnackbarWithDescription } from '@/base/components/feedback/SnackbarWith
|
|||||||
import { AppPageHistoryContextProvider } from '@/base/contexts/AppPageHistoryContext.tsx';
|
import { AppPageHistoryContextProvider } from '@/base/contexts/AppPageHistoryContext.tsx';
|
||||||
import { AppThemeContextProvider } from '@/features/theme/AppThemeContext.tsx';
|
import { AppThemeContextProvider } from '@/features/theme/AppThemeContext.tsx';
|
||||||
import { NavBarContextProvider } from '@/features/navigation-bar/NavbarContext.tsx';
|
import { NavBarContextProvider } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||||
import { SessionContextProvider } from '@/features/authentication/SessionContext.tsx';
|
|
||||||
import { SubpathUtil } from '@/lib/utils/SubpathUtil.ts';
|
import { SubpathUtil } from '@/lib/utils/SubpathUtil.ts';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -26,31 +25,29 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AppContext: React.FC<Props> = ({ children }) => (
|
export const AppContext: React.FC<Props> = ({ children }) => (
|
||||||
<SessionContextProvider>
|
<Router basename={SubpathUtil.getRouterBasename()}>
|
||||||
<Router basename={SubpathUtil.getRouterBasename()}>
|
<StyledEngineProvider injectFirst>
|
||||||
<StyledEngineProvider injectFirst>
|
<AppThemeContextProvider>
|
||||||
<AppThemeContextProvider>
|
<QueryParamProvider adapter={ReactRouter6Adapter}>
|
||||||
<QueryParamProvider adapter={ReactRouter6Adapter}>
|
<NavBarContextProvider>
|
||||||
<NavBarContextProvider>
|
<AppPageHistoryContextProvider>
|
||||||
<AppPageHistoryContextProvider>
|
<ActiveDeviceContextProvider>
|
||||||
<ActiveDeviceContextProvider>
|
<SnackbarProvider
|
||||||
<SnackbarProvider
|
Components={{
|
||||||
Components={{
|
default: SnackbarWithDescription,
|
||||||
default: SnackbarWithDescription,
|
info: SnackbarWithDescription,
|
||||||
info: SnackbarWithDescription,
|
success: SnackbarWithDescription,
|
||||||
success: SnackbarWithDescription,
|
warning: SnackbarWithDescription,
|
||||||
warning: SnackbarWithDescription,
|
error: SnackbarWithDescription,
|
||||||
error: SnackbarWithDescription,
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<AppHotkeysProvider>{children}</AppHotkeysProvider>
|
||||||
<AppHotkeysProvider>{children}</AppHotkeysProvider>
|
</SnackbarProvider>
|
||||||
</SnackbarProvider>
|
</ActiveDeviceContextProvider>
|
||||||
</ActiveDeviceContextProvider>
|
</AppPageHistoryContextProvider>
|
||||||
</AppPageHistoryContextProvider>
|
</NavBarContextProvider>
|
||||||
</NavBarContextProvider>
|
</QueryParamProvider>
|
||||||
</QueryParamProvider>
|
</AppThemeContextProvider>
|
||||||
</AppThemeContextProvider>
|
</StyledEngineProvider>
|
||||||
</StyledEngineProvider>
|
</Router>
|
||||||
</Router>
|
|
||||||
</SessionContextProvider>
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,34 +6,71 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useSyncExternalStore } from 'react';
|
||||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||||
import { useSessionStorage } from '@/base/hooks/useStorage.tsx';
|
|
||||||
|
let notifierValue = 0;
|
||||||
|
|
||||||
export class AuthManager {
|
export class AuthManager {
|
||||||
static readonly AUTH_REQUIRED_KEY = 'auth-required';
|
|
||||||
|
|
||||||
static readonly REFRESH_TOKEN_KEY = 'auth-refresh-token';
|
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 accessToken: string | null = null;
|
||||||
|
|
||||||
private static authInitialized: boolean = false;
|
private static authInitialized: boolean = false;
|
||||||
|
|
||||||
|
private static authRequired: boolean | null = null;
|
||||||
|
|
||||||
private static refreshingToken: boolean = false;
|
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 {
|
static isAuthRequired(): boolean | null {
|
||||||
return AppStorage.session.getItemParsed(AuthManager.AUTH_REQUIRED_KEY, null);
|
return AuthManager.authRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
static setAuthRequired(value: boolean | null): void {
|
static isRefreshingToken(): boolean {
|
||||||
AppStorage.session.setItem(AuthManager.AUTH_REQUIRED_KEY, value);
|
return AuthManager.refreshingToken;
|
||||||
}
|
|
||||||
|
|
||||||
static useIsAuthRequired(): boolean | null {
|
|
||||||
const [value] = useSessionStorage(AuthManager.AUTH_REQUIRED_KEY, null);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getAccessToken(): string | null {
|
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 {
|
static setAccessToken(token: string): void {
|
||||||
AuthManager.accessToken = token;
|
AuthManager.accessToken = token;
|
||||||
AuthManager.refreshReactSessionContext();
|
AuthManager.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
static setRefreshToken(token: string): void {
|
static setRefreshToken(token: string): void {
|
||||||
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, token);
|
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, token);
|
||||||
AuthManager.refreshReactSessionContext();
|
AuthManager.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
static setTokens(accessToken: string, refreshToken: string): void {
|
static setTokens(accessToken: string, refreshToken: string): void {
|
||||||
@@ -68,12 +120,12 @@ export class AuthManager {
|
|||||||
|
|
||||||
static removeAccessToken(): void {
|
static removeAccessToken(): void {
|
||||||
AuthManager.accessToken = null;
|
AuthManager.accessToken = null;
|
||||||
AuthManager.refreshReactSessionContext();
|
AuthManager.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeRefreshToken(): void {
|
static removeRefreshToken(): void {
|
||||||
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, undefined);
|
AppStorage.session.setItem(AuthManager.REFRESH_TOKEN_KEY, undefined);
|
||||||
AuthManager.refreshReactSessionContext();
|
AuthManager.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeTokens(): void {
|
static removeTokens(): void {
|
||||||
@@ -81,36 +133,6 @@ export class AuthManager {
|
|||||||
AuthManager.removeRefreshToken();
|
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 {
|
static shouldQueueRequests(): boolean {
|
||||||
const isLoginRequired = AuthManager.isAuthRequired() === true && AuthManager.getAccessToken() === null;
|
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 { ReactNode } from 'react';
|
||||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
|
||||||
import { SplashScreen } from '@/features/authentication/components/SplashScreen.tsx';
|
import { SplashScreen } from '@/features/authentication/components/SplashScreen.tsx';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||||
|
|
||||||
AuthManager.setAuthRequired(null);
|
|
||||||
|
|
||||||
export const AuthGuard = ({ children }: { children: ReactNode }) => {
|
export const AuthGuard = ({ children }: { children: ReactNode }) => {
|
||||||
const { isAuthRequired } = useSessionContext();
|
const { isAuthRequired } = AuthManager.useSession();
|
||||||
|
|
||||||
requestManager.useGetAbout({
|
requestManager.useGetAbout({
|
||||||
skip: isAuthRequired !== null,
|
skip: isAuthRequired !== null,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|||||||
import { makeToast } from '@/base/utils/Toast.ts';
|
import { makeToast } from '@/base/utils/Toast.ts';
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||||
import { useSessionContext } from '@/features/authentication/SessionContext.tsx';
|
|
||||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
|
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||||
import { SearchParam } from '@/base/Base.types.ts';
|
import { SearchParam } from '@/base/Base.types.ts';
|
||||||
@@ -31,7 +30,7 @@ export const LoginPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setOverride } = useNavBarContext();
|
const { setOverride } = useNavBarContext();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isAuthRequired, accessToken, refreshToken } = useSessionContext();
|
const { isAuthRequired, accessToken, refreshToken } = AuthManager.useSession();
|
||||||
|
|
||||||
const [redirect] = useQueryParam(SearchParam.REDIRECT, StringParam);
|
const [redirect] = useQueryParam(SearchParam.REDIRECT, StringParam);
|
||||||
const [loginUser, { loading: isLoading }] = requestManager.useLoginUser();
|
const [loginUser, { loading: isLoading }] = requestManager.useLoginUser();
|
||||||
@@ -39,7 +38,7 @@ export const LoginPage = () => {
|
|||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
const isAuthenticated = !isAuthRequired || (!!isAuthRequired && !!accessToken && !!refreshToken);
|
const isAuthenticated = !isAuthRequired || (isAuthRequired && !!accessToken && !!refreshToken);
|
||||||
|
|
||||||
const doLogin = async () => {
|
const doLogin = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user