Skip to content

Commit 645a27b

Browse files
committed
feat: add dynamic auth processing for PrivateRoute
1 parent d1c6c26 commit 645a27b

File tree

4 files changed

+95
-18
lines changed

4 files changed

+95
-18
lines changed

src/routes.jsx

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,55 +35,83 @@ const dashboardRoutes = [
3535
path: '/repo',
3636
name: 'Repositories',
3737
icon: RepoIcon,
38-
component: (props) => <PrivateRoute component={RepoList} />,
38+
component: (props) =>
39+
<PrivateRoute
40+
component={RepoList}
41+
fullRoutePath={`/dashboard/repo`}
42+
/>,
3943
layout: '/dashboard',
4044
visible: true,
4145
},
4246
{
4347
path: '/repo/:id',
4448
name: 'Repo Details',
4549
icon: Person,
46-
component: (props) => <PrivateRoute component={RepoDetails} />,
50+
component: (props) =>
51+
<PrivateRoute
52+
component={RepoDetails}
53+
fullRoutePath={`/dashboard/repo/:id`}
54+
/>,
4755
layout: '/dashboard',
4856
visible: false,
4957
},
5058
{
5159
path: '/push',
5260
name: 'Dashboard',
5361
icon: Dashboard,
54-
component: (props) => <PrivateRoute component={OpenPushRequests} />,
62+
component: (props) =>
63+
<PrivateRoute
64+
component={OpenPushRequests}
65+
fullRoutePath={`/dashboard/push`}
66+
/>,
5567
layout: '/dashboard',
5668
visible: true,
5769
},
5870
{
5971
path: '/push/:id',
6072
name: 'Open Push Requests',
6173
icon: Person,
62-
component: (props) => <PrivateRoute component={PushDetails} />,
74+
component: (props) =>
75+
<PrivateRoute
76+
component={PushDetails}
77+
fullRoutePath={`/dashboard/push/:id`}
78+
/>,
6379
layout: '/dashboard',
6480
visible: false,
6581
},
6682
{
6783
path: '/profile',
6884
name: 'My Account',
6985
icon: AccountCircle,
70-
component: (props) => <PrivateRoute component={User} />,
86+
component: (props) =>
87+
<PrivateRoute
88+
component={User}
89+
fullRoutePath={`/dashboard/profile`}
90+
/>,
7191
layout: '/dashboard',
7292
visible: true,
7393
},
7494
{
7595
path: '/admin/user',
7696
name: 'Users',
7797
icon: Group,
78-
component: (props) => <PrivateRoute adminOnly component={UserList} />,
98+
component: (props) =>
99+
<PrivateRoute
100+
component={UserList}
101+
fullRoutePath={`/dashboard/admin/user`}
102+
/>,
79103
layout: '/dashboard',
80104
visible: true,
81105
},
82106
{
83107
path: '/admin/user/:id',
84108
name: 'User',
85109
icon: Person,
86-
component: (props) => <PrivateRoute adminOnly component={User} />,
110+
component: (props) =>
111+
<PrivateRoute
112+
component={User}
113+
fullRoutePath={`/dashboard/admin/user/:id`}
114+
/>,
87115
layout: '/dashboard',
88116
visible: false,
89117
},

src/service/routes/config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ router.get('/contactEmail', function ({ res }) {
1515
res.send(config.getContactEmail());
1616
});
1717

18+
router.get('/uiRouteAuth', function ({ res }) {
19+
res.send(config.getUIRouteAuth());
20+
});
21+
1822
module.exports = router;

src/ui/components/PrivateRoute/PrivateRoute.tsx

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,56 @@
1-
import React from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { Navigate } from 'react-router-dom';
33
import { useAuth } from '../../auth/AuthProvider';
4+
import { getUIRouteAuth } from '../../services/config';
45

5-
const PrivateRoute = ({ component: Component, adminOnly = false }) => {
6+
interface PrivateRouteProps {
7+
component: React.ComponentType<any>;
8+
fullRoutePath: string;
9+
}
10+
11+
interface UIRouteAuth {
12+
enabled: boolean;
13+
rules: {
14+
pattern: string;
15+
adminOnly: boolean;
16+
loginRequired: boolean;
17+
}[];
18+
}
19+
20+
const PrivateRoute = ({ component: Component, fullRoutePath }: PrivateRouteProps) => {
621
const { user, isLoading } = useAuth();
7-
console.debug('PrivateRoute', { user, isLoading, adminOnly });
822

9-
if (isLoading) {
10-
console.debug('Auth is loading, waiting');
11-
return <div>Loading...</div>; // TODO: Add loading spinner
23+
const [loginRequired, setLoginRequired] = useState(false);
24+
const [adminOnly, setAdminOnly] = useState(false);
25+
const [authChecked, setAuthChecked] = useState(false);
26+
27+
useEffect(() => {
28+
getUIRouteAuth((uiRouteAuth: UIRouteAuth) => {
29+
if (uiRouteAuth?.enabled) {
30+
for (const rule of uiRouteAuth.rules) {
31+
if (new RegExp(rule.pattern).test(fullRoutePath)) {
32+
// Allow multiple rules to be applied according to route precedence
33+
// Ex: /dashboard/admin/* will override /dashboard/*
34+
setLoginRequired(loginRequired || rule.loginRequired);
35+
setAdminOnly(adminOnly || rule.adminOnly);
36+
}
37+
}
38+
} else {
39+
console.log('UI route auth is not enabled.');
40+
}
41+
setAuthChecked(true);
42+
});
43+
}, [fullRoutePath]);
44+
45+
if (!authChecked || isLoading) {
46+
return <div>Loading...</div>; // TODO: Add a skeleton loader or spinner
1247
}
1348

14-
if (!user) {
15-
console.debug('User not logged in, redirecting to login page');
49+
if (loginRequired && !user) {
1650
return <Navigate to="/login" />;
1751
}
1852

19-
if (adminOnly && !user.admin) {
20-
console.debug('User is not an admin, redirecting to not authorized page');
53+
if (adminOnly && !user?.admin) {
2154
return <Navigate to="/not-authorized" />;
2255
}
2356

src/ui/services/config.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,16 @@ const getEmailContact = async (setData) => {
2525
});
2626
};
2727

28-
export { getAttestationConfig, getURLShortener, getEmailContact };
28+
const getUIRouteAuth = async (setData) => {
29+
const url = new URL(`${baseUrl}/config/uiRouteAuth`);
30+
await axios(url.toString()).then((response) => {
31+
setData(response.data);
32+
});
33+
};
34+
35+
export {
36+
getAttestationConfig,
37+
getURLShortener,
38+
getEmailContact,
39+
getUIRouteAuth,
40+
};

0 commit comments

Comments
 (0)