feat(auth): implement full logout system and protect application routes

- Add LogoutProses function in app.tsx to destroy session via apiClient.logout() and clear user state
- Apply <AuthGuard> to private routes (/dashboard, /scan, /expert/reviews, etc.) to prevent unauthorized access
- Update the "Logout" button design in Navbar and MobileNav with a red accent and LogOut icon
- Fix vulnerability allowing direct URL access to the dashboard by bypassing login
This commit is contained in:
seriouselly
2026-06-08 00:20:00 +07:00
parent c1fc244c73
commit 56139388d3
3 changed files with 89 additions and 35 deletions
+71 -30
View File
@@ -5,7 +5,7 @@ import {
Navigate, Navigate,
} from "react-router-dom"; } from "react-router-dom";
import { AuthInitializer } from "@/components/auth-initializer"; import { AuthInitializer } from "@/components/auth-initializer";
// import { AuthGuard } from "@/components/auth-guard"; import { AuthGuard } from "@/components/auth-guard";
import { DashboardPage } from "@/pages/dashboard-page"; import { DashboardPage } from "@/pages/dashboard-page";
import { ScanPage } from "@/pages/scan-page"; import { ScanPage } from "@/pages/scan-page";
import { LibraryPage } from "@/pages/library-page"; import { LibraryPage } from "@/pages/library-page";
@@ -14,78 +14,119 @@ import { DiseaseDetailPage } from "@/pages/disease-detail-page";
import { DiagnosisDetailPage } from "@/pages/diagnosis-detail-page"; import { DiagnosisDetailPage } from "@/pages/diagnosis-detail-page";
import { ExpertReviewsPage } from "@/pages/expert-reviews-page"; import { ExpertReviewsPage } from "@/pages/expert-reviews-page";
import { DiagnosesPage } from "@/pages/diagnoses-page"; import { DiagnosesPage } from "@/pages/diagnoses-page";
// import { LoginPage } from "@/pages/login-page"; import { LoginPage } from "@/pages/login-page";
// import { RegisterPage } from "@/pages/register-page"; import { RegisterPage } from "@/pages/register-page";
import { MainLayout } from "@/components/layout/main-layout"; import { MainLayout } from "@/components/layout/main-layout";
import { useEffect } from "react";
import { useAuthStore } from "@/store/auth-store";
import { apiClient } from "@/lib/api-client";
function LogoutProses() {
const setUser = useAuthStore((state) => state.setUser);
useEffect(() => {
apiClient.logout().then(() => {
setUser(null);
}).catch((error) => {
console.error("Oops, gagal logout dari server:", error);
setUser(null);
});
}, [setUser]);
return <Navigate to="/login" replace />;
}
const queryClient = new QueryClient(); const queryClient = new QueryClient();
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: "/", element: <Navigate to="/dashboard" replace /> }, { path: "/", element: <Navigate to="/login" replace /> },
// { path: "/login", element: <LoginPage /> }, { path: "/login", element: <LoginPage /> },
// { path: "/register", element: <RegisterPage /> }, { path: "/register", element: <RegisterPage /> },
{ {
path: "/dashboard", path: "/dashboard",
element: ( element: (
<MainLayout> <AuthGuard>
<DashboardPage /> <MainLayout>
</MainLayout> <DashboardPage />
</MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/scan", path: "/scan",
element: ( element: (
<MainLayout> <AuthGuard>
<ScanPage /> <MainLayout>
</MainLayout> <ScanPage />
</MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/library", path: "/library",
element: ( element: (
<MainLayout> <AuthGuard>
<LibraryPage /> <MainLayout>
</MainLayout> <LibraryPage />
</MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/diagnoses", path: "/diagnoses",
element: ( element: (
<MainLayout> <AuthGuard>
<DiagnosesPage /> <MainLayout>
</MainLayout> <DiagnosesPage />
</MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/diagnoses/:id", path: "/diagnoses/:id",
element: ( element: (
<MainLayout> <AuthGuard>
<DiagnosisDetailPage /> <MainLayout>
</MainLayout> <DiagnosisDetailPage />
</MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/expert/reviews", path: "/expert/reviews",
element: ( element: (
<MainLayout> <AuthGuard requireExpert={true}>
<ExpertReviewsPage /> <MainLayout>
</MainLayout> <ExpertReviewsPage />
</MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/catalog", path: "/catalog",
element: ( element: (
<MainLayout> <AuthGuard>
<CatalogPage /> <MainLayout>
</MainLayout> <CatalogPage />
</MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/catalog/:slug", path: "/catalog/:slug",
element: ( element: (
<MainLayout> <AuthGuard>
<DiseaseDetailPage /> <MainLayout>
</MainLayout> <DiseaseDetailPage />
</MainLayout>
</AuthGuard>
),
},
{
path: "/logout",
element: (
<LogoutProses />
), ),
}, },
]); ]);
@@ -1,6 +1,6 @@
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { X, Leaf } from "lucide-react"; import { X, Leaf, LogOut } from "lucide-react";
type Props = { type Props = {
open: boolean; open: boolean;
@@ -22,7 +22,6 @@ export function MobileNav({ open, onClose }: Props) {
const navContent = ( const navContent = (
<div <div
// 2. Ubah z-50 menjadi z-[999] agar levelnya mentok paling atas
className={`fixed inset-0 z-[999] lg:hidden transition-opacity duration-300 ease-in-out ${ className={`fixed inset-0 z-[999] lg:hidden transition-opacity duration-300 ease-in-out ${
open ? "opacity-100" : "opacity-0 pointer-events-none" open ? "opacity-100" : "opacity-0 pointer-events-none"
}`} }`}
@@ -84,6 +83,14 @@ export function MobileNav({ open, onClose }: Props) {
</Link> </Link>
); );
})} })}
<Link
to="/logout"
onClick={onClose}
className="inline-flex items-center rounded-full bg-[#ECF4E8] border border-white/50 px-4 py-2 text-[16px] font-medium text-black transition-colors hover:bg-white/50"
>
<span>Keluar</span>
<LogOut className="ml-64 h-4 w-4" />
</Link>
</nav> </nav>
</div> </div>
</div> </div>
+9 -3
View File
@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { Leaf, Menu } from "lucide-react"; import { Leaf, LogOut, Menu } from "lucide-react";
import { MobileNav } from "./mobile-nav"; import { MobileNav } from "./mobile-nav";
export function Navbar() { export function Navbar() {
@@ -43,7 +43,7 @@ export function Navbar() {
Dashboard Dashboard
</Link> </Link>
<Link to="/scan" className={navLinkClassName(isActive("/scan"))}> <Link to="/scan" className={navLinkClassName(isActive("/scan"))}>
Scan Tanaman Scan Daun
</Link> </Link>
<Link <Link
to="/diagnoses" to="/diagnoses"
@@ -63,7 +63,13 @@ export function Navbar() {
to="/expert/reviews" to="/expert/reviews"
className={navLinkClassName(isActive("/expert/reviews"))} className={navLinkClassName(isActive("/expert/reviews"))}
> >
Review Hasil Pakar
</Link>
<Link
to="/logout"
className="ml-4 inline-flex items-center rounded-full bg-[#ECF4E8] border border-white/50 px-4 py-2 text-[16px] font-medium text-black transition-colors hover:bg-white/50"
>
Keluar <LogOut className="ml-2 h-4 w-4" />
</Link> </Link>
</nav> </nav>