feat: changed blind index banner for everyone

This commit is contained in:
Akhil Mohan
2024-01-28 23:16:04 +05:30
parent bbf2634e73
commit f010a3a932
16 changed files with 62 additions and 34 deletions
@@ -29,10 +29,7 @@ export const secretBlindIndexServiceFactory = ({
projectId, projectId,
actorId actorId
}: TGetProjectBlindIndexStatusDTO) => { }: TGetProjectBlindIndexStatusDTO) => {
const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); await permissionService.getProjectPermission(actor, actorId, projectId);
if (membership?.role !== ProjectMembershipRole.Admin) {
throw new UnauthorizedError({ message: "User must be admin" });
}
const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId); const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId);
return Number(secretCount); return Number(secretCount);
@@ -25,7 +25,7 @@ export const ProjectPermissionCan: FunctionComponent<Props> = ({
allowedLabel, allowedLabel,
...props ...props
}) => { }) => {
const permission = useProjectPermission(); const { permission } = useProjectPermission();
return ( return (
<Can {...props} passThrough={passThrough} ability={props?.ability || permission}> <Can {...props} passThrough={passThrough} ability={props?.ability || permission}>
{(isAllowed, ability) => { {(isAllowed, ability) => {
@@ -1,6 +1,7 @@
import { createContext, ReactNode, useContext } from "react"; import { createContext, ReactNode, useContext } from "react";
import { useGetUserProjectPermissions } from "@app/hooks/api"; import { useGetUserProjectPermissions } from "@app/hooks/api";
import { TProjectMembership } from "@app/hooks/api/users/types";
import { useWorkspace } from "../WorkspaceContext"; import { useWorkspace } from "../WorkspaceContext";
import { TProjectPermission } from "./types"; import { TProjectPermission } from "./types";
@@ -9,7 +10,10 @@ type Props = {
children: ReactNode; children: ReactNode;
}; };
const ProjectPermissionContext = createContext<null | TProjectPermission>(null); const ProjectPermissionContext = createContext<null | {
permission: TProjectPermission;
membership: TProjectMembership;
}>(null);
export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => { export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => {
const { currentWorkspace, isLoading: isWsLoading } = useWorkspace(); const { currentWorkspace, isLoading: isWsLoading } = useWorkspace();
@@ -21,7 +21,7 @@ export const withProjectPermission = <T extends {}, J extends TProjectPermission
{ action, subject, className, containerClassName }: Props<Generics<J>["abilities"]> { action, subject, className, containerClassName }: Props<Generics<J>["abilities"]>
) => { ) => {
const HOC = (hocProps: T) => { const HOC = (hocProps: T) => {
const permission = useProjectPermission(); const { permission } = useProjectPermission();
// akhilmhdh: Set as any due to casl/react ts type bug // akhilmhdh: Set as any due to casl/react ts type bug
// REASON: casl due to its type checking can't seem to union even if union intersection is applied // REASON: casl due to its type checking can't seem to union even if union intersection is applied
@@ -29,13 +29,13 @@ export const withProjectPermission = <T extends {}, J extends TProjectPermission
return ( return (
<div <div
className={twMerge( className={twMerge(
"container h-full mx-auto flex justify-center items-center", "container mx-auto flex h-full items-center justify-center",
containerClassName containerClassName
)} )}
> >
<div <div
className={twMerge( className={twMerge(
"rounded-md bg-mineshaft-800 text-bunker-300 p-16 flex space-x-12 items-end", "flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300",
className className
)} )}
> >
@@ -43,7 +43,7 @@ export const withProjectPermission = <T extends {}, J extends TProjectPermission
<FontAwesomeIcon icon={faLock} size="6x" /> <FontAwesomeIcon icon={faLock} size="6x" />
</div> </div>
<div> <div>
<div className="text-4xl font-medium mb-2">Permission Denied</div> <div className="mb-2 text-4xl font-medium">Permission Denied</div>
<div className="text-sm"> <div className="text-sm">
You do not have permission to this page. <br /> Kindly contact your organization You do not have permission to this page. <br /> Kindly contact your organization
administrator administrator
+8 -5
View File
@@ -8,7 +8,7 @@ import { apiRequest } from "@app/config/request";
import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types"; import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext/types";
import { OrgUser } from "../users/types"; import { OrgUser, TProjectMembership } from "../users/types";
import { import {
TGetUserOrgPermissionsDTO, TGetUserOrgPermissionsDTO,
TGetUserProjectPermissionDTO, TGetUserProjectPermissionDTO,
@@ -104,10 +104,13 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) =
const getUserProjectPermissions = async ({ workspaceId }: TGetUserProjectPermissionDTO) => { const getUserProjectPermissions = async ({ workspaceId }: TGetUserProjectPermissionDTO) => {
const { data } = await apiRequest.get<{ const { data } = await apiRequest.get<{
data: { permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[] }; data: {
permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[];
membership: TProjectMembership;
};
}>(`/api/v1/workspace/${workspaceId}/permissions`, {}); }>(`/api/v1/workspace/${workspaceId}/permissions`, {});
return data.data.permissions; return data.data;
}; };
export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPermissionDTO) => export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPermissionDTO) =>
@@ -116,8 +119,8 @@ export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPer
queryFn: () => getUserProjectPermissions({ workspaceId }), queryFn: () => getUserProjectPermissions({ workspaceId }),
enabled: Boolean(workspaceId), enabled: Boolean(workspaceId),
select: (data) => { select: (data) => {
const rule = unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(data); const rule = unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(data.permissions);
const ability = createMongoAbility<ProjectPermissionSet>(rule, { conditionsMatcher }); const ability = createMongoAbility<ProjectPermissionSet>(rule, { conditionsMatcher });
return ability; return { permission: ability, membership: data.membership };
} }
}); });
+9
View File
@@ -52,6 +52,15 @@ export type OrgUser = {
roleId: string; roleId: string;
}; };
export type TProjectMembership = {
id: string;
role: string;
createdAt: string;
updatedAt: string;
projectId: string;
roleId: string;
};
export type TWorkspaceUser = OrgUser; export type TWorkspaceUser = OrgUser;
export type AddUserToWsDTO = { export type AddUserToWsDTO = {
@@ -30,7 +30,7 @@ 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 { permission } = useProjectPermission();
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const isEmpty = !isLoading && !cloudIntegrations?.length; const isEmpty = !isLoading && !cloudIntegrations?.length;
@@ -43,7 +43,7 @@ export const CloudIntegrationSection = ({
<h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1> <h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p> <p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
</div> </div>
<div className="mx-6 grid grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-4"> <div className="mx-6 grid grid-cols-2 gap-4 lg:grid-cols-3 2xl:grid-cols-4">
{isLoading && {isLoading &&
Array.from({ length: 12 }).map((_, index) => ( Array.from({ length: 12 }).map((_, index) => (
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} /> <Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />
@@ -44,7 +44,7 @@ export const SecretApprovalPolicyList = ({ workspaceId }: Props) => {
"deletePolicy", "deletePolicy",
"upgradePlan" "upgradePlan"
] as const); ] as const);
const permission = useProjectPermission(); const { permission } = useProjectPermission();
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
@@ -36,7 +36,7 @@ export const SecretApprovalPolicyRow = ({
}: Props) => { }: Props) => {
const [selectedApprovers, setSelectedApprovers] = useState<string[]>([]); const [selectedApprovers, setSelectedApprovers] = useState<string[]>([]);
const { mutate: updateSecretApprovalPolicy, isLoading } = useUpdateSecretApprovalPolicy(); const { mutate: updateSecretApprovalPolicy, isLoading } = useUpdateSecretApprovalPolicy();
const permission = useProjectPermission(); const { permission } = useProjectPermission();
return ( return (
<Tr> <Tr>
@@ -48,7 +48,7 @@ export const SecretMainPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const router = useRouter(); const router = useRouter();
const permission = useProjectPermission(); const { permission } = useProjectPermission();
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
const [sortDir, setSortDir] = useState<SortDir>(SortDir.ASC); const [sortDir, setSortDir] = useState<SortDir>(SortDir.ASC);
@@ -81,7 +81,7 @@ export const SecretDetailSidebar = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
values: secret values: secret
}); });
const permission = useProjectPermission(); const { permission } = useProjectPermission();
const cannotEditSecret = permission.cannot( const cannotEditSecret = permission.cannot(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath }) subject(ProjectPermissionSub.Secrets, { environment, secretPath })
@@ -85,7 +85,7 @@ export const SecretItem = memo(
secretPath secretPath
}: Props) => { }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const permission = useProjectPermission(); const { permission } = useProjectPermission();
const isReadOnly = const isReadOnly =
permission.can( permission.can(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
@@ -4,9 +4,15 @@ import {
decryptSymmetric decryptSymmetric
} from "@app/components/utilities/cryptography/crypto"; } from "@app/components/utilities/cryptography/crypto";
import { Button, Spinner } from "@app/components/v2"; import { Button, Spinner } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { useGetWorkspaceIndexStatus, useNameWorkspaceSecrets } from "@app/hooks/api"; import { useGetWorkspaceIndexStatus, useNameWorkspaceSecrets } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UserWsKeyPair } from "@app/hooks/api/types"; import { UserWsKeyPair } from "@app/hooks/api/types";
import { fetchWorkspaceSecrets } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceSecrets } from "@app/hooks/api/workspace/queries";
@@ -18,6 +24,7 @@ type Props = {
export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => { export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { membership } = useProjectPermission();
const { data: isBlindIndexed, isLoading: isBlindIndexedLoading } = useGetWorkspaceIndexStatus( const { data: isBlindIndexed, isLoading: isBlindIndexedLoading } = useGetWorkspaceIndexStatus(
currentWorkspace?.id ?? "" currentWorkspace?.id ?? ""
); );
@@ -78,13 +85,16 @@ export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => {
<p className="mb-4 leading-7 text-gray-400"> <p className="mb-4 leading-7 text-gray-400">
Your project was created before the introduction of blind indexing. To continue accessing Your project was created before the introduction of blind indexing. To continue accessing
secrets by name through the SDK, public API and web dashboard, please enable blind indexing.{" "} secrets by name through the SDK, public API and web dashboard, please enable blind indexing.{" "}
<b>This is a one time process.</b> <b>
This is a one time process.{" "}
{membership.role !== ProjectMembershipRole.Admin && "Admin only operation"}
</b>
</p> </p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
onClick={onEnableBlindIndices} onClick={onEnableBlindIndices}
isDisabled={!isAllowed} isDisabled={!isAllowed || membership.role !== ProjectMembershipRole.Admin}
color="mineshaft" color="mineshaft"
type="submit" type="submit"
isLoading={isIndexing} isLoading={isIndexing}
@@ -62,7 +62,7 @@ export const SecretRotationPage = withProjectPermission(
() => { () => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { t } = useTranslation(); const { t } = useTranslation();
const permission = useProjectPermission(); const { permission } = useProjectPermission();
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"createRotation", "createRotation",
@@ -22,7 +22,7 @@ export const EnvironmentSection = () => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const permision = useProjectPermission(); const { permission } = useProjectPermission();
const deleteWsEnvironment = useDeleteWsEnvironment(); const deleteWsEnvironment = useDeleteWsEnvironment();
@@ -94,7 +94,7 @@ export const EnvironmentSection = () => {
Choose which environments will show up in your dashboard like development, staging, Choose which environments will show up in your dashboard like development, staging,
production production
</p> </p>
{permision.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? ( {permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? (
<EnvironmentTable handlePopUpOpen={handlePopUpOpen} /> <EnvironmentTable handlePopUpOpen={handlePopUpOpen} />
) : ( ) : (
<PermissionDeniedBanner /> <PermissionDeniedBanner />
@@ -4,7 +4,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions"; import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2"; import { Button, DeleteActionModal } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context"; import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteWsTag } from "@app/hooks/api"; import { useDeleteWsTag } from "@app/hooks/api";
@@ -20,14 +25,14 @@ export const SecretTagsSection = (): JSX.Element => {
"deleteTagConfirmation" "deleteTagConfirmation"
] as const); ] as const);
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const permission = useProjectPermission(); const { permission } = useProjectPermission();
const deleteWsTag = useDeleteWsTag(); const deleteWsTag = useDeleteWsTag();
const onDeleteApproved = async () => { const onDeleteApproved = async () => {
try { try {
await deleteWsTag.mutateAsync({ await deleteWsTag.mutateAsync({
projectId:currentWorkspace?.id || "", projectId: currentWorkspace?.id || "",
tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id
}); });
@@ -47,8 +52,8 @@ export const SecretTagsSection = (): JSX.Element => {
}; };
return ( return (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex justify-between mb-8"> <div className="mb-8 flex justify-between">
<p className="mb-3 text-xl font-semibold">Secret Tags</p> <p className="mb-3 text-xl font-semibold">Secret Tags</p>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}> <ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}>
{(isAllowed) => ( {(isAllowed) => (
@@ -65,7 +70,7 @@ export const SecretTagsSection = (): JSX.Element => {
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
<p className="text-gray-400 mb-8"> <p className="mb-8 text-gray-400">
Every secret can be assigned to one or more tags. Here you can add and remove tags for the Every secret can be assigned to one or more tags. Here you can add and remove tags for the
current project. current project.
</p> </p>