Consolidate authentication code

This commit is contained in:
april
2024-01-03 10:01:15 -06:00
parent 73b11482ff
commit 912e4d1b0e
12 changed files with 206 additions and 114 deletions

View File

@@ -1,3 +1,4 @@
import { useNavigate } from "@remix-run/react";
import axios from "axios";
export const client = axios.create({
@@ -13,15 +14,9 @@ client.interceptors.request.use(
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
client.interceptors.request.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
const navigate = useNavigate();
if (error.response.status == 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = localStorage.getItem("refresh-token");
@@ -36,7 +31,10 @@ client.interceptors.request.use(
] = `Bearer ${data.refreshToken}`;
return client(originalRequest);
} catch (_error) {
return Promise.reject(_error);
localStorage.removeItem("token");
localStorage.removeItem("refresh-token");
console.log("Oh no!!!");
navigate("/login");
}
}
}

102
web/app/util/auth.tsx Normal file
View File

@@ -0,0 +1,102 @@
import { client } from "./api";
import { useNavigate } from "@remix-run/react";
import { createContext, useContext, useEffect, useState } from "react";
interface AuthContextValues {
user: string | null;
loading: boolean;
signin: ({
username,
password,
}: {
username: string;
password: string;
}) => void;
signout: () => void;
}
const AuthContext = createContext<AuthContextValues | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const auth = useProvideAuth();
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextValues {
const data = useContext(AuthContext);
if (!data) {
throw new Error("Could not find AuthContext provider");
}
return data;
}
function useProvideAuth() {
const [user, setUser] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const navigate = useNavigate();
const handleUser = (rawUser: string | null) => {
if (rawUser) {
setUser(rawUser);
setLoading(false);
return rawUser;
} else {
setUser(null);
clearTokens();
setLoading(false);
return false;
}
};
const handleTokens = (tokens: {
access_token: string;
refresh_token: string;
}) => {
if (tokens) {
localStorage.setItem("token", tokens.access_token);
localStorage.setItem("refresh-token", tokens.refresh_token);
}
};
const clearTokens = () => {
localStorage.removeItem("token");
localStorage.removeItem("refresh-token");
};
const signin = async (values: { username: string; password: string }) => {
setLoading(true);
await client
.postForm("/auth/login", values)
.then((response) => handleTokens(response.data))
.catch(() => {
setLoading(false);
throw new Error("Invalid username or password");
});
setLoading(false);
await client
.get("/users/me")
.then((response) => handleUser(response.data.username))
.catch(() => handleUser(null));
navigate("/logbook");
};
const signout = async () => {
return await client.post("/auth/logout").then(() => handleUser(null));
};
useEffect(() => {
client
.get("/users/me")
.then((response) => handleUser(response.data.username))
.catch(() => handleUser(null));
}, []);
return {
user,
loading,
signin,
signout,
};
}

View File

@@ -1,52 +0,0 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { client } from "./api";
import { useNavigate } from "@remix-run/react";
type User = {
username: string;
level: number;
};
export function useMe() {
return useQuery<User, Error>({
queryKey: ["me"],
queryFn: () => client.get(`/users/me`).then((res) => res.data),
});
}
export function useSignOut() {
const queryClient = useQueryClient();
const navigate = useNavigate();
const onSignOut = async () => {
queryClient.setQueryData(["user"], null);
const res = await client.post("/auth/logout");
if (res.status == 200) {
navigate("/login");
} else {
console.error("Failed to log out");
}
};
return onSignOut;
}
export function useLogin() {
const navigate = useNavigate();
const { mutate: signInMutation } = useMutation({
mutationFn: async (values) => {
return await client.postForm("/auth/login", values);
},
onSuccess: (data) => {
localStorage.setItem("token", data.data.access_token);
localStorage.setItem("refresh-token", data.data.refresh_token);
navigate("/logbook");
},
onError: (error) => {
console.error(error);
},
});
return signInMutation;
}

52
web/app/util/types.ts Normal file
View File

@@ -0,0 +1,52 @@
type Flight = {
id: string;
user: string;
date: string;
aircraft: string | null;
waypoint_from: string | null;
waypoint_to: string | null;
route: string | null;
hobbs_start: number | null;
hobbs_end: number | null;
tach_start: number | null;
tach_end: number | null;
time_start: number | null;
time_off: number | null;
time_down: number | null;
time_stop: number | null;
time_total: number;
time_pic: number;
time_sic: number;
time_night: number;
time_solo: number;
time_xc: number;
dist_xc: number;
takeoffs_day: number;
landings_day: number;
takeoffs_night: number;
landings_night: number;
time_instrument: number;
time_sim_instrument: number;
holds_instrument: number;
dual_given: number;
dual_recvd: number;
time_sim: number;
time_ground: number;
tags: string[];
pax: string[];
crew: string[];
comments: string;
};
export { type Flight };