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

@ -26,16 +26,16 @@ interface NavTabType {
}
const subNavigation: NavTabType[] = [
{ name: "Application", href: ".", icon: CogIcon, exact: true },
{ name: "Logs", href: "logs", icon: Square3Stack3DIcon },
{ name: "Indexers", href: "indexers", icon: KeyIcon },
{ name: "IRC", href: "irc", icon: ChatBubbleLeftRightIcon },
{ name: "Feeds", href: "feeds", icon: RssIcon },
{ name: "Clients", href: "clients", icon: FolderArrowDownIcon },
{ name: "Notifications", href: "notifications", icon: BellIcon },
{ name: "API keys", href: "api", icon: KeyIcon },
{ name: "Releases", href: "releases", icon: RectangleStackIcon },
{ name: "Account", href: "account", icon: UserCircleIcon }
{ name: "Application", href: "/settings", icon: CogIcon, exact: true },
{ name: "Logs", href: "/settings/logs", icon: Square3Stack3DIcon },
{ name: "Indexers", href: "/settings/indexers", icon: KeyIcon },
{ name: "IRC", href: "/settings/irc", icon: ChatBubbleLeftRightIcon },
{ name: "Feeds", href: "/settings/feeds", icon: RssIcon },
{ name: "Clients", href: "/settings/clients", icon: FolderArrowDownIcon },
{ name: "Notifications", href: "/settings/notifications", icon: BellIcon },
{ name: "API keys", href: "/settings/api", icon: KeyIcon },
{ name: "Releases", href: "/settings/releases", icon: RectangleStackIcon },
{ name: "Account", href: "/settings/account", icon: UserCircleIcon }
// {name: 'Regex Playground', href: 'regex-playground', icon: CogIcon, current: false}
// {name: 'Rules', href: 'rules', icon: ClipboardCheckIcon, current: false},
];

View file

@ -5,7 +5,7 @@
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQueryErrorResetBoundary } from "@tanstack/react-query";
import { useRouter, useSearch } from "@tanstack/react-router";
import toast from "react-hot-toast";
@ -18,15 +18,20 @@ import { PasswordInput, TextInput } from "@components/inputs/text";
import { LoginRoute } from "@app/routes";
import Logo from "@app/logo.svg?react";
import { AuthContext } from "@utils/Context";
// import { WarningAlert } from "@components/alerts";
type LoginFormFields = {
username: string;
password: string;
};
export const Login = () => {
export const Login = () => {
const [auth, setAuth] = AuthContext.use();
const queryErrorResetBoundary = useQueryErrorResetBoundary()
const router = useRouter()
const { auth } = LoginRoute.useRouteContext()
const search = useSearch({ from: LoginRoute.id })
const { handleSubmit, register, formState } = useForm<LoginFormFields>({
@ -35,14 +40,19 @@ export const Login = () => {
});
useEffect(() => {
queryErrorResetBoundary.reset()
// remove user session when visiting login page
auth.logout()
}, []);
AuthContext.reset();
}, [queryErrorResetBoundary]);
const loginMutation = useMutation({
mutationFn: (data: LoginFormFields) => APIClient.auth.login(data.username, data.password),
onSuccess: (_, variables: LoginFormFields) => {
auth.login(variables.username)
queryErrorResetBoundary.reset()
setAuth({
isLoggedIn: true,
username: variables.username
});
router.invalidate()
},
onError: (error) => {
@ -60,7 +70,7 @@ export const Login = () => {
} else if (auth.isLoggedIn) {
router.history.push("/")
}
}, [auth.isLoggedIn, search.redirect])
}, [auth.isLoggedIn, search.redirect]) // eslint-disable-line react-hooks/exhaustive-deps
return (
<div className="min-h-screen flex flex-col justify-center px-3">

View file

@ -43,7 +43,7 @@ export const Onboarding = () => {
const mutation = useMutation({
mutationFn: (data: InputValues) => APIClient.auth.onboard(data.username, data.password1),
onSuccess: () => navigate({ to: "/" })
onSuccess: () => navigate({ to: "/login" })
});
return (

View file

@ -33,12 +33,12 @@ interface tabType {
}
const tabs: tabType[] = [
{ name: "General", href: ".", exact: true },
{ name: "Movies and TV", href: "movies-tv" },
{ name: "Music", href: "music" },
{ name: "Advanced", href: "advanced" },
{ name: "External", href: "external" },
{ name: "Actions", href: "actions" }
{ name: "General", href: "/filters/$filterId", exact: true },
{ name: "Movies and TV", href: "/filters/$filterId/movies-tv" },
{ name: "Music", href: "/filters/$filterId/music" },
{ name: "Advanced", href: "/filters/$filterId/advanced" },
{ name: "External", href: "/filters/$filterId/external" },
{ name: "Actions", href: "/filters/$filterId/actions" }
];
export interface NavLinkProps {

View file

@ -16,7 +16,7 @@ import {
EyeSlashIcon
} from "@heroicons/react/24/solid";
import { ReleasesIndexRoute } from "@app/routes";
import { ReleasesRoute } from "@app/routes";
import { ReleasesListQueryOptions } from "@api/queries";
import { RandomLinuxIsos } from "@utils";
@ -94,7 +94,7 @@ const EmptyReleaseList = () => (
);
export const ReleaseTable = () => {
const search = ReleasesIndexRoute.useSearch()
const search = ReleasesRoute.useSearch()
const columns = React.useMemo(() => [
{

View file

@ -8,12 +8,11 @@ import { Form, Formik } from "formik";
import toast from "react-hot-toast";
import { UserIcon } from "@heroicons/react/24/solid";
import { SettingsAccountRoute } from "@app/routes";
import { AuthContext } from "@utils/Context";
import { APIClient } from "@api/APIClient";
import { Section } from "./_components";
import { PasswordField, TextField } from "@components/inputs";
import Toast from "@components/notifications/Toast";
import { AuthContext } from "@utils/Context";
const AccountSettings = () => (
<Section
@ -35,7 +34,7 @@ interface InputValues {
}
function Credentials() {
const ctx = SettingsAccountRoute.useRouteContext()
const username = AuthContext.useSelector((s) => s.username);
const validate = (values: InputValues) => {
const errors: Record<string, string> = {};
@ -52,7 +51,7 @@ function Credentials() {
const logoutMutation = useMutation({
mutationFn: APIClient.auth.logout,
onSuccess: () => {
AuthContext.logout();
AuthContext.reset();
toast.custom((t) => (
<Toast type="success" body="User updated successfully. Please sign in again!" t={t} />
@ -78,7 +77,7 @@ function Credentials() {
<div className="px-2 pb-6 bg-white dark:bg-gray-800">
<Formik
initialValues={{
username: ctx.auth.username!,
username: username,
newUsername: "",
oldPassword: "",
newPassword: "",