Skip to content

Commit ddc20bf

Browse files
committed
Merge remote-tracking branch 'origin/main' into enable-multiple-auth-methods
2 parents 304a2ec + 4e77a0f commit ddc20bf

File tree

10 files changed

+161
-36
lines changed

10 files changed

+161
-36
lines changed

config.schema.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@
124124
"description": "Configuration source"
125125
}
126126
}
127+
},
128+
"uiRouteAuth": {
129+
"description": "UI routes that require authentication (logged in or admin)",
130+
"type": "object",
131+
"properties": {
132+
"enabled": { "type": "boolean" },
133+
"rules": {
134+
"type": "array",
135+
"items": {
136+
"$ref": "#/definitions/routeAuthRule"
137+
}
138+
}
139+
}
127140
}
128141
},
129142
"definitions": {
@@ -155,6 +168,14 @@
155168
"options": { "type": "object" }
156169
},
157170
"required": ["type", "enabled"]
171+
},
172+
"routeAuthRule": {
173+
"type": "object",
174+
"properties": {
175+
"pattern": { "type": "string" },
176+
"adminOnly": { "type": "boolean" },
177+
"loginRequired": { "type": "boolean" }
178+
}
158179
}
159180
},
160181
"additionalProperties": false

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@finos/git-proxy",
3-
"version": "1.14.0",
3+
"version": "1.15.0",
44
"description": "Deploy custom push protections and policies on top of Git.",
55
"scripts": {
66
"cli": "node ./packages/git-proxy-cli/index.js",

proxy.config.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,20 @@
150150
"enabled": false,
151151
"key": "certs/key.pem",
152152
"cert": "certs/cert.pem"
153+
},
154+
"uiRouteAuth": {
155+
"enabled": false,
156+
"rules": [
157+
{
158+
"pattern": "/dashboard/*",
159+
"adminOnly": false,
160+
"loginRequired": true
161+
},
162+
{
163+
"pattern": "/admin/*",
164+
"adminOnly": true,
165+
"loginRequired": true
166+
}
167+
]
153168
}
154169
}

src/config/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ let _rateLimit: RateLimitConfig = defaultSettings.rateLimit;
3838
let _tlsEnabled = defaultSettings.tls.enabled;
3939
let _tlsKeyPemPath = defaultSettings.tls.key;
4040
let _tlsCertPemPath = defaultSettings.tls.cert;
41+
let _uiRouteAuth: Record<string, unknown> = defaultSettings.uiRouteAuth;
4142

4243
// Initialize configuration with defaults and user settings
4344
let _config = { ...defaultSettings, ...(_userSettings || {}) } as Configuration;
@@ -231,6 +232,13 @@ export const getDomains = () => {
231232
return _domains;
232233
};
233234

235+
export const getUIRouteAuth = () => {
236+
if (_userSettings && _userSettings.uiRouteAuth) {
237+
_uiRouteAuth = _userSettings.uiRouteAuth;
238+
}
239+
return _uiRouteAuth;
240+
};
241+
234242
export const getRateLimit = () => {
235243
if (_userSettings && _userSettings.rateLimit) {
236244
_rateLimit = _userSettings.rateLimit;

src/routes.jsx

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818

1919
import React from 'react';
20-
import PrivateRoute from './ui/components/PrivateRoute/PrivateRoute';
20+
import RouteGuard from './ui/components/RouteGuard/RouteGuard';
2121
import Person from '@material-ui/icons/Person';
2222
import OpenPushRequests from './ui/views/OpenPushRequests/OpenPushRequests';
2323
import PushDetails from './ui/views/PushDetails/PushDetails';
@@ -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+
<RouteGuard
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+
<RouteGuard
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+
<RouteGuard
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+
<RouteGuard
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+
<RouteGuard
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+
<RouteGuard
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+
<RouteGuard
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: 0 additions & 24 deletions
This file was deleted.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Navigate } from 'react-router-dom';
3+
import { useAuth } from '../../auth/AuthProvider';
4+
import { getUIRouteAuth } from '../../services/config';
5+
import CircularProgress from '@material-ui/core/CircularProgress';
6+
7+
interface RouteGuardProps {
8+
component: React.ComponentType<any>;
9+
fullRoutePath: string;
10+
}
11+
12+
interface UIRouteAuth {
13+
enabled: boolean;
14+
rules: {
15+
pattern: string;
16+
adminOnly: boolean;
17+
loginRequired: boolean;
18+
}[];
19+
}
20+
21+
const RouteGuard = ({ component: Component, fullRoutePath }: RouteGuardProps) => {
22+
const { user, isLoading } = useAuth();
23+
24+
const [loginRequired, setLoginRequired] = useState(false);
25+
const [adminOnly, setAdminOnly] = useState(false);
26+
const [authChecked, setAuthChecked] = useState(false);
27+
28+
useEffect(() => {
29+
getUIRouteAuth((uiRouteAuth: UIRouteAuth) => {
30+
if (uiRouteAuth?.enabled) {
31+
for (const rule of uiRouteAuth.rules) {
32+
if (new RegExp(rule.pattern).test(fullRoutePath)) {
33+
// Allow multiple rules to be applied according to route precedence
34+
// Ex: /dashboard/admin/* will override /dashboard/*
35+
setLoginRequired(loginRequired || rule.loginRequired);
36+
setAdminOnly(adminOnly || rule.adminOnly);
37+
}
38+
}
39+
} else {
40+
console.log('UI route auth is not enabled.');
41+
}
42+
setAuthChecked(true);
43+
});
44+
}, [fullRoutePath]);
45+
46+
if (!authChecked || isLoading) {
47+
return <CircularProgress />;
48+
}
49+
50+
if (loginRequired && !user) {
51+
return <Navigate to="/login" />;
52+
}
53+
54+
if (adminOnly && !user?.admin) {
55+
return <Navigate to="/not-authorized" />;
56+
}
57+
58+
return <Component />;
59+
};
60+
61+
export default RouteGuard;

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)