CognxSafeTrack commited on
Commit
803f4c5
·
1 Parent(s): 1ef1cda

feat: implement RoleGuard for centralized authorization

Browse files

- Created reusable RoleGuard component
- Refactored MainLayout to use RoleGuard for admin features
- Reduced manual role checks in the UI layout

apps/admin/src/components/RoleGuard.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { useAuth } from '@/lib/auth';
3
+
4
+ type Role = 'STUDENT' | 'ADMIN' | 'ORG_MEMBER' | 'ORG_ADMIN' | 'SUPER_ADMIN';
5
+
6
+ interface RoleGuardProps {
7
+ children: React.ReactNode;
8
+ allowedRoles?: Role[];
9
+ requireSuperAdmin?: boolean;
10
+ fallback?: React.ReactNode;
11
+ }
12
+
13
+ /**
14
+ * RoleGuard component to conditionally render content based on user roles.
15
+ *
16
+ * Usage:
17
+ * <RoleGuard allowedRoles={['SUPER_ADMIN', 'ADMIN']}>
18
+ * <SecretComponent />
19
+ * </RoleGuard>
20
+ */
21
+ export const RoleGuard: React.FC<RoleGuardProps> = ({
22
+ children,
23
+ allowedRoles,
24
+ requireSuperAdmin = false,
25
+ fallback = null
26
+ }) => {
27
+ const { user } = useAuth();
28
+
29
+ if (!user) return <>{fallback}</>;
30
+
31
+ const userRole = user.role as Role;
32
+
33
+ // Special case for Super Admin logic
34
+ if (requireSuperAdmin) {
35
+ if (userRole === 'SUPER_ADMIN' || userRole === 'ADMIN') {
36
+ return <>{children}</>;
37
+ }
38
+ return <>{fallback}</>;
39
+ }
40
+
41
+ // Default role check
42
+ if (allowedRoles && allowedRoles.length > 0) {
43
+ if (allowedRoles.includes(userRole)) {
44
+ return <>{children}</>;
45
+ }
46
+ return <>{fallback}</>;
47
+ }
48
+
49
+ return <>{children}</>;
50
+ };
51
+
52
+ export default RoleGuard;
apps/admin/src/components/layouts/MainLayout.tsx CHANGED
@@ -4,6 +4,8 @@ import { useAuth } from '@/lib/auth';
4
  import { useTenant } from '@/lib/tenant';
5
  import { BarChart2, TrendingUp, Users, BookOpen, Mic, Building2, Activity, Lightbulb } from 'lucide-react';
6
 
 
 
7
  interface MainLayoutProps {
8
  children: React.ReactNode;
9
  isSuperAdmin: boolean;
@@ -46,7 +48,7 @@ export default function MainLayout({ children, isSuperAdmin, orgs }: MainLayoutP
46
  <span className="truncate">{currentOrg?.name || 'EdTech Admin'}</span>
47
  </div>
48
 
49
- {isSuperAdmin && (
50
  <div className="mb-8">
51
  <label className="block text-[10px] uppercase font-bold text-slate-500 tracking-wider mb-2">Gestion Multi-Tenant</label>
52
  <select
@@ -60,7 +62,7 @@ export default function MainLayout({ children, isSuperAdmin, orgs }: MainLayoutP
60
  ))}
61
  </select>
62
  </div>
63
- )}
64
 
65
  <nav className="space-y-1 flex-1">
66
  {navItems.map(n => (
 
4
  import { useTenant } from '@/lib/tenant';
5
  import { BarChart2, TrendingUp, Users, BookOpen, Mic, Building2, Activity, Lightbulb } from 'lucide-react';
6
 
7
+ import RoleGuard from '@/components/RoleGuard';
8
+
9
  interface MainLayoutProps {
10
  children: React.ReactNode;
11
  isSuperAdmin: boolean;
 
48
  <span className="truncate">{currentOrg?.name || 'EdTech Admin'}</span>
49
  </div>
50
 
51
+ <RoleGuard requireSuperAdmin>
52
  <div className="mb-8">
53
  <label className="block text-[10px] uppercase font-bold text-slate-500 tracking-wider mb-2">Gestion Multi-Tenant</label>
54
  <select
 
62
  ))}
63
  </select>
64
  </div>
65
+ </RoleGuard>
66
 
67
  <nav className="space-y-1 flex-1">
68
  {navItems.map(n => (