Improve auth system

This commit is contained in:
sianida26 2024-05-08 01:27:56 +07:00
parent c5024d7b69
commit 3aaec5a323
11 changed files with 138 additions and 57 deletions

View File

@ -30,26 +30,19 @@ const authRoutes = new Hono<HonoEnv>()
async (c) => {
const formData = c.req.valid("form");
const user = (
await db
.select({
id: users.id,
username: users.username,
email: users.email,
password: users.password,
})
.from(users)
.where(
and(
isNull(users.deletedAt),
eq(users.isEnabled, true),
or(
eq(users.username, formData.username),
eq(users.email, formData.username)
)
const [user] = await db
.select()
.from(users)
.where(
and(
isNull(users.deletedAt),
eq(users.isEnabled, true),
or(
eq(users.username, formData.username),
eq(users.email, formData.username)
)
)
)[0];
);
if (!user) {
throw new HTTPException(400, {
@ -99,6 +92,11 @@ const authRoutes = new Hono<HonoEnv>()
return c.json({
accessToken,
refreshToken,
user: {
id: user.id,
name: user.name,
permissions: [] as string[],
},
});
}
)

View File

@ -8,6 +8,7 @@ import { routeTree } from "./routeTree.gen";
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import { AuthProvider } from "./contexts/AuthContext";
const queryClient = new QueryClient();
@ -28,7 +29,9 @@ function App() {
<MantineProvider>
<Notifications />
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
</QueryClientProvider>
</MantineProvider>
);

View File

@ -13,6 +13,8 @@ import logo from "@/assets/logos/logo.png";
import cx from "clsx";
import classNames from "./styles/appHeader.module.css";
import { TbChevronDown } from "react-icons/tb";
import { Link } from "@tanstack/react-router";
import useAuth from "@/hooks/useAuth";
// import getUserMenus from "../actions/getUserMenus";
// import { useAuth } from "@/modules/auth/contexts/AuthContext";
// import UserMenuItem from "./UserMenuItem";
@ -31,7 +33,7 @@ interface Props {
export default function AppHeader(props: Props) {
const [userMenuOpened, setUserMenuOpened] = useState(false);
// const { user } = useAuth();
const { user } = useAuth();
// const userMenus = getUserMenus().map((item, i) => (
// <UserMenuItem item={item} key={i} />
@ -66,11 +68,11 @@ export default function AppHeader(props: Props) {
// src={user?.photoProfile}
// alt={user?.name}
radius="xl"
size={20}
size={30}
/>
<Text fw={500} size="sm" lh={1} mr={3}>
{/* {user?.name} */}
Username
{user?.name ?? "Anonymous"}
</Text>
<TbChevronDown
style={{ width: rem(12), height: rem(12) }}
@ -81,7 +83,9 @@ export default function AppHeader(props: Props) {
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Settings</Menu.Label>
<Menu.Item component={Link} to="/logout">
Logout
</Menu.Item>
{/* {userMenus} */}
</Menu.Dropdown>

View File

@ -0,0 +1,67 @@
import { ReactNode } from "@tanstack/react-router";
import { createContext, useState } from "react";
interface AuthContextType {
user: {
id: string;
name: string;
permissions: string[];
} | null;
accessToken: string | null;
saveAuthData: (
userData: NonNullable<AuthContextType["user"]>,
accessToken: NonNullable<AuthContextType["accessToken"]>
) => void;
clearAuthData: () => void;
isAuthenticated: boolean;
}
export const AuthContext = createContext<AuthContextType | undefined>(
undefined
);
export function AuthProvider({ children }: { children: ReactNode }) {
const [userId, setUserId] = useState<string | null>(null);
const [userName, setUserName] = useState<string | null>(null);
const [permissions, setPermissions] = useState<string[] | null>(null);
const [accessToken, setAccessToken] = useState<string | null>(
localStorage.getItem("accessToken")
);
const saveAuthData = (
userData: NonNullable<AuthContextType["user"]>,
accessToken: NonNullable<AuthContextType["accessToken"]>
) => {
setUserId(userData.id);
setUserName(userData.name);
setPermissions(userData.permissions);
setAccessToken(accessToken);
localStorage.setItem("accessToken", accessToken);
};
const clearAuthData = () => {
setUserId(null);
setUserName(null);
setPermissions(null);
setAccessToken(null);
localStorage.removeItem("accessToken");
};
const isAuthenticated = Boolean(accessToken);
return (
<AuthContext.Provider
value={{
user: userId
? { id: userId, name: userName!, permissions: permissions! }
: null,
accessToken,
saveAuthData,
clearAuthData,
isAuthenticated,
}}
>
{children}
</AuthContext.Provider>
);
}

View File

@ -3,12 +3,14 @@ import { AppType } from "backend";
const backendUrl = import.meta.env.VITE_BACKEND_BASE_URL as string | undefined;
console.log(backendUrl);
if (!backendUrl) throw new Error("Backend URL not set");
const client = hc<AppType>(backendUrl, {
headers: {
headers: () => ({
Authorization: `Bearer ${localStorage.getItem("accessToken")}`,
},
}),
});
export default client;

View File

@ -0,0 +1,14 @@
import { AuthContext } from "@/contexts/AuthContext";
import { useContext } from "react";
const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
};
export default useAuth;

View File

@ -1,10 +1,9 @@
import { AppShell } from "@mantine/core";
import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router";
import { Navigate, Outlet, createFileRoute } from "@tanstack/react-router";
import { useDisclosure } from "@mantine/hooks";
import AppHeader from "../components/AppHeader";
import AppNavbar from "../components/AppNavbar";
import isAuthenticated from "@/utils/isAuthenticated";
import { useEffect } from "react";
import useAuth from "@/hooks/useAuth";
export const Route = createFileRoute("/_dashboardLayout")({
component: DashboardLayout,
@ -19,17 +18,11 @@ export const Route = createFileRoute("/_dashboardLayout")({
});
function DashboardLayout() {
const { isAuthenticated } = useAuth();
const [openNavbar, { toggle }] = useDisclosure(false);
const navigate = useNavigate();
useEffect(() => {
if (!isAuthenticated()) {
navigate({ to: "/login", replace: true });
}
}, [navigate]);
return (
return isAuthenticated ? (
<AppShell
padding="md"
header={{ height: 70 }}
@ -50,5 +43,7 @@ function DashboardLayout() {
<Outlet />
</AppShell.Main>
</AppShell>
) : (
<Navigate to="/login" />
);
}

View File

@ -15,7 +15,7 @@ import { useForm } from "@mantine/form";
import { z } from "zod";
import { zodResolver } from "mantine-form-zod-resolver";
import { useEffect, useState } from "react";
import isAuthenticated from "@/utils/isAuthenticated";
import useAuth from "@/hooks/useAuth";
export const Route = createFileRoute("/login/")({
component: LoginPage,
@ -35,6 +35,8 @@ export default function LoginPage() {
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const { isAuthenticated, saveAuthData } = useAuth();
const form = useForm<FormSchema>({
initialValues: {
username: "",
@ -44,13 +46,13 @@ export default function LoginPage() {
});
useEffect(() => {
if (isAuthenticated()) {
if (isAuthenticated) {
navigate({
to: "/dashboard",
replace: true,
});
}
}, [navigate]);
}, [navigate, isAuthenticated]);
const loginMutation = useMutation({
mutationFn: async (values: FormSchema) => {
@ -66,13 +68,14 @@ export default function LoginPage() {
},
onSuccess: (data) => {
console.log(data);
localStorage.setItem("accessToken", data.accessToken);
navigate({
to: "/dashboard",
});
saveAuthData(
{
id: data.user.id,
name: data.user.name,
permissions: data.user.permissions,
},
data.accessToken
);
},
onError: async (error) => {

View File

@ -1,4 +1,4 @@
import isAuthenticated from "@/utils/isAuthenticated";
import useAuth from "@/hooks/useAuth";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useEffect } from "react";
@ -7,18 +7,19 @@ export const Route = createFileRoute("/logout/")({
});
export default function LogoutPage() {
const { isAuthenticated, clearAuthData } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (isAuthenticated()) {
localStorage.removeItem("accessToken");
if (isAuthenticated) {
clearAuthData();
}
navigate({
to: "/login",
replace: true,
});
}, [navigate]);
}, [navigate, isAuthenticated, clearAuthData]);
return <div>Logging out...</div>;
}

View File

@ -1,6 +0,0 @@
function isAuthenticated(): boolean {
const accessToken = localStorage.getItem("accessToken");
return accessToken !== null;
}
export default isAuthenticated;