fix(auth): cookie expiry and renewal (#1527)

* fix(auth/web): logout when expired/invalid/no cookie is present

* fix(auth/web): specify error message in invalid cookie

* fix(auth/web): reset error boundary on login

* fix(auth/web): fix onboarding

* chore: code cleanup

* fix(web): revert tanstack/router to 1.31.0

* refactor(web): remove react-error-boundary

* feat(auth): refresh cookie when close to expiry

* enhancement(web): specify defaultError message in HttpClient

* fix(web): use absolute paths for router links (#1530)

* chore(web): bump `@tanstack/react-router` to `1.31.6`

* fix(web): settings routes

* fix(web): filter routes

* fix(web): remove unused ReleasesIndexRoute

* chore(web): add documentation for HttpClient

* chore(lint): remove unnecessary whitespace
This commit is contained in:
martylukyy 2024-05-08 10:38:02 +02:00 committed by GitHub
parent 3dab295387
commit 8120c33f6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 364 additions and 366 deletions

View file

@ -7,11 +7,11 @@ import {
createRootRouteWithContext,
createRoute,
createRouter,
ErrorComponent,
Navigate,
notFound,
Outlet,
redirect,
} from "@tanstack/react-router";
} from "@tanstack/react-router";
import { z } from "zod";
import { QueryClient } from "@tanstack/react-query";
@ -46,11 +46,13 @@ import DownloadClientSettings from "@screens/settings/DownloadClient";
import FeedSettings from "@screens/settings/Feed";
import { Dashboard } from "@screens/Dashboard";
import AccountSettings from "@screens/settings/Account";
import { AuthContext, AuthCtx, localStorageUserKey, SettingsContext } from "@utils/Context";
import { AuthContext, SettingsContext } from "@utils/Context";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { queryClient } from "@api/QueryClient";
import { ErrorPage } from "@components/alerts";
const DashboardRoute = createRoute({
getParentRoute: () => AuthIndexRoute,
path: '/',
@ -133,16 +135,9 @@ export const FilterActionsRoute = createRoute({
component: Actions
});
const ReleasesRoute = createRoute({
export const ReleasesRoute = createRoute({
getParentRoute: () => AuthIndexRoute,
path: 'releases'
});
// type ReleasesSearch = z.infer<typeof releasesSearchSchema>
export const ReleasesIndexRoute = createRoute({
getParentRoute: () => ReleasesRoute,
path: '/',
path: 'releases',
component: Releases,
validateSearch: (search) => z.object({
offset: z.number().optional(),
@ -260,7 +255,7 @@ export const LoginRoute = createRoute({
validateSearch: z.object({
redirect: z.string().optional(),
}),
beforeLoad: ({ navigate}) => {
beforeLoad: ({ navigate }) => {
// handle canOnboard
APIClient.auth.canOnboard().then(() => {
console.info("onboarding available, redirecting")
@ -277,44 +272,36 @@ export const AuthRoute = createRoute({
id: 'auth',
// Before loading, authenticate the user via our auth context
// This will also happen during prefetching (e.g. hovering over links, etc.)
beforeLoad: ({context, location}) => {
beforeLoad: ({ context, location }) => {
// If the user is not logged in, check for item in localStorage
if (!context.auth.isLoggedIn) {
const storage = localStorage.getItem(localStorageUserKey);
if (storage) {
try {
const json = JSON.parse(storage);
if (json === null) {
console.warn(`JSON localStorage value for '${localStorageUserKey}' context state is null`);
} else {
context.auth.isLoggedIn = json.isLoggedIn
context.auth.username = json.username
}
} catch (e) {
console.error(`auth Failed to merge ${localStorageUserKey} context state: ${e}`);
}
} else {
// If the user is logged out, redirect them to the login page
throw redirect({
to: LoginRoute.to,
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.href,
},
})
}
if (!AuthContext.get().isLoggedIn) {
throw redirect({
to: LoginRoute.to,
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.href,
},
});
}
// Otherwise, return the user in context
return {
username: AuthContext.username,
}
return context;
},
})
function AuthenticatedLayout() {
const isLoggedIn = AuthContext.useSelector((s) => s.isLoggedIn);
if (!isLoggedIn) {
const redirect = (
location.pathname.length > 1
? { redirect: location.pathname }
: undefined
);
return <Navigate to="/login" search={redirect} />;
}
return (
<div className="flex flex-col min-h-screen">
<Header/>
@ -345,7 +332,6 @@ export const RootComponent = () => {
}
export const RootRoute = createRootRouteWithContext<{
auth: AuthCtx,
queryClient: QueryClient
}>()({
component: RootComponent,
@ -354,7 +340,7 @@ export const RootRoute = createRootRouteWithContext<{
const filterRouteTree = FiltersRoute.addChildren([FilterIndexRoute, FilterGetByIdRoute.addChildren([FilterGeneralRoute, FilterMoviesTvRoute, FilterMusicRoute, FilterAdvancedRoute, FilterExternalRoute, FilterActionsRoute])])
const settingsRouteTree = SettingsRoute.addChildren([SettingsIndexRoute, SettingsLogRoute, SettingsIndexersRoute, SettingsIrcRoute, SettingsFeedsRoute, SettingsClientsRoute, SettingsNotificationsRoute, SettingsApiRoute, SettingsReleasesRoute, SettingsAccountRoute])
const authenticatedTree = AuthRoute.addChildren([AuthIndexRoute.addChildren([DashboardRoute, filterRouteTree, ReleasesRoute.addChildren([ReleasesIndexRoute]), settingsRouteTree, LogsRoute])])
const authenticatedTree = AuthRoute.addChildren([AuthIndexRoute.addChildren([DashboardRoute, filterRouteTree, ReleasesRoute, settingsRouteTree, LogsRoute])])
const routeTree = RootRoute.addChildren([
authenticatedTree,
LoginRoute,
@ -368,9 +354,10 @@ export const Router = createRouter({
<RingResizeSpinner className="text-blue-500 size-24"/>
</div>
),
defaultErrorComponent: ({error}) => <ErrorComponent error={error}/>,
defaultErrorComponent: (ctx) => (
<ErrorPage error={ctx.error} reset={ctx.reset} />
),
context: {
auth: undefined!, // We'll inject this when we render
queryClient
},
});