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,
|
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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user