Update frontend to only allow selection of valid permissions for subusers
This commit is contained in:
parent
00b0d30c60
commit
a1c3730861
4 changed files with 52 additions and 10 deletions
|
@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Pterodactyl\Models\Permission;
|
||||||
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
|
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
|
||||||
use Pterodactyl\Services\Subusers\SubuserCreationService;
|
use Pterodactyl\Services\Subusers\SubuserCreationService;
|
||||||
use Pterodactyl\Transformers\Api\Client\SubuserTransformer;
|
use Pterodactyl\Transformers\Api\Client\SubuserTransformer;
|
||||||
|
@ -123,6 +124,6 @@ class SubuserController extends ClientApiController
|
||||||
*/
|
*/
|
||||||
protected function getDefaultPermissions(Request $request): array
|
protected function getDefaultPermissions(Request $request): array
|
||||||
{
|
{
|
||||||
return array_unique(array_merge($request->input('permissions') ?? [], ['websocket.connect']));
|
return array_unique(array_merge($request->input('permissions') ?? [], [Permission::ACTION_WEBSOCKET_CONNECT]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { forwardRef, useRef } from 'react';
|
import React, { forwardRef, useEffect, useRef } from 'react';
|
||||||
import { Subuser } from '@/state/server/subusers';
|
import { Subuser } from '@/state/server/subusers';
|
||||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||||
import { array, object, string } from 'yup';
|
import { array, object, string } from 'yup';
|
||||||
|
@ -16,6 +16,7 @@ import { httpErrorToHuman } from '@/api/http';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import Can from '@/components/elements/Can';
|
import Can from '@/components/elements/Can';
|
||||||
import { usePermissions } from '@/plugins/usePermissions';
|
import { usePermissions } from '@/plugins/usePermissions';
|
||||||
|
import { useDeepMemo } from '@/plugins/useDeepMemo';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
subuser?: Subuser;
|
subuser?: Subuser;
|
||||||
|
@ -37,12 +38,38 @@ const PermissionLabel = styled.label`
|
||||||
${tw`border-neutral-500 bg-neutral-800`};
|
${tw`border-neutral-500 bg-neutral-800`};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
${tw`opacity-50`};
|
||||||
|
|
||||||
|
& input[type="checkbox"]:not(:checked) {
|
||||||
|
${tw`border-0`};
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...props }, ref) => {
|
const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...props }, ref) => {
|
||||||
const { values, isSubmitting, setFieldValue } = useFormikContext<Values>();
|
const { values, isSubmitting, setFieldValue } = useFormikContext<Values>();
|
||||||
const [ canEditUser ] = usePermissions([ 'user.update' ]);
|
const [ canEditUser ] = usePermissions(subuser ? [ 'user.update' ] : [ 'user.create' ]);
|
||||||
const permissions = useStoreState((state: ApplicationStore) => state.permissions.data);
|
const permissions = useStoreState(state => state.permissions.data);
|
||||||
|
|
||||||
|
// The currently logged in user's permissions. We're going to filter out any permissions
|
||||||
|
// that they should not need.
|
||||||
|
const loggedInPermissions = ServerContext.useStoreState(state => state.server.permissions);
|
||||||
|
|
||||||
|
// The permissions that can be modified by this user.
|
||||||
|
const editablePermissions = useDeepMemo(() => {
|
||||||
|
const cleaned = Object.keys(permissions)
|
||||||
|
.map(key => Object.keys(permissions[key].keys).map(pkey => `${key}.${pkey}`));
|
||||||
|
|
||||||
|
const list: string[] = ([] as string[]).concat.apply([], Object.values(cleaned));
|
||||||
|
|
||||||
|
if (loggedInPermissions.length === 1 && loggedInPermissions[0] === '*') {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.filter(key => loggedInPermissions.indexOf(key) >= 0);
|
||||||
|
}, [permissions, loggedInPermissions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal {...props} top={false} showSpinnerOverlay={isSubmitting}>
|
<Modal {...props} top={false} showSpinnerOverlay={isSubmitting}>
|
||||||
|
@ -54,6 +81,12 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
||||||
}
|
}
|
||||||
</h3>
|
</h3>
|
||||||
<FlashMessageRender byKey={'user:edit'} className={'mt-4'}/>
|
<FlashMessageRender byKey={'user:edit'} className={'mt-4'}/>
|
||||||
|
<div className={'mt-4 pl-4 py-2 border-l-4 border-cyan-400'}>
|
||||||
|
<p className={'text-sm text-neutral-300'}>
|
||||||
|
Only permissions which your account is currently assigned may be selected when creating or
|
||||||
|
modifying other users.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{!subuser &&
|
{!subuser &&
|
||||||
<div className={'mt-6'}>
|
<div className={'mt-6'}>
|
||||||
<Field
|
<Field
|
||||||
|
@ -70,7 +103,7 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
||||||
title={
|
title={
|
||||||
<div className={'flex items-center'}>
|
<div className={'flex items-center'}>
|
||||||
<p className={'text-sm uppercase flex-1'}>{key}</p>
|
<p className={'text-sm uppercase flex-1'}>{key}</p>
|
||||||
{canEditUser &&
|
{canEditUser && editablePermissions.indexOf(key) >= 0 &&
|
||||||
<input
|
<input
|
||||||
type={'checkbox'}
|
type={'checkbox'}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
|
@ -106,7 +139,7 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
||||||
htmlFor={`permission_${key}_${pkey}`}
|
htmlFor={`permission_${key}_${pkey}`}
|
||||||
className={classNames('transition-colors duration-75', {
|
className={classNames('transition-colors duration-75', {
|
||||||
'mt-2': index !== 0,
|
'mt-2': index !== 0,
|
||||||
disabled: !canEditUser,
|
disabled: !canEditUser || editablePermissions.indexOf(`${key}.${pkey}`) < 0,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className={'p-2'}>
|
<div className={'p-2'}>
|
||||||
|
@ -115,7 +148,7 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
||||||
name={'permissions'}
|
name={'permissions'}
|
||||||
value={`${key}.${pkey}`}
|
value={`${key}.${pkey}`}
|
||||||
className={'w-5 h-5 mr-2'}
|
className={'w-5 h-5 mr-2'}
|
||||||
disabled={!canEditUser}
|
disabled={!canEditUser || editablePermissions.indexOf(`${key}.${pkey}`) < 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1'}>
|
<div className={'flex-1'}>
|
||||||
|
@ -133,7 +166,7 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
|
||||||
</TitledGreyBox>
|
</TitledGreyBox>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Can action={subuser ? 'user.update' : 'user.delete'}>
|
<Can action={subuser ? 'user.update' : 'user.create'}>
|
||||||
<div className={'pb-6 flex justify-end'}>
|
<div className={'pb-6 flex justify-end'}>
|
||||||
<button className={'btn btn-primary btn-sm'} type={'submit'}>
|
<button className={'btn btn-primary btn-sm'} type={'submit'}>
|
||||||
{subuser ? 'Save' : 'Invite User'}
|
{subuser ? 'Save' : 'Invite User'}
|
||||||
|
@ -169,6 +202,10 @@ export default ({ subuser, ...props }: Props) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearFlashes('user:edit');
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
onSubmit={submit}
|
onSubmit={submit}
|
||||||
|
|
|
@ -8,12 +8,14 @@ import { faUnlockAlt } from '@fortawesome/free-solid-svg-icons/faUnlockAlt';
|
||||||
import { faUserLock } from '@fortawesome/free-solid-svg-icons/faUserLock';
|
import { faUserLock } from '@fortawesome/free-solid-svg-icons/faUserLock';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Can from '@/components/elements/Can';
|
import Can from '@/components/elements/Can';
|
||||||
|
import { useStoreState } from 'easy-peasy';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
subuser: Subuser;
|
subuser: Subuser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ subuser }: Props) => {
|
export default ({ subuser }: Props) => {
|
||||||
|
const uuid = useStoreState(state => state.user!.data!.uuid);
|
||||||
const [ visible, setVisible ] = useState(false);
|
const [ visible, setVisible ] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -54,7 +56,9 @@ export default ({ subuser }: Props) => {
|
||||||
<button
|
<button
|
||||||
type={'button'}
|
type={'button'}
|
||||||
aria-label={'Edit subuser'}
|
aria-label={'Edit subuser'}
|
||||||
className={'block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mx-4'}
|
className={classNames('block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mx-4', {
|
||||||
|
hidden: subuser.uuid === uuid,
|
||||||
|
})}
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faPencilAlt}/>
|
<FontAwesomeIcon icon={faPencilAlt}/>
|
||||||
|
|
|
@ -229,7 +229,7 @@ a.btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"], input[type="radio"] {
|
input[type="checkbox"], input[type="radio"] {
|
||||||
@apply .appearance-none .inline-block .align-middle .select-none .flex-no-shrink .w-4 .h-4 .text-primary-400 .border .border-neutral-300 .rounded-sm;
|
@apply .cursor-pointer .appearance-none .inline-block .align-middle .select-none .flex-no-shrink .w-4 .h-4 .text-primary-400 .border .border-neutral-300 .rounded-sm;
|
||||||
color-adjust: exact;
|
color-adjust: exact;
|
||||||
background-origin: border-box;
|
background-origin: border-box;
|
||||||
transition: all 75ms linear, box-shadow 25ms linear;
|
transition: all 75ms linear, box-shadow 25ms linear;
|
||||||
|
|
Loading…
Reference in a new issue