Skip to content

Commit 4c4dacf

Browse files
committed
feat: add validation check role on protected route
1 parent b34247b commit 4c4dacf

3 files changed

Lines changed: 37 additions & 30 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
},
1717
"husky": {
1818
"hooks": {
19-
"pre-commit": "yarn format && git add -A . --allow-empty"
19+
"pre-commit": "pnpm format && git add -A . --allow-empty"
2020
}
2121
},
2222
"dependencies": {

src/app/[locale]/admin/layout.tsx

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
1-
import { cookies } from "next/headers";
21
import RouteBreadcrumb from "@/components/common/RouteBreadcrumb";
32
import AdminSidebar from "@/components/layout/AdminSidebar";
43

5-
import { jwtDecode } from "jwt-decode";
6-
import { redirect } from "next/navigation";
7-
import { AuthJwtPayload } from "@/types";
84
import { Separator } from "@/components/ui/Separator";
95
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/Sidebar";
6+
import ProtectedRoute from "@/components/layout/ProtectedRoute";
107

118
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
12-
const cookieStore = await cookies();
13-
const token = cookieStore.get("token");
14-
15-
if (!token) return redirect("/");
16-
17-
const payload = jwtDecode<AuthJwtPayload>(token.value);
18-
if (payload.role !== "admin") return redirect("/");
19-
209
return (
2110
<SidebarProvider>
22-
<AdminSidebar />
23-
<SidebarInset>
24-
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
25-
<div className="flex items-center gap-2 px-4">
26-
<SidebarTrigger className="-ml-1" />
27-
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
28-
<RouteBreadcrumb />
29-
</div>
30-
</header>
31-
<main className="p-4">{children}</main>
32-
</SidebarInset>
11+
<ProtectedRoute requiredRole="admin">
12+
<AdminSidebar />
13+
<SidebarInset>
14+
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
15+
<div className="flex items-center gap-2 px-4">
16+
<SidebarTrigger className="-ml-1" />
17+
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
18+
<RouteBreadcrumb />
19+
</div>
20+
</header>
21+
<main className="p-4">{children}</main>
22+
</SidebarInset>
23+
</ProtectedRoute>
3324
</SidebarProvider>
3425
);
3526
}

src/components/layout/ProtectedRoute/index.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,38 @@ import { useEffect } from "react";
66

77
interface ProtectedRouteProps {
88
children: React.ReactNode;
9+
requiredRole?: string;
910
}
1011

1112
/**
12-
* Protects a route by checking if the user is authenticated
13+
* Protects a route by checking if the user is authenticated and has required role
1314
*/
14-
export default function ProtectedRoute({ children }: ProtectedRouteProps) {
15-
const { isAuthenticated } = useAuthUser();
15+
export default function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) {
16+
const { isAuthenticated, user } = useAuthUser();
1617
const router = useRouter();
1718

19+
// Check if user has required role (if no role required, always true)
20+
const hasRequiredRole = !requiredRole || user?.role === requiredRole;
21+
// User can access if authenticated and has required role
22+
const canAccess = isAuthenticated && hasRequiredRole;
23+
24+
const redirectSignin = () => {
25+
router.replace("/sign-in");
26+
};
27+
1828
useEffect(() => {
1929
if (!isAuthenticated) {
20-
router.replace("/sign-in");
30+
redirectSignin();
31+
return;
32+
}
33+
34+
if (!hasRequiredRole) {
35+
router.replace("/");
36+
return;
2137
}
22-
}, [isAuthenticated]);
38+
}, [isAuthenticated, hasRequiredRole]);
2339

24-
if (!isAuthenticated) {
40+
if (!canAccess) {
2541
return null;
2642
}
2743

0 commit comments

Comments
 (0)