Redirect on token expiry

This commit is contained in:
april 2024-01-04 13:36:21 -06:00
parent 9de454d491
commit 4e84dc842a
10 changed files with 116 additions and 34 deletions

View File

@ -12,10 +12,16 @@ import {
Scripts, Scripts,
ScrollRestoration, ScrollRestoration,
isRouteErrorResponse, isRouteErrorResponse,
redirect,
useNavigate,
useRouteError, useRouteError,
} from "@remix-run/react"; } from "@remix-run/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import {
QueryCache,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { import {

View File

@ -1,7 +1,10 @@
import { client } from "@/util/api"; import { client } from "@/util/api";
import { useAuth } from "@/util/auth";
import { Center, Container, List, Loader, Stack, Text } from "@mantine/core"; import { Center, Container, List, Loader, Stack, Text } from "@mantine/core";
import { useParams } from "@remix-run/react"; import { useNavigate, useParams } from "@remix-run/react";
import { IconAlertTriangle } from "@tabler/icons-react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useEffect } from "react";
export default function Flight() { export default function Flight() {
const params = useParams(); const params = useParams();
@ -10,8 +13,21 @@ export default function Flight() {
queryKey: [params.id], queryKey: [params.id],
queryFn: async () => queryFn: async () =>
await client.get(`/flights/${params.id}`).then((res) => res.data), await client.get(`/flights/${params.id}`).then((res) => res.data),
retry: (failureCount, error) => {
return !error || error.response?.status !== 401;
},
}); });
const navigate = useNavigate();
const { clearUser } = useAuth();
useEffect(() => {
if (flight.isError && flight.error.response.status === 401) {
clearUser();
navigate("/login");
}
}, [flight]);
return ( return (
<Container> <Container>
<Stack h="calc(100vh - 95px)"> <Stack h="calc(100vh - 95px)">

View File

@ -5,7 +5,6 @@ import {
Fieldset, Fieldset,
Group, Group,
NumberInput, NumberInput,
ScrollArea,
ScrollAreaAutosize, ScrollAreaAutosize,
Stack, Stack,
TextInput, TextInput,
@ -86,6 +85,9 @@ export default function NewFlight() {
console.log(res); console.log(res);
return res.data; return res.data;
}, },
retry: (failureCount, error) => {
return !error || error.response?.status !== 401;
},
onError: (error) => { onError: (error) => {
console.log(error); console.log(error);
}, },

View File

@ -1,4 +1,5 @@
import { client } from "@/util/api"; import { client } from "@/util/api";
import { useAuth } from "@/util/auth";
import { FlightConciseSchema } from "@/util/types"; import { FlightConciseSchema } from "@/util/types";
import { import {
NavLink, NavLink,
@ -8,22 +9,38 @@ import {
Stack, Stack,
Loader, Loader,
Center, Center,
Container,
} from "@mantine/core"; } from "@mantine/core";
import { Link, useLocation, useNavigate } from "@remix-run/react"; import { Link, useLocation, useNavigate } from "@remix-run/react";
import { IconPlus } from "@tabler/icons-react"; import { IconPlus } from "@tabler/icons-react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useEffect } from "react";
export function FlightsList() { export function FlightsList() {
const flights = useQuery({ // const flights = useQuery({
queryKey: ["flights-list"], // queryKey: ["flights-list"],
queryFn: () => client.get(`/flights`).then((res) => res.data), // queryFn: () => client.get(`/flights`).then((res) => res.data),
}); // });
const location = useLocation(); const location = useLocation();
const page = location.pathname.split("/")[3]; const page = location.pathname.split("/")[3];
const flights = useQuery({
queryKey: ["flights-list"],
queryFn: async () => await client.get(`/flights`).then((res) => res.data),
retry: (failureCount, error) => {
return !error || error.response?.status !== 401;
},
});
const navigate = useNavigate(); const navigate = useNavigate();
const { clearUser } = useAuth();
useEffect(() => {
if (flights.isError && flights.error.response.status === 401) {
clearUser();
navigate("/login");
}
}, [flights]);
return ( return (
<Stack p="0" m="0" gap="0"> <Stack p="0" m="0" gap="0">

View File

@ -1,12 +1,33 @@
import { client } from "@/util/api";
import { useAuth } from "@/util/auth"; import { useAuth } from "@/util/auth";
import { Container, Title } from "@mantine/core"; import { Container, Text, Title } from "@mantine/core";
import { useNavigate } from "@remix-run/react";
import { useQuery } from "@tanstack/react-query";
import { useEffect } from "react";
export default function Me() { export default function Me() {
const { user } = useAuth(); const user = useQuery({
queryKey: ["user"],
queryFn: async () => await client.get(`users/me`).then((res) => res.data),
retry: (failureCount, error) => {
return !error || error.response?.status !== 401;
},
});
const { clearUser } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (user.isError && user.error.response?.status === 401) {
clearUser();
navigate("/login");
}
}, [user]);
return ( return (
<Container> <Container>
<Title order={2}>{user}</Title> <Title order={2}>{user.data.username}</Title>
<Text>Level {user.data.level}</Text>
</Container> </Container>
); );
} }

View File

@ -12,6 +12,7 @@ import {
Title, Title,
} from "@mantine/core"; } from "@mantine/core";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { useEffect } from "react";
export default function Login() { export default function Login() {
const form = useForm({ const form = useForm({
@ -23,6 +24,10 @@ export default function Login() {
const { signin } = useAuth(); const { signin } = useAuth();
useEffect(() => {
document.title = "Log In - Tailfin";
});
return ( return (
<Container h="75%"> <Container h="75%">
<Stack gap="md" h="100%" justify="center" align="stretch"> <Stack gap="md" h="100%" justify="center" align="stretch">
@ -50,7 +55,9 @@ export default function Login() {
mt="md" mt="md"
/> />
<Group justify="center" mt="xl"> <Group justify="center" mt="xl">
<Button type="submit">Log In</Button> <Button type="submit" fullWidth>
Log In
</Button>
</Group> </Group>
</form> </form>
</Fieldset> </Fieldset>

View File

@ -1,5 +1,5 @@
import { useNavigate } from "@remix-run/react";
import axios from "axios"; import axios from "axios";
import { useAuth } from "./auth";
export const client = axios.create({ export const client = axios.create({
baseURL: "http://localhost:8081", baseURL: "http://localhost:8081",
@ -15,27 +15,21 @@ client.interceptors.request.use(
return config; return config;
}, },
async (error) => { async (error) => {
const originalRequest = error.config; const { clearUser } = useAuth();
const navigate = useNavigate(); console.log(error.response);
if (error.response.status == 401 && !originalRequest._retry) { if (error.response && error.response.status === 401) {
originalRequest._retry = true; try {
const refreshToken = localStorage.getItem("refresh-token"); const refreshToken = localStorage.getItem("refresh-token");
if (refreshToken) { const response = await client.post("/auth/refresh", { refreshToken });
try { const newAccessToken = response.data.access_token;
const { data } = await client.post("/auth/refresh", {
refresh: refreshToken, localStorage.setItem("token", newAccessToken);
}); error.config.headers.Authorization = `Bearer ${newAccessToken}`;
localStorage.setItem("token", data.refreshToken); return client(error.config);
client.defaults.headers.common[ } catch (err) {
"Authorization" console.log("ERRORRRRRRRRRRRRRR");
] = `Bearer ${data.refreshToken}`; clearUser();
return client(originalRequest); window.location.href = "/login";
} catch (_error) {
localStorage.removeItem("token");
localStorage.removeItem("refresh-token");
console.log("Oh no!!!");
navigate("/login");
}
} }
} }
return Promise.reject(error); return Promise.reject(error);

View File

@ -13,6 +13,7 @@ interface AuthContextValues {
password: string; password: string;
}) => void; }) => void;
signout: () => void; signout: () => void;
clearUser: () => void;
} }
const AuthContext = createContext<AuthContextValues | null>(null); const AuthContext = createContext<AuthContextValues | null>(null);
@ -86,6 +87,10 @@ function useProvideAuth() {
return await client.post("/auth/logout").then(() => handleUser(null)); return await client.post("/auth/logout").then(() => handleUser(null));
}; };
const clearUser = () => {
handleUser(null);
};
useEffect(() => { useEffect(() => {
client client
.get("/users/me") .get("/users/me")
@ -99,5 +104,6 @@ function useProvideAuth() {
loading, loading,
signin, signin,
signout, signout,
clearUser,
}; };
} }

12
web/package-lock.json generated
View File

@ -10,6 +10,7 @@
"@mantine/dropzone": "^7.4.0", "@mantine/dropzone": "^7.4.0",
"@mantine/form": "^7.4.0", "@mantine/form": "^7.4.0",
"@mantine/hooks": "^7.4.0", "@mantine/hooks": "^7.4.0",
"@mantine/modals": "^7.4.0",
"@mantine/notifications": "^7.4.0", "@mantine/notifications": "^7.4.0",
"@remix-run/css-bundle": "^2.4.1", "@remix-run/css-bundle": "^2.4.1",
"@remix-run/node": "^2.4.1", "@remix-run/node": "^2.4.1",
@ -1493,6 +1494,17 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/@mantine/modals": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-7.4.0.tgz",
"integrity": "sha512-uXZuN5vCx0Wdu0gOmoDaGD8/GVpx7qCeyAAFCH94WPHl/aK3fzKSk4K63deWY5Ml9a5ktic/i5pYil3MUBEj5w==",
"peerDependencies": {
"@mantine/core": "7.4.0",
"@mantine/hooks": "7.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
},
"node_modules/@mantine/notifications": { "node_modules/@mantine/notifications": {
"version": "7.4.0", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.4.0.tgz",

View File

@ -16,6 +16,7 @@
"@mantine/dropzone": "^7.4.0", "@mantine/dropzone": "^7.4.0",
"@mantine/form": "^7.4.0", "@mantine/form": "^7.4.0",
"@mantine/hooks": "^7.4.0", "@mantine/hooks": "^7.4.0",
"@mantine/modals": "^7.4.0",
"@mantine/notifications": "^7.4.0", "@mantine/notifications": "^7.4.0",
"@remix-run/css-bundle": "^2.4.1", "@remix-run/css-bundle": "^2.4.1",
"@remix-run/node": "^2.4.1", "@remix-run/node": "^2.4.1",