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:
+71
-30
@@ -5,7 +5,7 @@ import {
|
||||
Navigate,
|
||||
} from "react-router-dom";
|
||||
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 { ScanPage } from "@/pages/scan-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 { ExpertReviewsPage } from "@/pages/expert-reviews-page";
|
||||
import { DiagnosesPage } from "@/pages/diagnoses-page";
|
||||
// import { LoginPage } from "@/pages/login-page";
|
||||
// import { RegisterPage } from "@/pages/register-page";
|
||||
import { LoginPage } from "@/pages/login-page";
|
||||
import { RegisterPage } from "@/pages/register-page";
|
||||
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 router = createBrowserRouter([
|
||||
{ path: "/", element: <Navigate to="/dashboard" replace /> },
|
||||
// { path: "/login", element: <LoginPage /> },
|
||||
// { path: "/register", element: <RegisterPage /> },
|
||||
{ path: "/", element: <Navigate to="/login" replace /> },
|
||||
{ path: "/login", element: <LoginPage /> },
|
||||
{ path: "/register", element: <RegisterPage /> },
|
||||
{
|
||||
path: "/dashboard",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<DashboardPage />
|
||||
</MainLayout>
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<DashboardPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/scan",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<ScanPage />
|
||||
</MainLayout>
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<ScanPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/library",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<LibraryPage />
|
||||
</MainLayout>
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<LibraryPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/diagnoses",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<DiagnosesPage />
|
||||
</MainLayout>
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<DiagnosesPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/diagnoses/:id",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<DiagnosisDetailPage />
|
||||
</MainLayout>
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<DiagnosisDetailPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/expert/reviews",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<ExpertReviewsPage />
|
||||
</MainLayout>
|
||||
<AuthGuard requireExpert={true}>
|
||||
<MainLayout>
|
||||
<ExpertReviewsPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/catalog",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<CatalogPage />
|
||||
</MainLayout>
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<CatalogPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/catalog/:slug",
|
||||
element: (
|
||||
<MainLayout>
|
||||
<DiseaseDetailPage />
|
||||
</MainLayout>
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<DiseaseDetailPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/logout",
|
||||
element: (
|
||||
<LogoutProses />
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createPortal } from "react-dom";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { X, Leaf } from "lucide-react";
|
||||
import { X, Leaf, LogOut } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
@@ -22,7 +22,6 @@ export function MobileNav({ open, onClose }: Props) {
|
||||
|
||||
const navContent = (
|
||||
<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 ${
|
||||
open ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||
}`}
|
||||
@@ -84,6 +83,14 @@ export function MobileNav({ open, onClose }: Props) {
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
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";
|
||||
|
||||
export function Navbar() {
|
||||
@@ -43,7 +43,7 @@ export function Navbar() {
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link to="/scan" className={navLinkClassName(isActive("/scan"))}>
|
||||
Scan Tanaman
|
||||
Scan Daun
|
||||
</Link>
|
||||
<Link
|
||||
to="/diagnoses"
|
||||
@@ -63,7 +63,13 @@ export function Navbar() {
|
||||
to="/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>
|
||||
</nav>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user