mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add authentication/connection checks for react-router routes
This commit is contained in:
parent
b2372a96e2
commit
1aeb90d323
2 changed files with 171 additions and 1 deletions
162
src/components/ConnectedRoute.tsx
Normal file
162
src/components/ConnectedRoute.tsx
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import globalize from '../scripts/globalize';
|
||||||
|
|
||||||
|
import alert from './alert';
|
||||||
|
import { appRouter } from './appRouter';
|
||||||
|
import loading from './loading/loading';
|
||||||
|
import ServerConnections from './ServerConnections';
|
||||||
|
|
||||||
|
enum Routes {
|
||||||
|
Home = '/home.html',
|
||||||
|
Login = '/login.html',
|
||||||
|
SelectServer = '/selectserver.html',
|
||||||
|
StartWizard = '/wizardstart.html'
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This should probably be in the SDK
|
||||||
|
enum ConnectionState {
|
||||||
|
SignedIn = 'SignedIn',
|
||||||
|
ServerSignIn = 'ServerSignIn',
|
||||||
|
ServerSelection = 'ServerSelection',
|
||||||
|
ServerUpdateNeeded = 'ServerUpdateNeeded'
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectedRouteProps = {
|
||||||
|
isAdminRoute?: boolean,
|
||||||
|
isUserRoute?: boolean,
|
||||||
|
roles?: string[]
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConnectedRoute: FunctionComponent<ConnectedRouteProps> = ({
|
||||||
|
children,
|
||||||
|
isAdminRoute = false,
|
||||||
|
isUserRoute = true
|
||||||
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [ isLoading, setIsLoading ] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const bounce = async (connectionResponse: any) => {
|
||||||
|
switch (connectionResponse.State) {
|
||||||
|
case ConnectionState.SignedIn:
|
||||||
|
// Already logged in, bounce to the home page
|
||||||
|
console.debug('[ConnectedRoute] already logged in, redirecting to home');
|
||||||
|
navigate(Routes.Home);
|
||||||
|
return;
|
||||||
|
case ConnectionState.ServerSignIn:
|
||||||
|
// Bounce to the login page
|
||||||
|
console.debug('[ConnectedRoute] not logged in, redirecting to login page');
|
||||||
|
navigate(Routes.Login, {
|
||||||
|
state: {
|
||||||
|
serverid: connectionResponse.ApiClient.serverId()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case ConnectionState.ServerSelection:
|
||||||
|
// Bounce to select server page
|
||||||
|
console.debug('[ConnectedRoute] redirecting to select server page');
|
||||||
|
navigate(Routes.SelectServer);
|
||||||
|
return;
|
||||||
|
case ConnectionState.ServerUpdateNeeded:
|
||||||
|
// Show update needed message and bounce to select server page
|
||||||
|
try {
|
||||||
|
await alert({
|
||||||
|
text: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'),
|
||||||
|
html: globalize.translate('ServerUpdateNeeded', '<a href="https://github.com/jellyfin/jellyfin">https://github.com/jellyfin/jellyfin</a>')
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
console.warn('[ConnectedRoute] failed to show alert', ex);
|
||||||
|
}
|
||||||
|
console.debug('[ConnectedRoute] server update required, redirecting to select server page');
|
||||||
|
navigate(Routes.SelectServer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('[ConnectedRoute] unhandled connection state', connectionResponse.State);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async () => {
|
||||||
|
// Check connection status on initial page load
|
||||||
|
const firstConnection = appRouter.firstConnectionResult;
|
||||||
|
appRouter.firstConnectionResult = null;
|
||||||
|
|
||||||
|
if (firstConnection && firstConnection.State !== ConnectionState.SignedIn) {
|
||||||
|
if (firstConnection.State === ConnectionState.ServerSignIn) {
|
||||||
|
// Verify the wizard is complete
|
||||||
|
try {
|
||||||
|
const infoResponse = await fetch(`${firstConnection.ApiClient.serverAddress()}/System/Info/Public`);
|
||||||
|
if (!infoResponse.ok) {
|
||||||
|
throw new Error('Public system info request failed');
|
||||||
|
}
|
||||||
|
const systemInfo = await infoResponse.json();
|
||||||
|
if (!systemInfo.StartupWizardCompleted) {
|
||||||
|
// Bounce to the wizard
|
||||||
|
console.info('[ConnectedRoute] startup wizard is not complete, redirecting there');
|
||||||
|
navigate(Routes.StartWizard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
console.error('[ConnectedRoute] checking wizard status failed', ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounce to the correct page in the login flow
|
||||||
|
bounce(firstConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should exiting the app be handled here?
|
||||||
|
|
||||||
|
const client = ServerConnections.currentApiClient();
|
||||||
|
|
||||||
|
// If this is a user route, ensure a user is logged in
|
||||||
|
if ((isAdminRoute || isUserRoute) && !client?.isLoggedIn()) {
|
||||||
|
try {
|
||||||
|
console.warn('[ConnectedRoute] unauthenticated user attempted to access user route');
|
||||||
|
bounce(await ServerConnections.connect());
|
||||||
|
} catch (ex) {
|
||||||
|
console.warn('[ConnectedRoute] error bouncing from user route', ex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is an admin route, ensure the user has access
|
||||||
|
if (isAdminRoute) {
|
||||||
|
try {
|
||||||
|
const user = await client.getCurrentUser();
|
||||||
|
if (!user.Policy.IsAdministrator) {
|
||||||
|
console.warn('[ConnectedRoute] normal user attempted to access admin route');
|
||||||
|
bounce(await ServerConnections.connect());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
console.warn('[ConnectedRoute] error bouncing from admin route', ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
loading.show();
|
||||||
|
validateConnection();
|
||||||
|
}, [ isAdminRoute, isUserRoute, navigate ]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading) {
|
||||||
|
loading.hide();
|
||||||
|
}
|
||||||
|
}, [ isLoading ]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>{children}</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConnectedRoute;
|
|
@ -1,12 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
|
import ConnectedRoute from '../components/ConnectedRoute';
|
||||||
import SearchPage from './search';
|
import SearchPage from './search';
|
||||||
|
|
||||||
const AppRoutes = () => (
|
const AppRoutes = () => (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/'>
|
<Route path='/'>
|
||||||
<Route path='search.html' element={<SearchPage />} />
|
<Route
|
||||||
|
path='search.html'
|
||||||
|
element={
|
||||||
|
<ConnectedRoute>
|
||||||
|
<SearchPage />
|
||||||
|
</ConnectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
{/* Suppress warnings for unhandled routes */}
|
{/* Suppress warnings for unhandled routes */}
|
||||||
<Route path='*' element={null} />
|
<Route path='*' element={null} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue