Skip to content

Commit ab4c4e7

Browse files
committed
Add basic permissions checking logic to frontend
1 parent 7e0ac2c commit ab4c4e7

File tree

4 files changed

+81
-11
lines changed

4 files changed

+81
-11
lines changed

resources/scripts/api/server/getServer.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,23 @@ export const rawDataToServerObject = (data: any): Server => ({
4141
port: data.sftp_details.port,
4242
},
4343
description: data.description ? ((data.description.length > 0) ? data.description : null) : null,
44-
allocations: [{
44+
allocations: [ {
4545
ip: data.allocation.ip,
4646
alias: null,
4747
port: data.allocation.port,
4848
default: true,
49-
}],
49+
} ],
5050
limits: { ...data.limits },
5151
featureLimits: { ...data.feature_limits },
5252
});
5353

54-
export default (uuid: string): Promise<Server> => {
54+
export default (uuid: string): Promise<[ Server, string[] ]> => {
5555
return new Promise((resolve, reject) => {
5656
http.get(`/api/client/servers/${uuid}`)
57-
.then(response => resolve(rawDataToServerObject(response.data.attributes)))
57+
.then(({ data }) => resolve([
58+
rawDataToServerObject(data.attributes),
59+
data.meta?.is_server_owner ? ['*'] : (data.meta?.user_permissions || []),
60+
]))
5861
.catch(reject);
5962
});
6063
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, { useMemo } from 'react';
2+
import { ServerContext } from '@/state/server';
3+
4+
interface Props {
5+
action: string | string[];
6+
renderOnError?: React.ReactNode | null;
7+
children: React.ReactNode;
8+
}
9+
10+
const Can = ({ action, renderOnError, children }: Props) => {
11+
const userPermissions = ServerContext.useStoreState(state => state.server.permissions);
12+
const actions = Array.isArray(action) ? action : [ action ];
13+
14+
const missingPermissionCount = useMemo(() => {
15+
if (userPermissions[0] === '*') {
16+
return 0;
17+
}
18+
19+
return actions.filter(permission => {
20+
return !(
21+
// Allows checking for any permission matching a name, for example files.*
22+
// will return if the user has any permission under the file.XYZ namespace.
23+
(
24+
permission.endsWith('.*')
25+
&& permission !== 'websocket.*'
26+
&& userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0
27+
)
28+
// Otherwise just check if the entire permission exists in the array or not.
29+
|| userPermissions.indexOf(permission) >= 0);
30+
}).length;
31+
}, [ action, userPermissions ]);
32+
33+
return (
34+
<>
35+
{missingPermissionCount > 0 ?
36+
renderOnError
37+
:
38+
children
39+
}
40+
</>
41+
);
42+
};
43+
44+
export default Can;

resources/scripts/routers/ServerRouter.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SettingsContainer from '@/components/server/settings/SettingsContainer';
1515
import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
1616
import ScheduleEditContainer from '@/components/server/schedules/ScheduleEditContainer';
1717
import UsersContainer from '@/components/server/users/UsersContainer';
18+
import Can from '@/components/elements/Can';
1819

1920
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
2021
const server = ServerContext.useStoreState(state => state.server.data);
@@ -34,11 +35,21 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
3435
<div id={'sub-navigation'}>
3536
<div className={'items'}>
3637
<NavLink to={`${match.url}`} exact>Console</NavLink>
37-
<NavLink to={`${match.url}/files`}>File Manager</NavLink>
38-
<NavLink to={`${match.url}/databases`}>Databases</NavLink>
39-
<NavLink to={`${match.url}/schedules`}>Schedules</NavLink>
40-
<NavLink to={`${match.url}/users`}>Users</NavLink>
41-
<NavLink to={`${match.url}/settings`}>Settings</NavLink>
38+
<Can action={'file.*'}>
39+
<NavLink to={`${match.url}/files`}>File Manager</NavLink>
40+
</Can>
41+
<Can action={'database.*'}>
42+
<NavLink to={`${match.url}/databases`}>Databases</NavLink>
43+
</Can>
44+
<Can action={'schedule.*'}>
45+
<NavLink to={`${match.url}/schedules`}>Schedules</NavLink>
46+
</Can>
47+
<Can action={'user.*'}>
48+
<NavLink to={`${match.url}/users`}>Users</NavLink>
49+
</Can>
50+
<Can action={'settings.*'}>
51+
<NavLink to={`${match.url}/settings`}>Settings</NavLink>
52+
</Can>
4253
</div>
4354
</div>
4455
</CSSTransition>

resources/scripts/state/server/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,30 @@ export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running';
1010

1111
interface ServerDataStore {
1212
data?: Server;
13+
permissions: string[];
14+
1315
getServer: Thunk<ServerDataStore, string, {}, ServerStore, Promise<void>>;
1416
setServer: Action<ServerDataStore, Server>;
17+
setPermissions: Action<ServerDataStore, string[]>;
1518
}
1619

1720
const server: ServerDataStore = {
21+
permissions: [],
22+
1823
getServer: thunk(async (actions, payload) => {
19-
const server = await getServer(payload);
24+
const [server, permissions] = await getServer(payload);
25+
2026
actions.setServer(server);
27+
actions.setPermissions(permissions);
2128
}),
29+
2230
setServer: action((state, payload) => {
2331
state.data = payload;
2432
}),
33+
34+
setPermissions: action((state, payload) => {
35+
state.permissions = payload;
36+
}),
2537
};
2638

2739
interface ServerStatusStore {
@@ -75,9 +87,9 @@ export const ServerContext = createContextStore<ServerStore>({
7587
subusers,
7688
clearServerState: action(state => {
7789
state.server.data = undefined;
90+
state.server.permissions = [];
7891
state.databases.items = [];
7992
state.subusers.data = [];
80-
8193
state.files.directory = '/';
8294
state.files.contents = [];
8395

0 commit comments

Comments
 (0)