mirror of
https://github.com/jeffvli/feishin.git
synced 2024-11-20 14:37:06 +01:00
Add per-server permissions
This commit is contained in:
parent
73e6002cc7
commit
581ef32845
@ -9,8 +9,8 @@ const getUserDetail = async (
|
||||
req: TypedRequest<typeof validation.users.detail>,
|
||||
res: Response
|
||||
) => {
|
||||
const { id } = req.params;
|
||||
const user = await service.users.findById(req.authUser, { id });
|
||||
const { userId } = req.params;
|
||||
const user = await service.users.findById(req.authUser, { id: userId });
|
||||
const success = ApiSuccess.ok({ data: toApiModel.users([user])[0] });
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
40
server/middleware/authenticate-server-admin.ts
Normal file
40
server/middleware/authenticate-server-admin.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { ServerPermission, ServerPermissionType } from '@prisma/client';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
export const authenticateServerAdmin = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
if (!req.params.serverId) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
message: 'Server id is required.',
|
||||
path: req.path,
|
||||
},
|
||||
response: 'Error',
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
if (req.authUser.isAdmin || req.authUser.isSuperAdmin) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const permission = req.authUser.serverPermissions.find(
|
||||
(p: ServerPermission) => p.serverId === req.params.serverId
|
||||
)?.type;
|
||||
|
||||
if (permission !== ServerPermissionType.ADMIN) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
message: 'This action requires "Admin" server permissions.',
|
||||
path: req.path,
|
||||
},
|
||||
response: 'Error',
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
43
server/middleware/authenticate-server-editor.ts
Normal file
43
server/middleware/authenticate-server-editor.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { ServerPermission, ServerPermissionType } from '@prisma/client';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
export const authenticateServerEditor = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
if (!req.params.serverId) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
message: 'Server id is required.',
|
||||
path: req.path,
|
||||
},
|
||||
response: 'Error',
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
if (req.authUser.isAdmin || req.authUser.isSuperAdmin) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const permission = req.authUser.serverPermissions.find(
|
||||
(p: ServerPermission) => p.serverId === req.params.serverId
|
||||
)?.type;
|
||||
|
||||
if (
|
||||
permission !== ServerPermissionType.EDITOR &&
|
||||
permission !== ServerPermissionType.ADMIN
|
||||
) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
message: 'This action requires "Editor" server permissions.',
|
||||
path: req.path,
|
||||
},
|
||||
response: 'Error',
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
40
server/middleware/authenticate-server-viewer.ts
Normal file
40
server/middleware/authenticate-server-viewer.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { ServerPermission, ServerPermissionType } from '@prisma/client';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
export const authenticateServerViewer = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
if (!req.params.serverId) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
message: 'Server id is required.',
|
||||
path: req.path,
|
||||
},
|
||||
response: 'Error',
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
if (req.authUser.isAdmin || req.authUser.isSuperAdmin) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const permission = req.authUser.serverPermissions.find(
|
||||
(p: ServerPermission) => p.serverId === req.params.serverId
|
||||
)?.type;
|
||||
|
||||
if (permission === undefined) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
message: 'This action requires "Viewer" server permissions.',
|
||||
path: req.path,
|
||||
},
|
||||
response: 'Error',
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
20
server/middleware/authenticate-super-admin.ts
Normal file
20
server/middleware/authenticate-super-admin.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
export const authenticateSuperAdmin = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
if (!req.authUser.isSuperAdmin) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
message: 'This action requires an administrator account.',
|
||||
path: req.path,
|
||||
},
|
||||
response: 'Error',
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
@ -1,3 +1,7 @@
|
||||
export * from './error-handler';
|
||||
export * from './authenticate';
|
||||
export * from './authenticate-admin';
|
||||
export * from './authenticate-super-admin';
|
||||
export * from './authenticate-server-admin';
|
||||
export * from './authenticate-server-editor';
|
||||
export * from './authenticate-server-viewer';
|
||||
|
@ -1,6 +1,9 @@
|
||||
import express, { Router } from 'express';
|
||||
import { controller } from '@controllers/index';
|
||||
import { authenticateAdmin } from '@middleware/authenticate-admin';
|
||||
import { authenticateServerAdmin } from '@middleware/authenticate-server-admin';
|
||||
import { authenticateServerEditor } from '@middleware/authenticate-server-editor';
|
||||
import { authenticateServerViewer } from '@middleware/authenticate-server-viewer';
|
||||
import { service } from '@services/index';
|
||||
import { validateRequest, validation } from '@validations/index';
|
||||
|
||||
@ -25,7 +28,7 @@ router
|
||||
controller.servers.getServerDetail
|
||||
)
|
||||
.patch(
|
||||
authenticateAdmin,
|
||||
authenticateServerAdmin,
|
||||
validateRequest(validation.servers.update),
|
||||
controller.servers.updateServer
|
||||
)
|
||||
@ -38,7 +41,7 @@ router
|
||||
router
|
||||
.route('/:serverId/refresh')
|
||||
.get(
|
||||
authenticateAdmin,
|
||||
authenticateServerEditor,
|
||||
validateRequest(validation.servers.refresh),
|
||||
controller.servers.refreshServer
|
||||
);
|
||||
@ -46,23 +49,23 @@ router
|
||||
router
|
||||
.route('/:serverId/scan')
|
||||
.post(
|
||||
authenticateServerAdmin,
|
||||
validateRequest(validation.servers.scan),
|
||||
authenticateAdmin,
|
||||
controller.servers.quickScanServer
|
||||
);
|
||||
|
||||
router
|
||||
.route('/:serverId/full-scan')
|
||||
.post(
|
||||
authenticateServerAdmin,
|
||||
validateRequest(validation.servers.scan),
|
||||
authenticateAdmin,
|
||||
controller.servers.fullScanServer
|
||||
);
|
||||
|
||||
router
|
||||
.route('/:serverId/url')
|
||||
.post(
|
||||
authenticateAdmin,
|
||||
authenticateServerEditor,
|
||||
validateRequest(validation.servers.createUrl),
|
||||
controller.servers.createServerUrl
|
||||
);
|
||||
@ -75,7 +78,7 @@ router.param('urlId', async (_req, _res, next, urlId) => {
|
||||
router
|
||||
.route('/:serverId/url/:urlId')
|
||||
.delete(
|
||||
authenticateAdmin,
|
||||
authenticateServerEditor,
|
||||
validateRequest(validation.servers.deleteUrl),
|
||||
controller.servers.deleteServerUrl
|
||||
);
|
||||
@ -83,6 +86,7 @@ router
|
||||
router
|
||||
.route('/:serverId/url/:urlId/enable')
|
||||
.post(
|
||||
authenticateServerViewer,
|
||||
validateRequest(validation.servers.enableUrl),
|
||||
controller.servers.enableServerUrl
|
||||
);
|
||||
@ -90,6 +94,7 @@ router
|
||||
router
|
||||
.route('/:serverId/url/:urlId/disable')
|
||||
.post(
|
||||
authenticateServerViewer,
|
||||
validateRequest(validation.servers.disableUrl),
|
||||
controller.servers.disableServerUrl
|
||||
);
|
||||
@ -102,7 +107,7 @@ router.param('folderId', async (_req, _res, next, folderId) => {
|
||||
router
|
||||
.route('/:serverId/folder/:folderId')
|
||||
.delete(
|
||||
authenticateAdmin,
|
||||
authenticateServerAdmin,
|
||||
validateRequest(validation.servers.deleteFolder),
|
||||
controller.servers.deleteServerFolder
|
||||
);
|
||||
@ -110,6 +115,7 @@ router
|
||||
router
|
||||
.route('/:serverId/folder/:folderId/enable')
|
||||
.post(
|
||||
authenticateServerAdmin,
|
||||
validateRequest(validation.servers.enableFolder),
|
||||
controller.servers.enableServerFolder
|
||||
);
|
||||
@ -117,6 +123,7 @@ router
|
||||
router
|
||||
.route('/:serverId/folder/:folderId/disable')
|
||||
.post(
|
||||
authenticateServerAdmin,
|
||||
validateRequest(validation.servers.disableFolder),
|
||||
controller.servers.disableServerFolder
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ const findById = async (user: AuthUser, options: { id: string }) => {
|
||||
}
|
||||
|
||||
const uniqueUser = await prisma.user.findUnique({
|
||||
include: { serverFolderPermissions: true },
|
||||
include: { serverFolderPermissions: true, serverPermissions: true },
|
||||
where: { id },
|
||||
});
|
||||
|
||||
@ -61,7 +61,7 @@ const createUser = async (
|
||||
const createdUser = await prisma.user.create({
|
||||
data: {
|
||||
deviceId: `${username}_${randomString(10)}`,
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
isAdmin,
|
||||
password: hashedPassword,
|
||||
username,
|
||||
|
@ -19,6 +19,7 @@ export const useLogin = (
|
||||
const props = {
|
||||
accessToken: res.data.accessToken,
|
||||
permissions: {
|
||||
id: res.data.id,
|
||||
isAdmin: res.data.isAdmin,
|
||||
isSuperAdmin: res.data.isSuperAdmin,
|
||||
username: res.data.username,
|
||||
|
@ -18,7 +18,7 @@ import { useEnableServerUrl } from '@/renderer/features/servers/mutations/use-en
|
||||
import { useFullScan } from '@/renderer/features/servers/mutations/use-full-scan';
|
||||
import { useQuickScan } from '@/renderer/features/servers/mutations/use-quick-scan';
|
||||
import { useUpdateServer } from '@/renderer/features/servers/mutations/use-update-server';
|
||||
import { usePermissions } from '@/renderer/features/shared';
|
||||
import { ServerPermission, usePermissions } from '@/renderer/features/shared';
|
||||
import { useTaskList } from '@/renderer/features/tasks';
|
||||
import { useAuthStore } from '@/renderer/store';
|
||||
import { Font } from '@/renderer/styles';
|
||||
@ -34,6 +34,10 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
||||
const [addCredential, addCredentialHandlers] = useDisclosure(false);
|
||||
|
||||
const permissions = usePermissions();
|
||||
const serverPermission = permissions[
|
||||
server.id as keyof typeof permissions
|
||||
] as ServerPermission;
|
||||
|
||||
const updateServer = useUpdateServer();
|
||||
const enableServerUrl = useEnableServerUrl();
|
||||
const disableServerUrl = useDisableServerUrl();
|
||||
@ -148,23 +152,27 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
||||
<Group position="apart">
|
||||
<Text font={Font.EPILOGUE}>Server details</Text>
|
||||
<Group spacing="md">
|
||||
<Button
|
||||
compact
|
||||
disabled={isRunningTask}
|
||||
loading={fullScan.isLoading}
|
||||
variant="subtle"
|
||||
onClick={handleFullScan}
|
||||
>
|
||||
Full scan
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
disabled={true || isRunningTask}
|
||||
variant="subtle"
|
||||
onClick={handleQuickScan}
|
||||
>
|
||||
Quick scan
|
||||
</Button>
|
||||
{serverPermission >= ServerPermission.ADMIN && (
|
||||
<Button
|
||||
compact
|
||||
disabled={isRunningTask}
|
||||
loading={fullScan.isLoading}
|
||||
variant="subtle"
|
||||
onClick={handleFullScan}
|
||||
>
|
||||
Full scan
|
||||
</Button>
|
||||
)}
|
||||
{serverPermission >= ServerPermission.EDITOR && (
|
||||
<Button
|
||||
compact
|
||||
disabled={true || isRunningTask}
|
||||
variant="subtle"
|
||||
onClick={handleQuickScan}
|
||||
>
|
||||
Quick scan
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
}
|
||||
@ -179,50 +187,58 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
||||
<Group>
|
||||
<Stack>
|
||||
<Text>URL</Text>
|
||||
<Text>Username</Text>
|
||||
{serverPermission >= ServerPermission.EDITOR && (
|
||||
<Text>Username</Text>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text size="sm">{server.url}</Text>
|
||||
<Text size="sm">{server.username}</Text>
|
||||
{serverPermission >= ServerPermission.EDITOR && (
|
||||
<Text size="sm">{server.username}</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Group>
|
||||
<Group>
|
||||
<Button
|
||||
disabled={!permissions.editServer}
|
||||
tooltip={{ label: 'Edit server details' }}
|
||||
variant="default"
|
||||
onClick={() => editHandlers.toggle()}
|
||||
>
|
||||
<RiEdit2Fill color="white" />
|
||||
</Button>
|
||||
</Group>
|
||||
{serverPermission >= ServerPermission.ADMIN && (
|
||||
<Group>
|
||||
<Button
|
||||
tooltip={{ label: 'Edit server details' }}
|
||||
variant="default"
|
||||
onClick={() => editHandlers.toggle()}
|
||||
>
|
||||
<RiEdit2Fill color="white" />
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
</ServerSection>
|
||||
|
||||
<ServerSection title="Music Folders">
|
||||
<Stack>
|
||||
{server.serverFolders?.map((folder) => (
|
||||
<Group key={folder.id} position="apart">
|
||||
<Text size="sm">{folder.name}</Text>
|
||||
<Group>
|
||||
<Button
|
||||
compact
|
||||
disabled={true || !permissions.deleteServerFolder}
|
||||
variant="subtle"
|
||||
>
|
||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||
</Button>
|
||||
<Text size="sm">{folder.name}</Text>
|
||||
<>
|
||||
<Switch
|
||||
checked={folder.enabled}
|
||||
disabled={serverPermission < ServerPermission.ADMIN}
|
||||
onChange={(e) =>
|
||||
handleToggleFolder(folder.id, !e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
{serverPermission >= ServerPermission.ADMIN && (
|
||||
<Button compact variant="subtle">
|
||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
</Group>
|
||||
<Switch
|
||||
checked={folder.enabled}
|
||||
onChange={(e) =>
|
||||
handleToggleFolder(folder.id, !e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</ServerSection>
|
||||
|
||||
<ServerSection title="Credentials">
|
||||
{addCredential ? (
|
||||
<AddServerCredentialForm
|
||||
@ -234,32 +250,30 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
||||
<Stack>
|
||||
{serverCredentials?.map((credential) => (
|
||||
<Group key={credential.id} position="apart">
|
||||
<Text size="sm">{credential.username}</Text>
|
||||
<Group>
|
||||
<Switch
|
||||
checked={credential.enabled}
|
||||
onChange={(e) =>
|
||||
handleToggleCredential(
|
||||
credential.id,
|
||||
!e.currentTarget.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
compact
|
||||
disabled={!permissions.deleteServerCredential}
|
||||
variant="subtle"
|
||||
onClick={() => handleDeleteCredential(credential.id)}
|
||||
>
|
||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||
</Button>
|
||||
<Text size="sm">{credential.username}</Text>
|
||||
</Group>
|
||||
<Switch
|
||||
checked={credential.enabled}
|
||||
onChange={(e) =>
|
||||
handleToggleCredential(
|
||||
credential.id,
|
||||
!e.currentTarget.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
<Button
|
||||
compact
|
||||
disabled={!permissions.createServerCredential}
|
||||
mt={10}
|
||||
variant="subtle"
|
||||
onClick={() => addCredentialHandlers.open()}
|
||||
@ -269,6 +283,7 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
||||
</>
|
||||
)}
|
||||
</ServerSection>
|
||||
|
||||
<ServerSection title="URLs">
|
||||
{addUrl ? (
|
||||
<AddServerUrlForm
|
||||
@ -280,60 +295,75 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
||||
<Stack>
|
||||
{server.serverUrls?.map((serverUrl) => (
|
||||
<Group key={serverUrl.id} position="apart">
|
||||
<Text size="sm">{serverUrl.url}</Text>
|
||||
<Group>
|
||||
<Button
|
||||
compact
|
||||
disabled={!permissions.deleteServerUrl}
|
||||
variant="subtle"
|
||||
onClick={() => handleDeleteUrl(serverUrl.id)}
|
||||
>
|
||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||
</Button>
|
||||
<Text size="sm">{serverUrl.url}</Text>
|
||||
<Switch
|
||||
checked={serverUrl.enabled}
|
||||
onChange={(e) =>
|
||||
handleToggleUrl(
|
||||
serverUrl.id,
|
||||
!e.currentTarget.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
{serverPermission >= ServerPermission.EDITOR && (
|
||||
<Button
|
||||
compact
|
||||
variant="subtle"
|
||||
onClick={() => handleDeleteUrl(serverUrl.id)}
|
||||
>
|
||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
<Switch
|
||||
checked={serverUrl.enabled}
|
||||
onChange={(e) =>
|
||||
handleToggleUrl(serverUrl.id, !e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
<Button
|
||||
compact
|
||||
disabled={!permissions.createServerUrl}
|
||||
mt={10}
|
||||
variant="subtle"
|
||||
onClick={() => addUrlHandlers.open()}
|
||||
>
|
||||
Add url
|
||||
</Button>
|
||||
{serverPermission >= ServerPermission.EDITOR && (
|
||||
<Button
|
||||
compact
|
||||
mt={10}
|
||||
variant="subtle"
|
||||
onClick={() => addUrlHandlers.open()}
|
||||
>
|
||||
Add url
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ServerSection>
|
||||
|
||||
<ServerSection title="Danger zone">
|
||||
<Group position="apart">
|
||||
<Text size="sm">Require user credentials</Text>
|
||||
<Switch
|
||||
checked={server.noCredential}
|
||||
disabled={!permissions.isAdmin}
|
||||
onChange={(e) =>
|
||||
toggleRequiredCredential(e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Divider my="xl" />
|
||||
<Button
|
||||
compact
|
||||
disabled={!permissions.deleteServer}
|
||||
leftIcon={<RiDeleteBin2Line color="var(--danger-color)" />}
|
||||
variant="default"
|
||||
>
|
||||
Delete server
|
||||
</Button>
|
||||
</ServerSection>
|
||||
{serverPermission >= ServerPermission.ADMIN && (
|
||||
<ServerSection title="Danger zone">
|
||||
<Group position="apart">
|
||||
<Text size="sm">Require user credentials</Text>
|
||||
<Switch
|
||||
checked={server.noCredential}
|
||||
onChange={(e) =>
|
||||
toggleRequiredCredential(e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
{permissions.isSuperAdmin && (
|
||||
<>
|
||||
<Divider my="xl" />
|
||||
<Button
|
||||
compact
|
||||
leftIcon={<RiDeleteBin2Line />}
|
||||
size="xs"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
background: 'var(--danger-color)',
|
||||
},
|
||||
background: 'var(--danger-color)',
|
||||
}}
|
||||
>
|
||||
Delete server
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ServerSection>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
@ -1,27 +1,63 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { queryKeys } from '@/renderer/api/query-keys';
|
||||
import { ServerListResponse } from '@/renderer/api/servers.api';
|
||||
import { ServerPermissionType } from '@/renderer/api/types';
|
||||
import { UserDetailResponse } from '@/renderer/api/users.api';
|
||||
import { useAuthStore } from '@/renderer/store';
|
||||
|
||||
export enum ServerPermission {
|
||||
VIEWER = 0,
|
||||
EDITOR = 1,
|
||||
ADMIN = 2,
|
||||
}
|
||||
|
||||
const SERVER_PERMISSION_MAP = {
|
||||
[ServerPermissionType.VIEWER]: 0,
|
||||
[ServerPermissionType.EDITOR]: 1,
|
||||
[ServerPermissionType.ADMIN]: 2,
|
||||
};
|
||||
|
||||
export const usePermissions = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const userId = useAuthStore((state) => state.permissions.id);
|
||||
const permissions = useAuthStore((state) => state.permissions);
|
||||
|
||||
const permissionSet = useMemo(() => {
|
||||
const set = {
|
||||
createServer: permissions.isAdmin,
|
||||
createServerCredential: true,
|
||||
createServerUrl: permissions.isAdmin,
|
||||
deleteServer: permissions.isAdmin,
|
||||
deleteServerCredential: true,
|
||||
deleteServerFolder: permissions.isAdmin,
|
||||
deleteServerUrl: permissions.isAdmin,
|
||||
editServer: permissions.isAdmin,
|
||||
editServerFolder: permissions.isAdmin,
|
||||
isAdmin: permissions.isAdmin,
|
||||
isSuperAdmin: permissions.isSuperAdmin,
|
||||
manageUsers: permissions.isAdmin,
|
||||
};
|
||||
const user = queryClient.getQueryData<UserDetailResponse>(
|
||||
queryKeys.users.detail(userId)
|
||||
);
|
||||
|
||||
return set;
|
||||
}, [permissions]);
|
||||
const servers = queryClient.getQueryData<ServerListResponse>(
|
||||
queryKeys.servers.list()
|
||||
);
|
||||
|
||||
const permissionSet: { [key: string]: any } = useMemo(() => {
|
||||
const serverPermissions: { [key: string]: ServerPermission } = {};
|
||||
|
||||
servers?.data?.forEach((server) => {
|
||||
const permission = user?.data?.serverPermissions?.find(
|
||||
(p) => p.serverId === server.id
|
||||
)?.type;
|
||||
|
||||
serverPermissions[server.id] =
|
||||
permissions.isAdmin || permissions.isSuperAdmin
|
||||
? ServerPermission.ADMIN
|
||||
: permission
|
||||
? SERVER_PERMISSION_MAP[permission]
|
||||
: -1;
|
||||
});
|
||||
|
||||
return {
|
||||
isAdmin: permissions.isAdmin || permissions.isSuperAdmin,
|
||||
isSuperAdmin: permissions.isSuperAdmin,
|
||||
...serverPermissions,
|
||||
};
|
||||
}, [
|
||||
permissions.isAdmin,
|
||||
permissions.isSuperAdmin,
|
||||
servers?.data,
|
||||
user?.data?.serverPermissions,
|
||||
]);
|
||||
|
||||
return permissionSet;
|
||||
};
|
||||
|
@ -1,16 +1,20 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '@/renderer/api';
|
||||
import { queryKeys } from '@/renderer/api/query-keys';
|
||||
import { UserDetailResponse } from '@/renderer/api/users.api';
|
||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||
|
||||
export const useUserDetail = (options: { userId: string }) => {
|
||||
const { data, error, isLoading } = useQuery({
|
||||
queryFn: () => api.users.getUserDetail({ userId: options.userId }),
|
||||
queryKey: queryKeys.users.detail(options.userId),
|
||||
export const useUserDetail = (
|
||||
q: { userId: string },
|
||||
options?: QueryOptions<UserDetailResponse>
|
||||
) => {
|
||||
const query = useQuery({
|
||||
cacheTime: Infinity,
|
||||
queryFn: () => api.users.getUserDetail({ userId: q.userId }),
|
||||
queryKey: queryKeys.users.detail(q.userId),
|
||||
staleTime: 1000 * 60 * 60 * 24,
|
||||
...options,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
isLoading,
|
||||
};
|
||||
return query;
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ export interface AuthState {
|
||||
accessToken: string;
|
||||
currentServer: Server | null;
|
||||
permissions: {
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
isSuperAdmin: boolean;
|
||||
username: string;
|
||||
@ -91,11 +92,17 @@ export const useAuthStore = create<AuthSlice>()(
|
||||
logout: () => {
|
||||
return set({
|
||||
accessToken: undefined,
|
||||
permissions: { isAdmin: false, isSuperAdmin: false, username: '' },
|
||||
permissions: {
|
||||
id: '',
|
||||
isAdmin: false,
|
||||
isSuperAdmin: false,
|
||||
username: '',
|
||||
},
|
||||
refreshToken: undefined,
|
||||
});
|
||||
},
|
||||
permissions: {
|
||||
id: '',
|
||||
isAdmin: false,
|
||||
isSuperAdmin: false,
|
||||
username: '',
|
||||
|
Loading…
Reference in New Issue
Block a user