Update permissions checking code
This commit is contained in:
parent
2e9d327dfc
commit
8bc81c8c4b
7 changed files with 79 additions and 43 deletions
|
@ -1,5 +1,5 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import React from 'react';
|
||||
import { usePermissions } from '@/plugins/usePermissions';
|
||||
|
||||
interface Props {
|
||||
action: string | string[];
|
||||
|
@ -8,35 +8,11 @@ interface Props {
|
|||
}
|
||||
|
||||
const Can = ({ action, renderOnError, children }: Props) => {
|
||||
const userPermissions = ServerContext.useStoreState(state => state.server.permissions);
|
||||
const actions = Array.isArray(action) ? action : [ action ];
|
||||
|
||||
const missingPermissionCount = useMemo(() => {
|
||||
if (userPermissions[0] === '*') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return actions.filter(permission => {
|
||||
return !(
|
||||
// Allows checking for any permission matching a name, for example files.*
|
||||
// will return if the user has any permission under the file.XYZ namespace.
|
||||
(
|
||||
permission.endsWith('.*') &&
|
||||
permission !== 'websocket.*' &&
|
||||
userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0
|
||||
)
|
||||
// Otherwise just check if the entire permission exists in the array or not.
|
||||
|| userPermissions.indexOf(permission) >= 0);
|
||||
}).length;
|
||||
}, [ action, userPermissions ]);
|
||||
const can = usePermissions(action);
|
||||
|
||||
return (
|
||||
<>
|
||||
{missingPermissionCount > 0 ?
|
||||
renderOnError
|
||||
:
|
||||
children
|
||||
}
|
||||
{can.every(p => p) ? children : renderOnError}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,6 +14,8 @@ import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser';
|
|||
import { ServerContext } from '@/state/server';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import Can from '@/components/elements/Can';
|
||||
import { usePermissions } from '@/plugins/usePermissions';
|
||||
|
||||
type Props = {
|
||||
subuser?: Subuser;
|
||||
|
@ -25,21 +27,32 @@ interface Values {
|
|||
}
|
||||
|
||||
const PermissionLabel = styled.label`
|
||||
${tw`flex items-center border border-transparent rounded p-2 cursor-pointer`};
|
||||
${tw`flex items-center border border-transparent rounded p-2`};
|
||||
text-transform: none;
|
||||
|
||||
&:not(.disabled) {
|
||||
${tw`cursor-pointer`};
|
||||
|
||||
&:hover {
|
||||
${tw`border-neutral-500 bg-neutral-800`};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...props }, ref) => {
|
||||
const { values, isSubmitting, setFieldValue } = useFormikContext<Values>();
|
||||
const [ canEditUser ] = usePermissions([ 'user.update' ]);
|
||||
const permissions = useStoreState((state: ApplicationStore) => state.permissions.data);
|
||||
|
||||
return (
|
||||
<Modal {...props} showSpinnerOverlay={isSubmitting}>
|
||||
<h3 ref={ref}>{subuser ? `Modify permissions for ${subuser.email}` : 'Create new subuser'}</h3>
|
||||
<h3 ref={ref}>
|
||||
{subuser ?
|
||||
`${canEditUser ? 'Modify' : 'View'} permissions for ${subuser.email}`
|
||||
:
|
||||
'Create new subuser'
|
||||
}
|
||||
</h3>
|
||||
<FlashMessageRender byKey={'user:edit'} className={'mt-4'}/>
|
||||
{!subuser &&
|
||||
<div className={'mt-6'}>
|
||||
|
@ -50,13 +63,14 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
|||
/>
|
||||
</div>
|
||||
}
|
||||
<div className={'mt-6'}>
|
||||
<div className={'my-6'}>
|
||||
{Object.keys(permissions).filter(key => key !== 'websocket').map((key, index) => (
|
||||
<TitledGreyBox
|
||||
key={key}
|
||||
title={
|
||||
<div className={'flex items-center'}>
|
||||
<p className={'text-sm uppercase flex-1'}>{key}</p>
|
||||
{canEditUser &&
|
||||
<input
|
||||
type={'checkbox'}
|
||||
onClick={e => {
|
||||
|
@ -78,6 +92,7 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
|||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
className={index !== 0 ? 'mt-4' : undefined}
|
||||
|
@ -87,9 +102,11 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
|||
</p>
|
||||
{Object.keys(permissions[key].keys).map((pkey, index) => (
|
||||
<PermissionLabel
|
||||
key={`permission_${key}_${pkey}`}
|
||||
htmlFor={`permission_${key}_${pkey}`}
|
||||
className={classNames('transition-colors duration-75', {
|
||||
'mt-2': index !== 0,
|
||||
disabled: !canEditUser,
|
||||
})}
|
||||
>
|
||||
<div className={'p-2'}>
|
||||
|
@ -98,6 +115,7 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
|||
name={'permissions'}
|
||||
value={`${key}.${pkey}`}
|
||||
className={'w-5 h-5 mr-2'}
|
||||
disabled={!canEditUser}
|
||||
/>
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
|
@ -115,11 +133,13 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
|||
</TitledGreyBox>
|
||||
))}
|
||||
</div>
|
||||
<div className={'mt-6 pb-6 flex justify-end'}>
|
||||
<Can action={subuser ? 'user.update' : 'user.delete'}>
|
||||
<div className={'pb-6 flex justify-end'}>
|
||||
<button className={'btn btn-primary btn-sm'} type={'submit'}>
|
||||
{subuser ? 'Save' : 'Invite User'}
|
||||
</button>
|
||||
</div>
|
||||
</Can>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import EditSubuserModal from '@/components/server/users/EditSubuserModal';
|
|||
import { faUnlockAlt } from '@fortawesome/free-solid-svg-icons/faUnlockAlt';
|
||||
import { faUserLock } from '@fortawesome/free-solid-svg-icons/faUserLock';
|
||||
import classNames from 'classnames';
|
||||
import Can from '@/components/elements/Can';
|
||||
|
||||
interface Props {
|
||||
subuser: Subuser;
|
||||
|
@ -58,7 +59,9 @@ export default ({ subuser }: Props) => {
|
|||
>
|
||||
<FontAwesomeIcon icon={faPencilAlt}/>
|
||||
</button>
|
||||
<Can action={'user.delete'}>
|
||||
<RemoveSubuserButton subuser={subuser}/>
|
||||
</Can>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ import UserRow from '@/components/server/users/UserRow';
|
|||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import getServerSubusers from '@/api/server/users/getServerSubusers';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import Can from '@/components/elements/Can';
|
||||
|
||||
export default () => {
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
|
@ -53,9 +54,11 @@ export default () => {
|
|||
<UserRow key={subuser.uuid} subuser={subuser}/>
|
||||
))
|
||||
}
|
||||
<Can action={'user.create'}>
|
||||
<div className={'flex justify-end mt-6'}>
|
||||
<AddSubuserButton/>
|
||||
</div>
|
||||
</Can>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
12
resources/scripts/plugins/useDeepMemo.ts
Normal file
12
resources/scripts/plugins/useDeepMemo.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { useRef } from 'react';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
|
||||
export const useDeepMemo = <T, K> (fn: () => T, key: K): T => {
|
||||
const ref = useRef<{ key: K, value: T }>();
|
||||
|
||||
if (!ref.current || !isEqual(key, ref.current.key)) {
|
||||
ref.current = { key, value: fn() };
|
||||
}
|
||||
|
||||
return ref.current.value;
|
||||
};
|
22
resources/scripts/plugins/usePermissions.ts
Normal file
22
resources/scripts/plugins/usePermissions.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { ServerContext } from '@/state/server';
|
||||
import { useDeepMemo } from '@/plugins/useDeepMemo';
|
||||
|
||||
export const usePermissions = (action: string | string[]): boolean[] => {
|
||||
const userPermissions = ServerContext.useStoreState(state => state.server.permissions);
|
||||
|
||||
return useDeepMemo(() => {
|
||||
return (Array.isArray(action) ? action : [ action ])
|
||||
.map(permission => (
|
||||
// Allows checking for any permission matching a name, for example files.*
|
||||
// will return if the user has any permission under the file.XYZ namespace.
|
||||
(
|
||||
permission.endsWith('.*') &&
|
||||
permission !== 'websocket.*' &&
|
||||
userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0
|
||||
) ||
|
||||
// Otherwise just check if the entire permission exists in the array or not.
|
||||
userPermissions.indexOf(permission) >= 0
|
||||
),
|
||||
);
|
||||
}, [ action, userPermissions ]);
|
||||
};
|
Loading…
Reference in a new issue