feat(rbac): added frontend permission validation for missing pages and merged new features

This commit is contained in:
Akhil Mohan
2023-09-08 21:22:36 +05:30
parent 4b0bc238fc
commit 7c698e755a
9 changed files with 333 additions and 262 deletions
+22 -16
View File
@@ -138,6 +138,17 @@ export const getRoles = async (req: Request, res: Response) => {
const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId }); const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
const roles = [ const roles = [
...(isOrgRole
? [
{
_id: "owner",
name: "Owner",
slug: "owner",
description: "Complete administration access over the organization.",
permissions: adminPermissions.rules
}
]
: []),
{ {
_id: "admin", _id: "admin",
name: "Admin", name: "Admin",
@@ -152,24 +163,19 @@ export const getRoles = async (req: Request, res: Response) => {
description: "Non-administrative role in an organization", description: "Non-administrative role in an organization",
permissions: isOrgRole ? memberPermissions.rules : adminProjectPermissions.rules permissions: isOrgRole ? memberPermissions.rules : adminProjectPermissions.rules
}, },
{ ...(isOrgRole
_id: "viewer", ? []
name: "Viewer", : [
slug: "viewer", {
description: "Non-administrative role in an organization", _id: "viewer",
permissions: isOrgRole ? viewerProjectPermission.rules : viewerProjectPermission.rules name: "Viewer",
}, slug: "viewer",
description: "Non-administrative role in an organization",
permissions: isOrgRole ? viewerProjectPermission.rules : viewerProjectPermission.rules
}
]),
...customRoles ...customRoles
]; ];
if (isOrgRole) {
roles.unshift({
_id: "owner",
name: "Owner",
slug: "owner",
description: "Complete administration access over the organization.",
permissions: adminPermissions.rules
});
}
res.status(200).json({ res.status(200).json({
message: "Successfully fetched role list", message: "Successfully fetched role list",
@@ -10,7 +10,7 @@ type Props = {
} & BoundCanProps<TProjectPermission>; } & BoundCanProps<TProjectPermission>;
export const ProjectPermissionCan: FunctionComponent<Props> = ({ export const ProjectPermissionCan: FunctionComponent<Props> = ({
label = "Permission Denied. Kindly contact your org admin", label = "Permission Denied. Kindly contact your project admin",
children, children,
passThrough = true, passThrough = true,
...props ...props
@@ -4,7 +4,8 @@ import { useRouter } from "next/router";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { Button, Modal, ModalContent } from "@app/components/v2"; import { Button, Modal, ModalContent } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { withProjectPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { import {
useDeleteIntegration, useDeleteIntegration,
@@ -31,191 +32,194 @@ type Props = {
frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>; frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>;
}; };
export const IntegrationsPage = ({ frameworkIntegrations }: Props) => { export const IntegrationsPage = withProjectPermission(
const { t } = useTranslation(); ({ frameworkIntegrations }: Props) => {
const { createNotification } = useNotificationContext(); const { t } = useTranslation();
const router = useRouter(); const { createNotification } = useNotificationContext();
const router = useRouter();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?._id || ""; const workspaceId = currentWorkspace?._id || "";
const environments = currentWorkspace?.environments || []; const environments = currentWorkspace?.environments || [];
const { data: latestWsKey } = useGetUserWsKey(workspaceId); const { data: latestWsKey } = useGetUserWsKey(workspaceId);
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"activeBot" "activeBot"
] as const); ] as const);
const { data: cloudIntegrations, isLoading: isCloudIntegrationsLoading } = const { data: cloudIntegrations, isLoading: isCloudIntegrationsLoading } =
useGetCloudIntegrations(); useGetCloudIntegrations();
const { data: integrationAuths, isLoading: isIntegrationAuthLoading } = const { data: integrationAuths, isLoading: isIntegrationAuthLoading } =
useGetWorkspaceAuthorizations( useGetWorkspaceAuthorizations(
workspaceId, workspaceId,
useCallback((data: IntegrationAuth[]) => { useCallback((data: IntegrationAuth[]) => {
const groupBy: Record<string, IntegrationAuth> = {}; const groupBy: Record<string, IntegrationAuth> = {};
data.forEach((el) => { data.forEach((el) => {
groupBy[el.integration] = el; groupBy[el.integration] = el;
}); });
return groupBy; return groupBy;
}, []) }, [])
); );
// mutation // mutation
const { const {
data: integrations, data: integrations,
isLoading: isIntegrationLoading, isLoading: isIntegrationLoading,
isFetching: isIntegrationFetching isFetching: isIntegrationFetching
} = useGetWorkspaceIntegrations(workspaceId); } = useGetWorkspaceIntegrations(workspaceId);
const { data: bot } = useGetWorkspaceBot(workspaceId); const { data: bot } = useGetWorkspaceBot(workspaceId);
// mutation // mutation
const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } = const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } =
useUpdateBotActiveStatus(); useUpdateBotActiveStatus();
const { mutateAsync: deleteIntegration } = useDeleteIntegration(); const { mutateAsync: deleteIntegration } = useDeleteIntegration();
const { const {
mutateAsync: deleteIntegrationAuth, mutateAsync: deleteIntegrationAuth,
isLoading: isDeleteIntegrationAuthSuccess, isLoading: isDeleteIntegrationAuthSuccess,
reset: resetDeleteIntegrationAuth reset: resetDeleteIntegrationAuth
} = useDeleteIntegrationAuth(); } = useDeleteIntegrationAuth();
// summary: this use effect is trigger when all integration auths are removed thus deactivate bot // summary: this use effect is trigger when all integration auths are removed thus deactivate bot
// details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched // details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook // After the refetch is completed check if its empty. Then set bot active and reset the submit hook
useEffect(() => { useEffect(() => {
if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) { if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) {
if (bot?._id) if (bot?._id)
updateBotActiveStatusSync({ updateBotActiveStatusSync({
isActive: false, isActive: false,
botId: bot._id, botId: bot._id,
workspaceId workspaceId
}); });
resetDeleteIntegrationAuth(); resetDeleteIntegrationAuth();
}
}, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]);
const handleProviderIntegration = async (provider: string) => {
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
if (!selectedCloudIntegration) return;
try {
if (bot && !bot.isActive) {
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
await updateBotActiveStatus({
workspaceId,
botKey,
isActive: true,
botId: bot._id
});
} }
const integrationAuthForProvider = integrationAuths?.[provider]; }, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]);
if (!integrationAuthForProvider) {
redirectForProviderAuth(selectedCloudIntegration); const handleProviderIntegration = async (provider: string) => {
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
if (!selectedCloudIntegration) return;
try {
if (bot && !bot.isActive) {
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
await updateBotActiveStatus({
workspaceId,
botKey,
isActive: true,
botId: bot._id
});
}
const integrationAuthForProvider = integrationAuths?.[provider];
if (!integrationAuthForProvider) {
redirectForProviderAuth(selectedCloudIntegration);
return;
}
const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id);
router.push(url);
} catch (error) {
console.error(error);
}
};
// function to strat integration for a provider
// confirmation to user passing the bot key for provider to get secret access
const handleProviderIntegrationStart = (provider: string) => {
if (!bot?.isActive) {
handlePopUpOpen("activeBot", { provider });
return; return;
} }
handleProviderIntegration(provider);
};
const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id); const handleUserAcceptBotCondition = () => {
router.push(url); const { provider } = popUp.activeBot?.data as { provider: string };
} catch (error) { handleProviderIntegration(provider);
console.error(error); handlePopUpClose("activeBot");
} };
};
// function to strat integration for a provider const handleIntegrationDelete = async (integrationId: string, cb: () => void) => {
// confirmation to user passing the bot key for provider to get secret access try {
const handleProviderIntegrationStart = (provider: string) => { await deleteIntegration({ id: integrationId, workspaceId });
if (!bot?.isActive) { if (cb) cb();
handlePopUpOpen("activeBot", { provider }); createNotification({
return; type: "success",
} text: "Deleted integration"
handleProviderIntegration(provider); });
}; } catch (err) {
console.log(err);
createNotification({
type: "error",
text: "Failed to delete integration"
});
}
};
const handleUserAcceptBotCondition = () => { const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
const { provider } = popUp.activeBot?.data as { provider: string }; const integrationAuthForProvider = integrationAuths?.[provider];
handleProviderIntegration(provider); if (!integrationAuthForProvider) return;
handlePopUpClose("activeBot"); try {
}; await deleteIntegrationAuth({
id: integrationAuthForProvider._id,
workspaceId
});
if (cb) cb();
createNotification({
type: "success",
text: "Revoked provider authentication"
});
} catch (err) {
console.error(err);
createNotification({
type: "error",
text: "Failed to revoke provider authentication"
});
}
};
const handleIntegrationDelete = async (integrationId: string, cb: () => void) => { return (
try { <div className="container mx-auto max-w-7xl pb-12 text-white">
await deleteIntegration({ id: integrationId, workspaceId }); <IntegrationsSection
if (cb) cb(); isLoading={isIntegrationLoading}
createNotification({ integrations={integrations}
type: "success", environments={environments}
text: "Deleted integration" onIntegrationDelete={({ _id: id }, cb) => handleIntegrationDelete(id, cb)}
}); />
} catch (err) { <CloudIntegrationSection
console.log(err); isLoading={isCloudIntegrationsLoading || isIntegrationAuthLoading}
createNotification({ cloudIntegrations={cloudIntegrations}
type: "error", integrationAuths={integrationAuths}
text: "Failed to delete integration" onIntegrationStart={handleProviderIntegrationStart}
}); onIntegrationRevoke={handleIntegrationAuthRevoke}
} />
}; <Modal
isOpen={popUp.activeBot?.isOpen}
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => { onOpenChange={(isOpen) => handlePopUpToggle("activeBot", isOpen)}
const integrationAuthForProvider = integrationAuths?.[provider];
if (!integrationAuthForProvider) return;
try {
await deleteIntegrationAuth({
id: integrationAuthForProvider._id,
workspaceId
});
if (cb) cb();
createNotification({
type: "success",
text: "Revoked provider authentication"
});
} catch (err) {
console.error(err);
createNotification({
type: "error",
text: "Failed to revoke provider authentication"
});
}
};
return (
<div className="container mx-auto max-w-7xl pb-12 text-white">
<IntegrationsSection
isLoading={isIntegrationLoading}
integrations={integrations}
environments={environments}
onIntegrationDelete={({ _id: id }, cb) => handleIntegrationDelete(id, cb)}
/>
<CloudIntegrationSection
isLoading={isCloudIntegrationsLoading || isIntegrationAuthLoading}
cloudIntegrations={cloudIntegrations}
integrationAuths={integrationAuths}
onIntegrationStart={handleProviderIntegrationStart}
onIntegrationRevoke={handleIntegrationAuthRevoke}
/>
<Modal
isOpen={popUp.activeBot?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("activeBot", isOpen)}
>
<ModalContent
title={t("integrations.grant-access-to-secrets") as string}
footerContent={
<div className="flex items-center space-x-2">
<Button onClick={() => handleUserAcceptBotCondition()}>
{t("integrations.grant-access-button") as string}
</Button>
<Button
onClick={() => handlePopUpClose("activeBot")}
variant="outline_bg"
colorSchema="secondary"
>
Cancel
</Button>
</div>
}
> >
{t("integrations.why-infisical-needs-access")} <ModalContent
</ModalContent> title={t("integrations.grant-access-to-secrets") as string}
</Modal> footerContent={
<FrameworkIntegrationSection frameworks={frameworkIntegrations} /> <div className="flex items-center space-x-2">
</div> <Button onClick={() => handleUserAcceptBotCondition()}>
); {t("integrations.grant-access-button") as string}
}; </Button>
<Button
onClick={() => handlePopUpClose("activeBot")}
variant="outline_bg"
colorSchema="secondary"
>
Cancel
</Button>
</div>
}
>
{t("integrations.why-infisical-needs-access")}
</ModalContent>
</Modal>
<FrameworkIntegrationSection frameworks={frameworkIntegrations} />
</div>
);
},
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Integrations }
);
@@ -2,7 +2,9 @@ import { useTranslation } from "react-i18next";
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DeleteActionModal,Skeleton, Tooltip } from "@app/components/v2"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { DeleteActionModal, Skeleton, Tooltip } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types"; import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types";
@@ -28,11 +30,13 @@ export const CloudIntegrationSection = ({
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteConfirmation" "deleteConfirmation"
] as const); ] as const);
const permission = useProjectPermission();
const { createNotification } = useNotificationContext();
const isEmpty = !isLoading && !cloudIntegrations?.length; const isEmpty = !isLoading && !cloudIntegrations?.length;
const sortedCloudIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name)); const sortedCloudIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name));
return ( return (
<div> <div>
<div className="m-4 mt-7 flex max-w-5xl flex-col items-start justify-between px-2 text-xl"> <div className="m-4 mt-7 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
@@ -57,6 +61,18 @@ export const CloudIntegrationSection = ({
} flex h-32 flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4`} } flex h-32 flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4`}
onClick={() => { onClick={() => {
if (!cloudIntegration.isAvailable) return; if (!cloudIntegration.isAvailable) return;
if (
permission.cannot(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
)
) {
createNotification({
type: "error",
text: "Permission Denied. Kindly contact your project admin"
});
return;
}
onIntegrationStart(cloudIntegration.slug); onIntegrationStart(cloudIntegration.slug);
}} }}
key={cloudIntegration.slug} key={cloudIntegration.slug}
@@ -2,6 +2,7 @@ import { faArrowRight, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { integrationSlugNameMapping } from "public/data/frequentConstants"; import { integrationSlugNameMapping } from "public/data/frequentConstants";
import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
DeleteActionModal, DeleteActionModal,
EmptyState, EmptyState,
@@ -13,6 +14,7 @@ import {
Skeleton, Skeleton,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { TIntegration } from "@app/hooks/api/types"; import { TIntegration } from "@app/hooks/api/types";
@@ -125,18 +127,26 @@ export const IntegrationsSection = ({
)} )}
</div> </div>
<div className="flex cursor-default items-center"> <div className="flex cursor-default items-center">
<div className="ml-2 opacity-80 duration-200 hover:opacity-100"> <ProjectPermissionCan
<Tooltip content="Remove Integration"> I={ProjectPermissionActions.Delete}
<IconButton a={ProjectPermissionSub.Integrations}
onClick={() => handlePopUpOpen("deleteConfirmation", integration)} >
ariaLabel="delete" {(isAllowed) => (
colorSchema="danger" <div className="ml-2 opacity-80 duration-200 hover:opacity-100">
variant="star" <Tooltip content="Remove Integration">
> <IconButton
<FontAwesomeIcon icon={faXmark} className="px-0.5" /> onClick={() => handlePopUpOpen("deleteConfirmation", integration)}
</IconButton> ariaLabel="delete"
</Tooltip> isDisabled={!isAllowed}
</div> colorSchema="danger"
variant="star"
>
<FontAwesomeIcon icon={faXmark} className="px-0.5" />
</IconButton>
</Tooltip>
</div>
)}
</ProjectPermissionCan>
</div> </div>
</div> </div>
))} ))}
@@ -42,17 +42,19 @@ export const AutoCapitalizationSection = withProjectPermission(
<p className="mb-3 text-xl font-semibold">{t("settings.project.auto-capitalization")}</p> <p className="mb-3 text-xl font-semibold">{t("settings.project.auto-capitalization")}</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => ( {(isAllowed) => (
<Checkbox <div className="w-max">
className="data-[state=checked]:bg-primary" <Checkbox
id="autoCapitalization" className="data-[state=checked]:bg-primary"
isDisabled={!isAllowed} id="autoCapitalization"
isChecked={currentWorkspace?.autoCapitalization ?? false} isDisabled={!isAllowed}
onCheckedChange={(state) => { isChecked={currentWorkspace?.autoCapitalization ?? false}
handleToggleCapitalizationToggle(state as boolean); onCheckedChange={(state) => {
}} handleToggleCapitalizationToggle(state as boolean);
> }}
{t("settings.project.auto-capitalization-description")} >
</Checkbox> {t("settings.project.auto-capitalization-description")}
</Checkbox>
</div>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
@@ -90,17 +90,19 @@ export const E2EESection = withProjectPermission(
</p> </p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => ( {(isAllowed) => (
<Checkbox <div className="w-max">
className="data-[state=checked]:bg-primary" <Checkbox
id="autoCapitalization" className="data-[state=checked]:bg-primary"
isChecked={!bot.isActive} id="autoCapitalization"
isDisabled={!isAllowed} isChecked={!bot.isActive}
onCheckedChange={async () => { isDisabled={!isAllowed}
await toggleBotActivate(); onCheckedChange={async () => {
}} await toggleBotActivate();
> }}
End-to-end encryption enabled >
</Checkbox> End-to-end encryption enabled
</Checkbox>
</div>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
@@ -1,4 +1,4 @@
import { faArrowDown,faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faArrowDown, faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@@ -16,9 +16,7 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { import { useReorderWsEnvironment } from "@app/hooks/api";
useReorderWsEnvironment
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = { type Props = {
@@ -39,18 +37,23 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const reorderWsEnvironment = useReorderWsEnvironment(); const reorderWsEnvironment = useReorderWsEnvironment();
const handleReorderEnv= async (shouldMoveUp: boolean, name: string, slug: string) => { const handleReorderEnv = async (shouldMoveUp: boolean, name: string, slug: string) => {
try { try {
if (!currentWorkspace?._id) return; if (!currentWorkspace?._id) return;
const indexOfEnv = currentWorkspace.environments.findIndex((env) => env.name === name && env.slug === slug); const indexOfEnv = currentWorkspace.environments.findIndex(
(env) => env.name === name && env.slug === slug
);
// check that this reordering is possible // check that this reordering is possible
if (indexOfEnv === 0 && shouldMoveUp || indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp) { if (
return (indexOfEnv === 0 && shouldMoveUp) ||
(indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp)
) {
return;
} }
const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1 const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1;
await reorderWsEnvironment.mutateAsync({ await reorderWsEnvironment.mutateAsync({
workspaceID: currentWorkspace._id, workspaceID: currentWorkspace._id,
@@ -92,31 +95,48 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
<Td>{name}</Td> <Td>{name}</Td>
<Td>{slug}</Td> <Td>{slug}</Td>
<Td className="flex items-center justify-end"> <Td className="flex items-center justify-end">
<IconButton <ProjectPermissionCan
className="mr-3 py-2" I={ProjectPermissionActions.Edit}
onClick={() => { a={ProjectPermissionSub.Environments}
handleReorderEnv(false, name, slug)
}}
colorSchema="primary"
variant="plain"
ariaLabel="update"
isDisabled={pos === currentWorkspace.environments.length - 1}
> >
<FontAwesomeIcon icon={faArrowDown} /> {(isAllowed) => (
</IconButton> <IconButton
<IconButton className="mr-3 py-2"
className="mr-3 py-2" onClick={() => {
onClick={() => { handleReorderEnv(false, name, slug);
handleReorderEnv(true, name, slug) }}
}} colorSchema="primary"
colorSchema="primary" variant="plain"
variant="plain" ariaLabel="update"
ariaLabel="update" isDisabled={pos === currentWorkspace.environments.length - 1 || !isAllowed}
isDisabled={pos === 0} >
<FontAwesomeIcon icon={faArrowDown} />
</IconButton>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Environments}
> >
<FontAwesomeIcon icon={faArrowUp} /> {(isAllowed) => (
</IconButton> <IconButton
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Environments} className="mr-3 py-2"
onClick={() => {
handleReorderEnv(true, name, slug);
}}
colorSchema="primary"
variant="plain"
ariaLabel="update"
isDisabled={pos === 0 || !isAllowed}
>
<FontAwesomeIcon icon={faArrowUp} />
</IconButton>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Environments}
> >
{(isAllowed) => ( {(isAllowed) => (
<IconButton <IconButton
@@ -1,3 +1,4 @@
import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
decryptAssymmetric, decryptAssymmetric,
decryptSymmetric decryptSymmetric
@@ -24,7 +25,7 @@ export const ProjectIndexSecretsSection = withProjectPermission(
const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? ""); const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
const { data: encryptedSecrets } = useGetWorkspaceSecrets(currentWorkspace?._id ?? ""); const { data: encryptedSecrets } = useGetWorkspaceSecrets(currentWorkspace?._id ?? "");
const nameWorkspaceSecrets = useNameWorkspaceSecrets(); const nameWorkspaceSecrets = useNameWorkspaceSecrets();
const onEnableBlindIndices = async () => { const onEnableBlindIndices = async () => {
if (!currentWorkspace?._id) return; if (!currentWorkspace?._id) return;
if (!encryptedSecrets) return; if (!encryptedSecrets) return;
@@ -65,9 +66,19 @@ export const ProjectIndexSecretsSection = withProjectPermission(
secrets. To access individual secrets by name through the SDK and public API, please secrets. To access individual secrets by name through the SDK and public API, please
enable blind indexing. enable blind indexing.
</p> </p>
<Button onClick={onEnableBlindIndices} color="mineshaft" size="sm" type="submit"> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
Enable Blind Indexing {(isAllowed) => (
</Button> <Button
onClick={onEnableBlindIndices}
isDisabled={!isAllowed}
color="mineshaft"
size="sm"
type="submit"
>
Enable Blind Indexing
</Button>
)}
</ProjectPermissionCan>
</div> </div>
) : ( ) : (
<div /> <div />