Merge branch 'main' of https://github.com/Infisical/infisical into feat/adds-GETtokenAuthTokenById-api-endpoint

This commit is contained in:
Piyush Gupta
2025-11-17 20:22:09 +05:30
422 changed files with 15873 additions and 4805 deletions
@@ -67,7 +67,8 @@ export const createNotification = (
...toastProps,
autoClose: toastProps.autoClose || 15000,
theme: "dark",
type: myProps?.type || "info"
type: myProps?.type || "info",
className: `pointer-events-auto ${toastProps.className}`
});
export const NotificationContainer = () => (
+36 -30
View File
@@ -9,7 +9,7 @@ import { cva, type VariantProps } from "cva";
import { twMerge } from "tailwind-merge";
const alertVariants = cva(
"w-full bg-mineshaft-800 rounded-lg border border-bunker-400 px-4 py-3 text-sm flex items-center gap-x-4",
"w-full bg-mineshaft-800 rounded-lg border border-bunker-400 px-4 py-3 text-sm flex items-center gap-x-3",
{
variants: {
variant: {
@@ -28,6 +28,7 @@ type AlertProps = {
title?: string;
hideTitle?: boolean;
icon?: React.ReactNode;
iconClassName?: string;
};
const variantTitleMap = {
@@ -45,36 +46,41 @@ const variantIconMap = {
const Alert = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> & AlertProps
>(({ className, variant, title, icon, hideTitle = false, children, ...props }, ref) => {
const defaultTitle = title ?? variantTitleMap[variant ?? "default"];
return (
<div
ref={ref}
role="alert"
className={twMerge(alertVariants({ variant }), className)}
{...props}
>
<div>
{typeof icon !== "undefined" ? (
<>{icon} </>
) : (
<FontAwesomeIcon
className="text-lg text-primary"
icon={variantIconMap[variant ?? "default"]}
/>
)}
>(
(
{ className, variant, title, icon, hideTitle = false, children, iconClassName, ...props },
ref
) => {
const defaultTitle = title ?? variantTitleMap[variant ?? "default"];
return (
<div
ref={ref}
role="alert"
className={twMerge(alertVariants({ variant }), className)}
{...props}
>
<div>
{typeof icon !== "undefined" ? (
<>{icon} </>
) : (
<FontAwesomeIcon
className={twMerge("text-lg text-primary", iconClassName)}
icon={variantIconMap[variant ?? "default"]}
/>
)}
</div>
<div className="flex flex-col gap-y-1">
{hideTitle ? null : (
<h5 className="leading-6 font-medium tracking-tight" {...props}>
{defaultTitle}
</h5>
)}
{children}
</div>
</div>
<div className="flex flex-col gap-y-1">
{hideTitle ? null : (
<h5 className="leading-6 font-medium tracking-tight" {...props}>
{defaultTitle}
</h5>
)}
{children}
</div>
</div>
);
});
);
}
);
Alert.displayName = "Alert";
const AlertDescription = forwardRef<
@@ -55,6 +55,19 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
{...props}
ref={forwardedRef}
className={twMerge(drawerContentVariation({ direction, className }))}
onPointerDownOutside={(e) => {
const target = e.target as HTMLElement;
const toastElement = target.closest('[class*="Toastify"]');
if (toastElement) {
e.preventDefault();
return;
}
if (onClose) {
onClose();
}
props.onPointerDownOutside?.(e);
}}
>
<Card isRounded={false} className="dark h-full w-full">
{title && (
@@ -2,10 +2,15 @@ import { forwardRef, TextareaHTMLAttributes, useCallback, useMemo, useRef, useSt
import { faFolder, faKey, faLayerGroup, faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Popover from "@radix-ui/react-popover";
import { useNavigate } from "@tanstack/react-router";
import { useProject } from "@app/context";
import { createNotification } from "@app/components/notifications";
import { ROUTE_PATHS } from "@app/const/routes";
import { useProject, useProjectPermission } from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { useDebounce, useToggle } from "@app/hooks";
import { useGetProjectFolders, useGetProjectSecrets } from "@app/hooks/api";
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
import { SecretInput } from "../SecretInput";
@@ -80,6 +85,8 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
) => {
const { currentProject } = useProject();
const projectId = currentProject?.id || "";
const navigate = useNavigate({ from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path });
const { permission } = useProjectPermission();
const [debouncedValue] = useDebounce(value, 100);
@@ -307,6 +314,120 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
}
}, []);
const handleClickSegment = useCallback(
(segment: string, allSegments: string[]) => {
if (!projectId) {
createNotification({
text: "Project ID is not set",
type: "error"
});
return;
}
if (allSegments.length === 0) {
createNotification({
text: "Invalid secret reference",
type: "error"
});
return;
}
if (allSegments.length === 1) {
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
permission,
ProjectPermissionSecretActions.ReadValue,
{
environment: propEnvironment ?? "*",
secretPath: propSecretPath ?? "/",
secretName: segment,
secretTags: ["*"]
}
);
if (!canReadSecretValue) {
createNotification({
text: "You do not have permission to access this secret",
type: "error"
});
return;
}
navigate({
search: (prev) => ({
...prev,
search: segment,
filterBy: "secret",
tags: ""
})
});
return;
}
const environmentSlug = allSegments[0];
const secretName = allSegments[allSegments.length - 1];
let folderPath = "/";
if (allSegments.length > 2) {
const pathSegments = allSegments.slice(1, -1);
for (let i = 0; i < pathSegments.length; i += 1) {
if (!pathSegments[i]) {
createNotification({
text: "Invalid secret reference",
type: "error"
});
return;
}
const pathSegment = pathSegments[i];
folderPath += `${pathSegment}`;
if (pathSegment === segment) {
folderPath += "/";
break;
}
folderPath += "/";
}
}
// Only validate secret permission, users can always view environments and folders
if (segment === secretName) {
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
permission,
ProjectPermissionSecretActions.ReadValue,
{
environment: environmentSlug,
secretPath: folderPath,
secretName,
secretTags: ["*"]
}
);
if (!canReadSecretValue) {
createNotification({
text: "You do not have permission to access this secret",
type: "error"
});
return;
}
}
navigate({
to: ROUTE_PATHS.SecretManager.SecretDashboardPage.path,
params: {
projectId,
envSlug: environmentSlug
},
search: (prev) => ({
...prev,
secretPath: segment === environmentSlug ? "/" : folderPath,
search: segment === secretName ? secretName : prev.search,
filterBy: segment === secretName ? "secret" : prev.filterBy,
tags: ""
})
});
},
[navigate, projectId, permission, propEnvironment, propSecretPath]
);
return (
<Popover.Root open={isPopupOpen} onOpenChange={handlePopUpOpen}>
<Popover.Trigger asChild>
@@ -329,6 +450,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
}}
onChange={(e) => onChange?.(e.target.value)}
containerClassName={containerClassName}
onClickSegment={handleClickSegment}
/>
</Popover.Trigger>
<Popover.Content
+19 -1
View File
@@ -15,6 +15,7 @@ export type ModalContentProps = Omit<DialogPrimitive.DialogContentProps, "title"
onClose?: () => void;
overlayClassName?: string;
showCloseButton?: boolean;
closeOnOutsideClick?: boolean;
};
export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
@@ -29,6 +30,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
bodyClassName,
onClose,
showCloseButton = true,
closeOnOutsideClick = true,
...props
},
forwardedRef
@@ -38,7 +40,23 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
className={twMerge("animate-fade-in fixed inset-0 z-30 h-full w-full", overlayClassName)}
style={{ backgroundColor: "rgba(0, 0, 0, 0.7)" }}
/>
<DialogPrimitive.Content {...props} ref={forwardedRef}>
<DialogPrimitive.Content
{...props}
ref={forwardedRef}
onPointerDownOutside={(e) => {
const target = e.target as HTMLElement;
const toastElement = target.closest('[class*="Toastify"]');
if (toastElement) {
e.preventDefault();
return;
}
if (closeOnOutsideClick && onClose) {
onClose();
}
props.onPointerDownOutside?.(e);
}}
>
<Card
isRounded
className={twMerge(
@@ -1,11 +1,11 @@
/* eslint-disable react/no-danger */
import { forwardRef, TextareaHTMLAttributes } from "react";
import { forwardRef, TextareaHTMLAttributes, useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { useToggle } from "@app/hooks";
import { HIDDEN_SECRET_VALUE } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem";
const REGEX = /(\${([a-zA-Z0-9-_.]+)})/g;
const REGEX = /(\${([a-zA-Z0-9-_. ]+)})/g;
const syntaxHighlight = (
content?: string | null,
@@ -13,6 +13,10 @@ const syntaxHighlight = (
isImport?: boolean,
isLoadingValue?: boolean,
isErrorLoadingValue?: boolean,
onHoverPart?: (part: string) => void,
hoveredPart?: string,
isCmdOrCtrlPressed?: boolean,
onClickSegment?: (segment: string, allSegments: string[]) => void,
placeholder?: string
) => {
if (isLoadingValue) return HIDDEN_SECRET_VALUE;
@@ -29,10 +33,57 @@ const syntaxHighlight = (
const isInterpolationSyntax = el.startsWith("${") && el.endsWith("}");
if (isInterpolationSyntax) {
skipNext = true;
const part = el;
const innerContent = el.slice(2, -1); // Remove ${ and }
const parts = innerContent.split(".");
return (
<span className="ph-no-capture text-yellow" key={`secret-value-${i + 1}`}>
<span className="ph-no-capture relative z-10 text-yellow" key={`secret-value-${i + 1}`}>
&#36;&#123;
<span className="ph-no-capture text-yellow-200/80">{el.slice(2, -1)}</span>
{parts.map((segment, segmentIndex) => {
const segmentKey = `${part}-segment-${segmentIndex}`;
const isHovered = hoveredPart === segmentKey;
const shouldShowHoverStyle = isHovered && isCmdOrCtrlPressed;
return (
<span key={segmentKey}>
<span
role="button"
tabIndex={isCmdOrCtrlPressed ? 0 : -1}
className={`ph-no-capture text-yellow-200/80 ${
isCmdOrCtrlPressed ? "pointer-events-auto" : "pointer-events-none"
} ${shouldShowHoverStyle ? "cursor-pointer underline decoration-yellow-400" : ""}`}
onMouseEnter={() => onHoverPart?.(segmentKey)}
onMouseLeave={() => onHoverPart?.("")}
onMouseDown={(e) => {
if (isCmdOrCtrlPressed) {
e.preventDefault();
e.stopPropagation();
}
}}
onClick={(e) => {
e.stopPropagation();
if (isCmdOrCtrlPressed) {
e.preventDefault();
onClickSegment?.(segment, parts);
}
}}
onKeyDown={(e) => {
if (isCmdOrCtrlPressed && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
e.stopPropagation();
onClickSegment?.(segment, parts);
}
}}
>
{segment}
</span>
{segmentIndex < parts.length - 1 && (
<span className="ph-no-capture pointer-events-none text-yellow-200/80">.</span>
)}
</span>
);
})}
&#125;
</span>
);
@@ -62,6 +113,7 @@ type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
canEditButNotView?: boolean;
isLoadingValue?: boolean;
isErrorLoadingValue?: boolean;
onClickSegment?: (segment: string, allSegments: string[]) => void;
};
const commonClassName = "font-mono text-sm caret-white border-none outline-hidden w-full break-all";
@@ -81,12 +133,43 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
canEditButNotView,
isLoadingValue,
isErrorLoadingValue,
onClickSegment,
placeholder,
...props
},
ref
) => {
const [isSecretFocused, setIsSecretFocused] = useToggle();
const [hoveredPart, setHoveredPart] = useState<string | undefined>();
const [isCmdOrCtrlPressed, setIsCmdOrCtrlPressed] = useState(false);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.metaKey || e.ctrlKey) {
setIsCmdOrCtrlPressed(true);
}
};
const handleKeyUp = (e: KeyboardEvent) => {
if (!e.metaKey && !e.ctrlKey) {
setIsCmdOrCtrlPressed(false);
}
};
const handleBlur = () => {
setIsCmdOrCtrlPressed(false);
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
window.addEventListener("blur", handleBlur);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
window.removeEventListener("blur", handleBlur);
};
}, []);
return (
<div
@@ -94,7 +177,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
style={{ maxHeight: `${21 * 7}px` }}
>
<div className="relative overflow-hidden">
<pre aria-hidden className="m-0">
<pre aria-hidden className="pointer-events-none relative z-10 m-0">
<code className={`inline-block w-full ${commonClassName}`}>
<span
className={twMerge(
@@ -108,6 +191,12 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
isImport,
isLoadingValue,
isErrorLoadingValue,
(part) => {
setHoveredPart(part);
},
hoveredPart,
isCmdOrCtrlPressed,
onClickSegment,
placeholder
)}
</span>
@@ -138,6 +227,9 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
onBlur?.(evt);
setIsSecretFocused.off();
}}
onMouseLeave={() => {
setHoveredPart(undefined);
}}
value={value || ""}
{...props}
readOnly={isReadOnly || isLoadingValue || isErrorLoadingValue}
-3
View File
@@ -26,9 +26,6 @@ export const envConfig = {
import.meta.env.VITE_TELEMETRY_CAPTURING_ENABLED === true
);
},
get ACME_FEATURE_ENABLED() {
return window?.__INFISICAL_RUNTIME_ENV__?.ACME_FEATURE_ENABLED ?? false;
},
get PLATFORM_VERSION() {
return import.meta.env.VITE_INFISICAL_PLATFORM_VERSION;
@@ -78,7 +78,11 @@ export enum ProjectPermissionIdentityActions {
Edit = "edit",
Delete = "delete",
GrantPrivileges = "grant-privileges",
AssumePrivileges = "assume-privileges"
AssumePrivileges = "assume-privileges",
RevokeAuth = "revoke-auth",
CreateToken = "create-token",
GetToken = "get-token",
DeleteToken = "delete-token"
}
export enum ProjectPermissionMemberActions {
-1
View File
@@ -7,7 +7,6 @@ declare global {
POSTHOG_API_KEY?: string;
INTERCOM_ID?: string;
TELEMETRY_CAPTURING_ENABLED: string;
ACME_FEATURE_ENABLED?: boolean;
};
}
}
@@ -251,6 +251,17 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.UPDATE_ORG_ROLE]: "Update Org Role",
[EventType.DELETE_ORG_ROLE]: "Delete Org Role",
[EventType.CREATE_SUB_ORGANIZATION]: "Create Sub Organization",
[EventType.UPDATE_SUB_ORGANIZATION]: "Update Sub Organization",
[EventType.CREATE_IDENTITY_ORG_MEMBERSHIP]: "Create Identity Org Membership",
[EventType.UPDATE_IDENTITY_ORG_MEMBERSHIP]: "Update Identity Org Membership",
[EventType.DELETE_IDENTITY_ORG_MEMBERSHIP]: "Delete Identity Org Membership",
[EventType.CREATE_IDENTITY_PROJECT_MEMBERSHIP]: "Create Identity Project Membership",
[EventType.UPDATE_IDENTITY_PROJECT_MEMBERSHIP]: "Update Identity Project Membership",
[EventType.DELETE_IDENTITY_PROJECT_MEMBERSHIP]: "Delete Identity Project Membership",
[EventType.PAM_SESSION_START]: "PAM Session Start",
[EventType.PAM_SESSION_LOGS_UPDATE]: "PAM Session Logs Update",
[EventType.PAM_SESSION_END]: "PAM Session End",
@@ -243,6 +243,17 @@ export enum EventType {
UPDATE_ORG_ROLE = "update-org-role",
DELETE_ORG_ROLE = "delete-org-role",
CREATE_SUB_ORGANIZATION = "create-sub-organization",
UPDATE_SUB_ORGANIZATION = "update-sub-organization",
CREATE_IDENTITY_ORG_MEMBERSHIP = "create-identity-org-membership",
UPDATE_IDENTITY_ORG_MEMBERSHIP = "update-identity-org-membership",
DELETE_IDENTITY_ORG_MEMBERSHIP = "delete-identity-org-membership",
CREATE_IDENTITY_PROJECT_MEMBERSHIP = "create-identity-project-membership",
UPDATE_IDENTITY_PROJECT_MEMBERSHIP = "update-identity-project-membership",
DELETE_IDENTITY_PROJECT_MEMBERSHIP = "delete-identity-project-membership",
PAM_SESSION_START = "pam-session-start",
PAM_SESSION_LOGS_UPDATE = "pam-session-logs-update",
PAM_SESSION_END = "pam-session-end",
@@ -1,6 +1,7 @@
export { CertStatus } from "./enums";
export {
useDeleteCert,
useDownloadCertPkcs12,
useImportCertificate,
useRenewCertificate,
useRevokeCert,
@@ -7,6 +7,7 @@ import { projectKeys } from "../projects";
import {
TCertificate,
TDeleteCertDTO,
TDownloadPkcs12DTO,
TImportCertificateDTO,
TImportCertificateResponse,
TRenewCertificateDTO,
@@ -134,3 +135,42 @@ export const useUpdateRenewalConfig = () => {
}
});
};
export const useDownloadCertPkcs12 = () => {
return useMutation<void, object, TDownloadPkcs12DTO>({
mutationFn: async ({ serialNumber, projectSlug, password, alias }) => {
try {
const response = await apiRequest.post(
`/api/v1/pki/certificates/${serialNumber}/pkcs12`,
{
password,
alias
},
{
params: { projectSlug },
responseType: "arraybuffer"
}
);
// Create blob and trigger download
const blob = new Blob([response.data], { type: "application/octet-stream" });
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `certificate-${serialNumber}.p12`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error: any) {
if (error.response?.data instanceof ArrayBuffer) {
const decoder = new TextDecoder();
const errorText = decoder.decode(error.response.data);
const errorData = JSON.parse(errorText);
throw new Error(errorData.message);
}
throw error;
}
}
});
};
@@ -72,3 +72,10 @@ export type TUpdateRenewalConfigDTO = {
enableAutoRenewal?: boolean;
projectSlug: string;
};
export type TDownloadPkcs12DTO = {
serialNumber: string;
projectSlug: string;
password: string;
alias: string;
};
+505 -216
View File
@@ -1,9 +1,9 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { projectIdentityQuery, projectKeys } from "@app/hooks/api";
import { organizationKeys } from "../organization/queries";
import { subscriptionQueryKeys } from "../subscriptions/queries";
import { identitiesKeys } from "./queries";
import {
AddIdentityAliCloudAuthDTO,
@@ -21,7 +21,6 @@ import {
ClearIdentityLdapAuthLockoutsDTO,
ClearIdentityUniversalAuthLockoutsDTO,
ClientSecretData,
CreateIdentityDTO,
CreateIdentityUniversalAuthClientSecretDTO,
CreateIdentityUniversalAuthClientSecretRes,
CreateTokenIdentityTokenAuthDTO,
@@ -29,7 +28,6 @@ import {
DeleteIdentityAliCloudAuthDTO,
DeleteIdentityAwsAuthDTO,
DeleteIdentityAzureAuthDTO,
DeleteIdentityDTO,
DeleteIdentityGcpAuthDTO,
DeleteIdentityJwtAuthDTO,
DeleteIdentityKubernetesAuthDTO,
@@ -40,7 +38,6 @@ import {
DeleteIdentityTokenAuthDTO,
DeleteIdentityUniversalAuthClientSecretDTO,
DeleteIdentityUniversalAuthDTO,
Identity,
IdentityAccessToken,
IdentityAliCloudAuth,
IdentityAwsAuth,
@@ -59,7 +56,6 @@ import {
UpdateIdentityAliCloudAuthDTO,
UpdateIdentityAwsAuthDTO,
UpdateIdentityAzureAuthDTO,
UpdateIdentityDTO,
UpdateIdentityGcpAuthDTO,
UpdateIdentityJwtAuthDTO,
UpdateIdentityKubernetesAuthDTO,
@@ -72,73 +68,6 @@ import {
UpdateTokenIdentityTokenAuthDTO
} from "./types";
export const useCreateIdentity = () => {
const queryClient = useQueryClient();
return useMutation<Identity, object, CreateIdentityDTO>({
mutationFn: async (body) => {
const {
data: { identity }
} = await apiRequest.post("/api/v1/identities/", body);
return identity;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
export const useUpdateIdentity = () => {
const queryClient = useQueryClient();
return useMutation<Identity, object, UpdateIdentityDTO>({
mutationFn: async ({ identityId, name, role, hasDeleteProtection, metadata }) => {
const {
data: { identity }
} = await apiRequest.patch(`/api/v1/identities/${identityId}`, {
name,
role,
hasDeleteProtection,
metadata
});
return identity;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
export const useDeleteIdentity = () => {
const queryClient = useQueryClient();
return useMutation<Identity, object, DeleteIdentityDTO>({
mutationFn: async ({ identityId }) => {
const {
data: { identity }
} = await apiRequest.delete(`/api/v1/identities/${identityId}`);
return identity;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
// TODO: move these to /auth
export const useAddIdentityUniversalAuth = () => {
@@ -171,10 +100,20 @@ export const useAddIdentityUniversalAuth = () => {
});
return identityUniversalAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId)
@@ -215,10 +154,20 @@ export const useUpdateIdentityUniversalAuth = () => {
});
return identityUniversalAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId)
@@ -236,10 +185,20 @@ export const useDeleteIdentityUniversalAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/universal-auth/identities/${identityId}`);
return identityUniversalAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId)
@@ -344,10 +303,20 @@ export const useAddIdentityGcpAuth = () => {
return identityGcpAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityGcpAuth(identityId) });
}
@@ -386,10 +355,20 @@ export const useUpdateIdentityGcpAuth = () => {
return identityGcpAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityGcpAuth(identityId) });
}
@@ -405,10 +384,20 @@ export const useDeleteIdentityGcpAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/gcp-auth/identities/${identityId}`);
return identityGcpAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityGcpAuth(identityId) });
}
@@ -445,10 +434,20 @@ export const useAddIdentityAwsAuth = () => {
return identityAwsAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAwsAuth(identityId) });
}
@@ -485,10 +484,20 @@ export const useUpdateIdentityAwsAuth = () => {
return identityAwsAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAwsAuth(identityId) });
}
@@ -504,10 +513,20 @@ export const useDeleteIdentityAwsAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/aws-auth/identities/${identityId}`);
return identityAwsAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAwsAuth(identityId) });
}
@@ -542,10 +561,20 @@ export const useAddIdentityOciAuth = () => {
return identityOciAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
}
@@ -580,10 +609,20 @@ export const useUpdateIdentityOciAuth = () => {
return identityOciAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
}
@@ -599,10 +638,20 @@ export const useDeleteIdentityOciAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/oci-auth/identities/${identityId}`);
return identityOciAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
}
@@ -635,10 +684,20 @@ export const useAddIdentityAliCloudAuth = () => {
return identityAliCloudAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
@@ -673,10 +732,20 @@ export const useUpdateIdentityAliCloudAuth = () => {
return identityAliCloudAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
@@ -694,10 +763,20 @@ export const useDeleteIdentityAliCloudAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/alicloud-auth/identities/${identityId}`);
return identityAliCloudAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
@@ -734,10 +813,20 @@ export const useAddIdentityTlsCertAuth = () => {
return identityTlsCertAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
@@ -774,10 +863,20 @@ export const useUpdateIdentityTlsCertAuth = () => {
return identityTlsCertAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
@@ -795,10 +894,20 @@ export const useDeleteIdentityTlsCertAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/tls-cert-auth/identities/${identityId}`);
return identityTlsCertAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
@@ -845,10 +954,20 @@ export const useUpdateIdentityOidcAuth = () => {
return identityOidcAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOidcAuth(identityId) });
}
@@ -893,10 +1012,20 @@ export const useAddIdentityOidcAuth = () => {
return identityOidcAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOidcAuth(identityId) });
}
@@ -912,10 +1041,20 @@ export const useDeleteIdentityOidcAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/oidc-auth/identities/${identityId}`);
return identityOidcAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOidcAuth(identityId) });
}
@@ -961,10 +1100,20 @@ export const useUpdateIdentityJwtAuth = () => {
return identityJwtAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityJwtAuth(identityId) });
}
@@ -1011,10 +1160,20 @@ export const useAddIdentityJwtAuth = () => {
return identityJwtAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityJwtAuth(identityId) });
}
@@ -1030,10 +1189,20 @@ export const useDeleteIdentityJwtAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/jwt-auth/identities/${identityId}`);
return identityJwtAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityJwtAuth(identityId) });
}
@@ -1070,10 +1239,20 @@ export const useAddIdentityAzureAuth = () => {
return identityAzureAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityKubernetesAuth(identityId)
@@ -1122,10 +1301,20 @@ export const useAddIdentityKubernetesAuth = () => {
return identityKubernetesAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAzureAuth(identityId) });
}
@@ -1162,10 +1351,20 @@ export const useUpdateIdentityAzureAuth = () => {
return identityAzureAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAzureAuth(identityId) });
}
@@ -1181,10 +1380,20 @@ export const useDeleteIdentityAzureAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/azure-auth/identities/${identityId}`);
return identityAzureAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAzureAuth(identityId) });
}
@@ -1231,10 +1440,20 @@ export const useUpdateIdentityKubernetesAuth = () => {
return identityKubernetesAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityKubernetesAuth(identityId)
@@ -1252,10 +1471,20 @@ export const useDeleteIdentityKubernetesAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/kubernetes-auth/identities/${identityId}`);
return identityKubernetesAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityKubernetesAuth(identityId)
@@ -1288,10 +1517,20 @@ export const useAddIdentityTokenAuth = () => {
return identityTokenAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTokenAuth(identityId)
@@ -1324,10 +1563,20 @@ export const useUpdateIdentityTokenAuth = () => {
return identityTokenAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTokenAuth(identityId)
@@ -1345,10 +1594,20 @@ export const useDeleteIdentityTokenAuth = () => {
} = await apiRequest.delete(`/api/v1/auth/token-auth/identities/${identityId}`);
return identityTokenAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityTokenAuth(identityId) });
}
@@ -1462,10 +1721,20 @@ export const useAddIdentityLdapAuth = () => {
);
return data.identityLdapAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
@@ -1519,10 +1788,20 @@ export const useUpdateIdentityLdapAuth = () => {
);
return data.identityLdapAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { identityId, organizationId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
@@ -1538,10 +1817,20 @@ export const useDeleteIdentityLdapAuth = () => {
const { data } = await apiRequest.delete(`/api/v1/auth/ldap-auth/identities/${identityId}`);
return data.identityLdapAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
onSuccess: (_, { organizationId, identityId, projectId }) => {
if (organizationId) {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
}
if (projectId) {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
});
}
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
@@ -13,10 +13,10 @@ import {
IdentityJwtAuth,
IdentityKubernetesAuth,
IdentityLdapAuth,
IdentityMembership,
IdentityMembershipOrg,
IdentityOciAuth,
IdentityOidcAuth,
IdentityProjectMembershipV1,
IdentityTlsCertAuth,
IdentityTokenAuth,
IdentityUniversalAuth,
@@ -52,7 +52,7 @@ export const identitiesKeys = {
[{ identityId }, "identity-project-memberships"] as const
};
export const useGetIdentityById = (identityId: string) => {
export const useGetOrgIdentityMembershipById = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),
queryKey: identitiesKeys.getIdentityById(identityId),
@@ -67,7 +67,7 @@ export const useGetIdentityById = (identityId: string) => {
});
};
export const useSearchIdentities = (dto: TSearchIdentitiesDTO) => {
export const useSearchOrgIdentityMemberships = (dto: TSearchIdentitiesDTO) => {
const { limit, search, offset, orderBy, orderDirection } = dto;
return useQuery({
queryKey: identitiesKeys.searchIdentities(dto),
@@ -95,7 +95,7 @@ export const useGetIdentityProjectMemberships = (identityId: string) => {
queryFn: async () => {
const {
data: { identityMemberships }
} = await apiRequest.get<{ identityMemberships: IdentityMembership[] }>(
} = await apiRequest.get<{ identityMemberships: IdentityProjectMembershipV1[] }>(
`/api/v1/identities/${identityId}/identity-memberships`
);
return identityMemberships;
+156 -76
View File
@@ -1,6 +1,8 @@
import { TemporaryPermissionMode } from "@app/hooks/api/shared";
import { OrderByDirection } from "../generic/types";
import { OrgIdentityOrderBy } from "../organization/types";
import { Project, ProjectUserMembershipTemporaryMode } from "../projects/types";
import { Project } from "../projects/types";
import { TOrgRole } from "../roles/types";
import { IdentityAuthMethod, IdentityJwtConfigurationType } from "./enums";
@@ -21,6 +23,8 @@ export type Identity = {
updatedAt: string;
isInstanceAdmin?: boolean;
orgId: string;
projectId?: string | null;
metadata?: { key: string; value: string; id: string }[];
};
export type IdentityAccessToken = {
@@ -52,7 +56,7 @@ export type IdentityMembershipOrg = {
updatedAt: string;
};
export type IdentityMembership = {
export type IdentityProjectMembershipV1 = {
id: string;
identity: Identity;
project: Pick<Project, "id" | "name" | "type">;
@@ -74,7 +78,7 @@ export type IdentityMembership = {
| {
isTemporary: true;
temporaryRange: string;
temporaryMode: ProjectUserMembershipTemporaryMode;
temporaryMode: TemporaryPermissionMode;
temporaryAccessEndTime: string;
temporaryAccessStartTime: string;
}
@@ -82,6 +86,41 @@ export type IdentityMembership = {
>;
createdAt: string;
updatedAt: string;
lastLoginTime?: string;
lastLoginAuthMethod?: IdentityAuthMethod;
};
export type IdentityProjectMembershipV2 = {
id: string;
identity: Identity;
roles: Array<
{
id: string;
role: "owner" | "admin" | "member" | "no-access" | "custom";
customRoleId: string;
customRoleName: string;
customRoleSlug: string;
} & (
| {
isTemporary: false;
temporaryRange: null;
temporaryMode: null;
temporaryAccessEndTime: null;
temporaryAccessStartTime: null;
}
| {
isTemporary: true;
temporaryRange: string;
temporaryMode: TemporaryPermissionMode;
temporaryAccessEndTime: string;
temporaryAccessStartTime: string;
}
)
>;
createdAt: string;
updatedAt: string;
lastLoginTime?: string;
lastLoginAuthMethod?: IdentityAuthMethod;
};
export type CreateIdentityDTO = {
@@ -122,7 +161,8 @@ export type IdentityUniversalAuth = {
};
export type AddIdentityUniversalAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
clientSecretTrustedIps: {
ipAddress: string;
@@ -138,10 +178,11 @@ export type AddIdentityUniversalAuthDTO = {
lockoutThreshold: number;
lockoutDurationSeconds: number;
lockoutCounterResetSeconds: number;
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityUniversalAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
clientSecretTrustedIps?: {
ipAddress: string;
@@ -157,12 +198,13 @@ export type UpdateIdentityUniversalAuthDTO = {
lockoutThreshold?: number;
lockoutDurationSeconds?: number;
lockoutCounterResetSeconds?: number;
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityUniversalAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityGcpAuth = {
identityId: string;
@@ -177,7 +219,8 @@ export type IdentityGcpAuth = {
};
export type AddIdentityGcpAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
type: "iam" | "gce";
allowedServiceAccounts: string;
@@ -189,10 +232,11 @@ export type AddIdentityGcpAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityGcpAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
type?: "iam" | "gce";
allowedServiceAccounts?: string;
@@ -204,12 +248,13 @@ export type UpdateIdentityGcpAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityGcpAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityOidcAuth = {
identityId: string;
@@ -227,7 +272,8 @@ export type IdentityOidcAuth = {
};
export type AddIdentityOidcAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
oidcDiscoveryUrl: string;
caCert: string;
@@ -242,10 +288,11 @@ export type AddIdentityOidcAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityOidcAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
oidcDiscoveryUrl?: string;
caCert?: string;
@@ -260,12 +307,13 @@ export type UpdateIdentityOidcAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityOidcAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityAwsAuth = {
identityId: string;
@@ -280,7 +328,8 @@ export type IdentityAwsAuth = {
};
export type AddIdentityAwsAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
stsEndpoint: string;
allowedPrincipalArns: string;
@@ -291,10 +340,11 @@ export type AddIdentityAwsAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityAwsAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
stsEndpoint?: string;
allowedPrincipalArns?: string;
@@ -308,9 +358,10 @@ export type UpdateIdentityAwsAuthDTO = {
};
export type DeleteIdentityAwsAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityAliCloudAuth = {
identityId: string;
@@ -323,7 +374,8 @@ export type IdentityAliCloudAuth = {
};
export type AddIdentityAliCloudAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
allowedArns: string;
accessTokenTTL: number;
@@ -332,10 +384,11 @@ export type AddIdentityAliCloudAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityAliCloudAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
allowedArns: string;
accessTokenTTL?: number;
@@ -344,12 +397,13 @@ export type UpdateIdentityAliCloudAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityAliCloudAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityOciAuth = {
identityId: string;
@@ -363,7 +417,8 @@ export type IdentityOciAuth = {
};
export type AddIdentityOciAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
tenancyOcid: string;
allowedUsernames?: string | null;
@@ -373,10 +428,11 @@ export type AddIdentityOciAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityOciAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
tenancyOcid?: string;
allowedUsernames?: string | null;
@@ -386,12 +442,13 @@ export type UpdateIdentityOciAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityOciAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityAzureAuth = {
identityId: string;
@@ -405,7 +462,8 @@ export type IdentityAzureAuth = {
};
export type AddIdentityAzureAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
tenantId: string;
resource: string;
@@ -416,10 +474,11 @@ export type AddIdentityAzureAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityAzureAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
tenantId?: string;
resource?: string;
@@ -430,12 +489,13 @@ export type UpdateIdentityAzureAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityAzureAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export enum IdentityKubernetesAuthTokenReviewMode {
Api = "api",
@@ -459,7 +519,8 @@ export type IdentityKubernetesAuth = {
};
export type AddIdentityKubernetesAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
kubernetesHost: string | null;
tokenReviewerJwt?: string;
@@ -475,10 +536,11 @@ export type AddIdentityKubernetesAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityKubernetesAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
kubernetesHost?: string | null;
tokenReviewerJwt?: string | null;
@@ -494,12 +556,13 @@ export type UpdateIdentityKubernetesAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityKubernetesAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityTlsCertAuth = {
identityId: string;
@@ -512,7 +575,8 @@ export type IdentityTlsCertAuth = {
};
export type AddIdentityTlsCertAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
caCertificate: string;
allowedCommonNames?: string;
@@ -522,10 +586,11 @@ export type AddIdentityTlsCertAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityTlsCertAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
caCertificate: string;
allowedCommonNames?: string | null;
@@ -535,12 +600,13 @@ export type UpdateIdentityTlsCertAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityTlsCertAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type CreateIdentityUniversalAuthClientSecretDTO = {
identityId: string;
@@ -585,7 +651,8 @@ export type IdentityTokenAuth = {
};
export type AddIdentityLdapAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
templateId?: string;
url?: string;
@@ -609,11 +676,12 @@ export type AddIdentityLdapAuthDTO = {
lockoutThreshold: number;
lockoutDurationSeconds: number;
lockoutCounterResetSeconds: number;
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityLdapAuthDTO = {
identityId: string;
organizationId: string;
organizationId?: string;
projectId?: string;
templateId?: string;
url?: string;
bindDN?: string;
@@ -636,12 +704,13 @@ export type UpdateIdentityLdapAuthDTO = {
lockoutThreshold?: number;
lockoutDurationSeconds?: number;
lockoutCounterResetSeconds?: number;
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityLdapAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityLdapAuth = {
url?: string;
@@ -673,7 +742,8 @@ export type ClearIdentityLdapAuthLockoutsDTO = {
};
export type AddIdentityTokenAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -681,10 +751,11 @@ export type AddIdentityTokenAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityTokenAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
@@ -692,12 +763,13 @@ export type UpdateIdentityTokenAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityTokenAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type IdentityJwtAuth = {
identityId: string;
@@ -716,7 +788,8 @@ export type IdentityJwtAuth = {
};
export type AddIdentityJwtAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
configurationType: string;
jwksUrl?: string;
@@ -732,10 +805,11 @@ export type AddIdentityJwtAuthDTO = {
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type UpdateIdentityJwtAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
configurationType?: string;
jwksUrl?: string;
@@ -751,12 +825,13 @@ export type UpdateIdentityJwtAuthDTO = {
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
} & ({ organizationId: string } | { projectId: string });
export type DeleteIdentityJwtAuthDTO = {
organizationId: string;
organizationId?: string;
projectId?: string;
identityId: string;
};
} & ({ organizationId: string } | { projectId: string });
export type CreateTokenIdentityTokenAuthDTO = {
identityId: string;
@@ -783,8 +858,13 @@ export type RevokeTokenRes = {
message: string;
};
export type TProjectIdentitiesList = {
identityMemberships: IdentityMembership[];
export type TProjectIdentityMembershipsList = {
identityMemberships: IdentityProjectMembershipV1[];
totalCount: number;
};
export type TProjectIdentityMembershipsListV2 = {
identityMemberships: IdentityProjectMembershipV2[];
totalCount: number;
};
+4
View File
@@ -25,10 +25,14 @@ export * from "./ldapConfig";
export * from "./oidcConfig";
export * from "./orgAdmin";
export * from "./organization";
export * from "./orgIdentity";
export * from "./orgIdentityMembership";
export * from "./pkiAlerts";
export * from "./pkiCollections";
export * from "./pkiSubscriber";
export * from "./pkiSyncs";
export * from "./projectIdentity";
export * from "./projectIdentityMembership";
export * from "./projects";
export * from "./projectUserAdditionalPrivilege";
export * from "./rateLimit";
@@ -0,0 +1,3 @@
export * from "./mutations";
export * from "./queries";
export type * from "./types";
@@ -0,0 +1,116 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { identitiesKeys } from "@app/hooks/api";
import { CreateIdentityDTO, Identity, UpdateIdentityDTO } from "@app/hooks/api/identities/types";
import { organizationKeys } from "@app/hooks/api/organization/queries";
import { subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
import { orgIdentityQuery } from "./queries";
import { TDeleteOrgIdentityDTO, TOrgIdentity } from "./types";
// TODO (scott/akhi): eventually move to the new api commented out below; the current ones use old api
export const useCreateOrgIdentity = () => {
const queryClient = useQueryClient();
return useMutation<Identity, object, CreateIdentityDTO>({
mutationFn: async (body) => {
const {
data: { identity }
} = await apiRequest.post("/api/v1/identities/", body);
return identity;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
export const useUpdateOrgIdentity = () => {
const queryClient = useQueryClient();
return useMutation<Identity, object, UpdateIdentityDTO>({
mutationFn: async ({ identityId, name, role, hasDeleteProtection, metadata }) => {
const {
data: { identity }
} = await apiRequest.patch(`/api/v1/identities/${identityId}`, {
name,
role,
hasDeleteProtection,
metadata
});
return identity;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
// export const useCreateOrgIdentity = () => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: async (dto: TCreateOrgIdentityDTO) => {
// const { data } = await apiRequest.post<{ identity: TOrgIdentity }>(
// "/api/v1/organization/identities",
// dto
// );
// return data;
// },
// onSuccess: () => {
// queryClient.invalidateQueries({ queryKey: orgIdentityQuery.allKey() });
// queryClient.invalidateQueries({
// queryKey: subscriptionQueryKeys.all()
// });
// }
// });
// };
//
// export const useUpdateOrgIdentity = () => {
// const queryClient = useQueryClient();
// return useMutation({
// mutationFn: async ({ identityId, ...updates }: TUpdateOrgIdentityDTO) => {
// const { data } = await apiRequest.patch<{ identity: TOrgIdentity }>(
// `/api/v1/organization/identities/${identityId}`,
// updates
// );
// return data;
// },
// onSuccess: () => {
// queryClient.invalidateQueries({ queryKey: orgIdentityQuery.allKey() });
// }
// });
// };
export const useDeleteOrgIdentity = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ identityId }: TDeleteOrgIdentityDTO) => {
const { data } = await apiRequest.delete<{ identity: TOrgIdentity }>(
`/api/v1/identities/${identityId}`
);
return data;
},
onSuccess: (_, { orgId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(orgId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(orgId) });
queryClient.invalidateQueries({ queryKey: orgIdentityQuery.allKey() });
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.all()
});
}
});
};
@@ -0,0 +1,40 @@
import { queryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TGetOrgIdentityByIdDTO, TListOrgIdentitiesDTO, TOrgIdentity } from "./types";
export const orgIdentityQuery = {
allKey: () => ["organization-identities"] as const,
getByIdKey: (params: TGetOrgIdentityByIdDTO) =>
[...orgIdentityQuery.allKey(), "by-id", params] as const,
listKey: (params?: TListOrgIdentitiesDTO) =>
[...orgIdentityQuery.allKey(), "list", params] as const,
getById: (params: TGetOrgIdentityByIdDTO) =>
queryOptions({
queryKey: orgIdentityQuery.getByIdKey(params),
queryFn: async () => {
const { data } = await apiRequest.get<{ identity: TOrgIdentity }>(
`/api/v1/organization/identities/${params.identityId}`
);
return data.identity;
}
}),
list: (params: TListOrgIdentitiesDTO = {}) =>
queryOptions({
queryKey: orgIdentityQuery.listKey(params),
queryFn: async () => {
const { data } = await apiRequest.get<{
identities: TOrgIdentity[];
totalCount: number;
}>("/api/v1/organization/identities", {
params: {
offset: params.offset,
limit: params.limit,
search: params.search
}
});
return data;
}
})
};
@@ -0,0 +1,31 @@
import { TIdentity, TMetadata } from "@app/hooks/api/shared";
export type TOrgIdentity = TIdentity;
export type TCreateOrgIdentityDTO = {
name: string;
hasDeleteProtection?: boolean;
metadata?: TMetadata;
};
export type TUpdateOrgIdentityDTO = {
identityId: string;
name?: string;
hasDeleteProtection?: boolean;
metadata?: TMetadata;
};
export type TGetOrgIdentityByIdDTO = {
identityId: string;
};
export type TListOrgIdentitiesDTO = {
offset?: number;
limit?: number;
search?: string;
};
export type TDeleteOrgIdentityDTO = {
identityId: string;
orgId: string;
};
@@ -0,0 +1,31 @@
import { queryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import {
TAvailableOrganizationIdentities,
TListAvailableOrganizationIdentitiesDTO,
TListOrgIdentityMembershipsDTO
} from "./types";
export const orgIdentityMembershipQuery = {
allKey: () => ["organization-identity-memberships"] as const,
listAvailableKey: (params?: TListOrgIdentityMembershipsDTO) =>
[...orgIdentityMembershipQuery.allKey(), "list-available", params] as const,
listAvailable: (params: TListAvailableOrganizationIdentitiesDTO = {}) =>
queryOptions({
queryKey: orgIdentityMembershipQuery.listAvailableKey(params),
queryFn: async () => {
const { data } = await apiRequest.get<{
identities: TAvailableOrganizationIdentities;
}>("/api/v1/organization/available-identities", {
params: {
offset: params.offset,
limit: params.limit,
identityName: params.identityName
}
});
return data.identities;
}
})
};
@@ -1,6 +1,4 @@
export enum TemporaryPermissionMode {
Relative = "relative"
}
import { TRoles } from "@app/hooks/api/shared";
export type TOrgIdentityMembership = {
id: string;
@@ -12,21 +10,24 @@ export type TOrgIdentityMembership = {
export type TCreateOrgIdentityMembershipDTO = {
identityId: string;
roles: Array<
| {
role: string;
isTemporary?: false;
}
| {
role: string;
isTemporary: true;
temporaryMode: TemporaryPermissionMode;
temporaryRange: string;
temporaryAccessStartTime: string;
}
>;
roles: TRoles;
};
export type TDeleteOrgIdentityMembershipDTO = {
identityId: string;
};
export type TListOrgIdentityMembershipsDTO = {
offset?: number;
limit?: number;
identityName?: string;
roles?: string[];
};
export type TListAvailableOrganizationIdentitiesDTO = {
offset?: number;
limit?: number;
identityName?: string;
};
export type TAvailableOrganizationIdentities = Array<{ id: string; name: string }>;
@@ -6,7 +6,6 @@ export {
useDeleteOrgById,
useDeleteOrgPmtMethod,
useDeleteOrgTaxId,
useGetAvailableOrgIdentities,
useGetIdentityMembershipOrgs,
useGetOrganizationGroups,
useGetOrganizations,
@@ -579,19 +579,6 @@ export const useGetOrgIntegrationAuths = <TData = IntegrationAuth[],>(
});
};
export const useGetAvailableOrgIdentities = (enabled = true) =>
useQuery({
queryKey: organizationKeys.getAvailableIdentities(),
queryFn: async () => {
const { data } = await apiRequest.get<{ identities: { name: string; id: string }[] }>(
"/api/v1/organization/identities/available"
);
return data.identities;
},
enabled
});
export const useGetAvailableOrgUsers = (enabled = true) =>
useQuery({
queryKey: organizationKeys.getAvailableUsers(),
+13 -1
View File
@@ -3,7 +3,19 @@ export enum PamResourceType {
MySQL = "mysql",
RDP = "rdp",
SSH = "ssh",
Kubernetes = "kubernetes"
Kubernetes = "kubernetes",
OracleDB = "oracledb",
SQLite = "sqlite",
MsSQL = "mssql",
MCP = "mcp",
Redis = "redis",
MongoDB = "mongodb",
WebApp = "webapp",
Cassandra = "cassandra",
CockroachDB = "cockroachdb",
Elasticsearch = "elasticsearch",
Snowflake = "snowflake",
DynamoDB = "dynamodb"
}
export enum PamSessionStatus {
+13 -1
View File
@@ -8,5 +8,17 @@ export const PAM_RESOURCE_TYPE_MAP: Record<
[PamResourceType.MySQL]: { name: "MySQL", image: "MySql.png" },
[PamResourceType.RDP]: { name: "RDP", image: "RDP.png" },
[PamResourceType.SSH]: { name: "SSH", image: "SSH.png" },
[PamResourceType.Kubernetes]: { name: "Kubernetes", image: "Kubernetes.png" }
[PamResourceType.Kubernetes]: { name: "Kubernetes", image: "Kubernetes.png" },
[PamResourceType.OracleDB]: { name: "OracleDB", image: "Oracle.png", size: 55 },
[PamResourceType.SQLite]: { name: "SQLite", image: "SQLite.png" },
[PamResourceType.MsSQL]: { name: "Microsoft SQL Server", image: "MsSql.png" },
[PamResourceType.MCP]: { name: "MCP", image: "MCP.png" },
[PamResourceType.Redis]: { name: "Redis", image: "Redis.png" },
[PamResourceType.MongoDB]: { name: "MongoDB", image: "MongoDB.png" },
[PamResourceType.WebApp]: { name: "Web Application", image: "Web.png" },
[PamResourceType.Cassandra]: { name: "Cassandra", image: "Cassandra.png", size: 55 },
[PamResourceType.CockroachDB]: { name: "CockroachDB", image: "CockroachDB.png" },
[PamResourceType.Elasticsearch]: { name: "Elasticsearch", image: "Elastic.png" },
[PamResourceType.Snowflake]: { name: "Snowflake", image: "Snowflake.png" },
[PamResourceType.DynamoDB]: { name: "DynamoDB", image: "DynamoDB.png", size: 55 }
};
@@ -0,0 +1,15 @@
export {
useCreateProjectIdentity,
useDeleteProjectIdentity,
useUpdateProjectIdentity
} from "./mutations";
export { projectIdentityQuery } from "./queries";
export type {
TCreateProjectIdentityDTO,
TDeleteProjectIdentityDTO,
TGetProjectIdentityByIdDTO,
TListProjectIdentitiesDTO,
TProjectIdentity,
TProjectIdentityMetadata,
TUpdateProjectIdentityDTO
} from "./types";
@@ -0,0 +1,76 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { identitiesKeys, projectKeys } from "@app/hooks/api";
import { subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
import { projectIdentityQuery } from "./queries";
import {
TCreateProjectIdentityDTO,
TDeleteProjectIdentityDTO,
TProjectIdentity,
TUpdateProjectIdentityDTO
} from "./types";
export const useCreateProjectIdentity = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ projectId, ...dto }: TCreateProjectIdentityDTO) => {
const { data } = await apiRequest.post<{ identity: TProjectIdentity }>(
`/api/v1/projects/${projectId}/identities`,
dto
);
return data.identity;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.all()
});
}
});
};
export const useUpdateProjectIdentity = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ projectId, identityId, ...updates }: TUpdateProjectIdentityDTO) => {
const { data } = await apiRequest.patch<{ identity: TProjectIdentity }>(
`/api/v1/projects/${projectId}/identities/${identityId}`,
updates
);
return data.identity;
},
onSuccess: (_, { projectId, identityId }) => {
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId)
});
}
});
};
export const useDeleteProjectIdentity = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ projectId, identityId }: TDeleteProjectIdentityDTO) => {
const { data } = await apiRequest.delete<{ identity: TProjectIdentity }>(
`/api/v1/projects/${projectId}/identities/${identityId}`
);
return data.identity;
},
onSuccess: (_, { projectId, identityId }) => {
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
});
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.all()
});
}
});
};
@@ -0,0 +1,40 @@
import { queryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TGetProjectIdentityByIdDTO, TListProjectIdentitiesDTO, TProjectIdentity } from "./types";
export const projectIdentityQuery = {
allKey: () => ["project-identities"] as const,
getByIdKey: (params: TGetProjectIdentityByIdDTO) =>
[...projectIdentityQuery.allKey(), "by-id", params] as const,
listKey: (params: TListProjectIdentitiesDTO) =>
[...projectIdentityQuery.allKey(), "list", params] as const,
getById: (params: TGetProjectIdentityByIdDTO) =>
queryOptions({
queryKey: projectIdentityQuery.getByIdKey(params),
queryFn: async () => {
const { data } = await apiRequest.get<{ identity: TProjectIdentity }>(
`/api/v1/projects/${params.projectId}/identities/${params.identityId}`
);
return data.identity;
}
}),
list: (params: TListProjectIdentitiesDTO) =>
queryOptions({
queryKey: projectIdentityQuery.listKey(params),
queryFn: async () => {
const { data } = await apiRequest.get<{
identities: TProjectIdentity[];
totalCount: number;
}>(`/api/v1/projects/${params.projectId}/identities`, {
params: {
offset: params.offset,
limit: params.limit,
search: params.search
}
});
return data;
}
})
};
@@ -0,0 +1,41 @@
import { TIdentity, TMetadata } from "@app/hooks/api/shared";
export type TProjectIdentityMetadata = {
key: string;
value: string;
id: string;
};
export type TProjectIdentity = TIdentity;
export type TCreateProjectIdentityDTO = {
projectId: string;
name: string;
hasDeleteProtection?: boolean;
metadata?: TMetadata[];
};
export type TUpdateProjectIdentityDTO = {
projectId: string;
identityId: string;
name?: string;
hasDeleteProtection?: boolean;
metadata?: TMetadata[];
};
export type TGetProjectIdentityByIdDTO = {
projectId: string;
identityId: string;
};
export type TListProjectIdentitiesDTO = {
projectId: string;
offset?: number;
limit?: number;
search?: string;
};
export type TDeleteProjectIdentityDTO = {
projectId: string;
identityId: string;
};
@@ -0,0 +1,3 @@
export * from "./mutations";
export * from "./queries";
export * from "./types";
@@ -0,0 +1,93 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { identitiesKeys, projectKeys } from "@app/hooks/api";
import { projectIdentityQuery } from "@app/hooks/api/projectIdentity";
import {
TCreateProjectIdentityMembershipDTO,
TDeleteProjectIdentityMembershipDTO,
TProjectIdentityMembership,
TUpdateProjectIdentityMembershipDTO
} from "./types";
export const useCreateProjectIdentityMembership = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ identityId, projectId, role }: TCreateProjectIdentityMembershipDTO) => {
const {
data: { identityMembership }
} = await apiRequest.post<{ identityMembership: TProjectIdentityMembership }>(
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`,
{
role
}
);
return identityMembership;
},
onSuccess: (_, { identityId, projectId }) => {
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
});
}
});
};
export const useUpdateProjectIdentityMembership = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
projectId,
identityId,
...updates
}: TUpdateProjectIdentityMembershipDTO) => {
const {
data: { identityMembership }
} = await apiRequest.patch<{ identityMembership: TProjectIdentityMembership }>(
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`,
updates
);
return identityMembership;
},
onSuccess: (_, { projectId, identityId }) => {
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
});
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId)
});
}
});
};
export const useDeleteProjectIdentityMembership = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ identityId, projectId }: TDeleteProjectIdentityMembershipDTO) => {
const {
data: { identityMembership }
} = await apiRequest.delete<{ identityMembership: TProjectIdentityMembership }>(
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`
);
return identityMembership;
},
onSuccess: (_, { identityId, projectId }) => {
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
});
}
});
};
@@ -0,0 +1,116 @@
import { queryOptions, useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import {
projectKeys,
TAvailableProjectIdentities,
TListAvailableProjectIdentitiesDTO
} from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import {
IdentityProjectMembershipV1,
TProjectIdentityMembershipsListV2
} from "@app/hooks/api/identities/types";
import { ProjectIdentityOrderBy, TListProjectIdentitiesDTO } from "@app/hooks/api/projects/types";
export const projectIdentityMembershipQuery = {
allKey: () => ["project-identity-memberships"] as const,
listAvailableKey: (params?: TListAvailableProjectIdentitiesDTO) =>
[...projectIdentityMembershipQuery.allKey(), "list-available", params] as const,
listAvailable: (params: TListAvailableProjectIdentitiesDTO) =>
queryOptions({
queryKey: projectIdentityMembershipQuery.listAvailableKey(params),
queryFn: async () => {
const { data } = await apiRequest.get<{
identities: TAvailableProjectIdentities;
}>(`/api/v1/projects/${params.projectId}/memberships/available-identities`, {
params: {
offset: params.offset,
limit: params.limit,
identityName: params.identityName
}
});
return data.identities;
}
})
};
// TODO (scott/akhi): move to new projectIdentityMembershipQuery structure
export const useListProjectIdentityMemberships = (
{
projectId,
offset = 0,
limit = 100,
orderBy = ProjectIdentityOrderBy.Name,
orderDirection = OrderByDirection.ASC,
search = ""
}: TListProjectIdentitiesDTO,
options?: Omit<
UseQueryOptions<
TProjectIdentityMembershipsListV2,
unknown,
TProjectIdentityMembershipsListV2,
ReturnType<typeof projectKeys.getProjectIdentityMembershipsWithParams>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: projectKeys.getProjectIdentityMembershipsWithParams({
projectId,
offset,
limit,
orderBy,
orderDirection,
search
}),
queryFn: async () => {
const params = new URLSearchParams({
offset: String(offset),
limit: String(limit),
orderBy: String(orderBy),
orderDirection: String(orderDirection),
search: String(search)
});
const { data } = await apiRequest.get<TProjectIdentityMembershipsListV2>(
`/api/v1/projects/${projectId}/memberships/identities`,
{ params }
);
return data;
},
enabled: true,
...options
});
};
export const useGetProjectIdentityMembership = (projectId: string, identityId: string) => {
return useQuery({
enabled: Boolean(projectId && identityId),
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId),
queryFn: async () => {
const {
data: { identityMembership }
} = await apiRequest.get<{ identityMembership: IdentityProjectMembershipV1 }>(
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`
);
return identityMembership;
}
});
};
export const useGetProjectIdentityMembershipV2 = (projectId: string, identityId: string) => {
return useQuery({
enabled: Boolean(projectId && identityId),
queryKey: projectKeys.getProjectIdentityMembershipDetailsV2(projectId, identityId),
queryFn: async () => {
const {
data: { identityMembership }
} = await apiRequest.get<{ identityMembership: IdentityProjectMembershipV1 }>(
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`
);
return identityMembership;
}
});
};
@@ -0,0 +1,36 @@
import { TRoles } from "@app/hooks/api/shared";
export type TProjectIdentityMembership = {
id: string;
projectId: string;
identityId: string;
createdAt: string;
updatedAt: string;
// TODO
};
export type TCreateProjectIdentityMembershipDTO = {
identityId: string;
projectId: string;
role?: string;
};
export type TUpdateProjectIdentityMembershipDTO = {
identityId: string;
projectId: string;
roles: TRoles;
};
export type TDeleteProjectIdentityMembershipDTO = {
identityId: string;
projectId: string;
};
export type TListAvailableProjectIdentitiesDTO = {
projectId: string;
offset?: number;
limit?: number;
identityName?: string;
};
export type TAvailableProjectIdentities = Array<{ id: string; name: string }>;
@@ -8,10 +8,8 @@ export {
useUpdateProjectSshConfig
} from "./mutations";
export {
useAddIdentityToWorkspace,
useCreateWorkspace,
useCreateWsEnvironment,
useDeleteIdentityFromWorkspace,
useDeleteUserFromWorkspace,
useDeleteWorkspace,
useDeleteWsEnvironment,
@@ -21,8 +19,6 @@ export {
useGetUserWorkspaceMemberships,
useGetWorkspaceAuthorizations,
useGetWorkspaceById,
useGetWorkspaceIdentityMembershipDetails,
useGetWorkspaceIdentityMemberships,
useGetWorkspaceIndexStatus,
useGetWorkspaceIntegrations,
useGetWorkspaceUserDetails,
@@ -41,7 +37,6 @@ export {
useListWorkspaceSshHostGroups,
useListWorkspaceSshHosts,
useSearchProjects,
useUpdateIdentityWorkspaceRole,
useUpdateProject,
useUpdateUserWorkspaceRole,
useUpdateWsEnvironment,
+1 -155
View File
@@ -1,15 +1,12 @@
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { CaStatus } from "../ca/enums";
import { TCertificateAuthority } from "../ca/types";
import { TCertificate } from "../certificates/types";
import { TCertificateTemplate } from "../certificateTemplates/types";
import { TGroupMembership } from "../groups/types";
import { identitiesKeys } from "../identities/queries";
import { IdentityMembership, TProjectIdentitiesList } from "../identities/types";
import { IntegrationAuth } from "../integrationAuth/types";
import { TIntegration } from "../integrations/types";
import { TPkiAlert } from "../pkiAlerts/types";
@@ -33,13 +30,10 @@ import {
DeleteWorkspaceDTO,
Project,
ProjectEnv,
ProjectIdentityOrderBy,
ProjectType,
TGetUpgradeProjectStatusDTO,
TListProjectIdentitiesDTO,
TProjectSshConfig,
TSearchProjectsDTO,
TUpdateWorkspaceIdentityRoleDTO,
TUpdateWorkspaceUserRoleDTO,
UpdateAuditLogsRetentionDTO,
UpdateEnvironmentDTO,
@@ -452,154 +446,6 @@ export const useUpdateUserWorkspaceRole = () => {
});
};
export const useAddIdentityToWorkspace = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
identityId,
projectId,
role
}: {
identityId: string;
projectId: string;
role?: string;
}) => {
const {
data: { identityMembership }
} = await apiRequest.post(
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`,
{
role
}
);
return identityMembership;
},
onSuccess: (_, { identityId, projectId }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
});
}
});
};
export const useUpdateIdentityWorkspaceRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ identityId, projectId, roles }: TUpdateWorkspaceIdentityRoleDTO) => {
const {
data: { identityMembership }
} = await apiRequest.patch(
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`,
{
roles
}
);
return identityMembership;
},
onSuccess: (_, { identityId, projectId }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
});
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId)
});
}
});
};
export const useDeleteIdentityFromWorkspace = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ identityId, projectId }: { identityId: string; projectId: string }) => {
const {
data: { identityMembership }
} = await apiRequest.delete(
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`
);
return identityMembership;
},
onSuccess: (_, { identityId, projectId }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
});
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
});
}
});
};
export const useGetWorkspaceIdentityMemberships = (
{
projectId,
offset = 0,
limit = 100,
orderBy = ProjectIdentityOrderBy.Name,
orderDirection = OrderByDirection.ASC,
search = ""
}: TListProjectIdentitiesDTO,
options?: Omit<
UseQueryOptions<
TProjectIdentitiesList,
unknown,
TProjectIdentitiesList,
ReturnType<typeof projectKeys.getProjectIdentityMembershipsWithParams>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: projectKeys.getProjectIdentityMembershipsWithParams({
projectId,
offset,
limit,
orderBy,
orderDirection,
search
}),
queryFn: async () => {
const params = new URLSearchParams({
offset: String(offset),
limit: String(limit),
orderBy: String(orderBy),
orderDirection: String(orderDirection),
search: String(search)
});
const { data } = await apiRequest.get<TProjectIdentitiesList>(
`/api/v1/projects/${projectId}/identity-memberships`,
{ params }
);
return data;
},
enabled: true,
...options
});
};
export const useGetWorkspaceIdentityMembershipDetails = (projectId: string, identityId: string) => {
return useQuery({
enabled: Boolean(projectId && identityId),
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId),
queryFn: async () => {
const {
data: { identityMembership }
} = await apiRequest.get<{ identityMembership: IdentityMembership }>(
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`
);
return identityMembership;
}
});
};
export const useGetWorkspaceGroupMembershipDetails = (projectId: string, groupId: string) => {
return useQuery({
enabled: Boolean(projectId && groupId),
@@ -23,6 +23,8 @@ export const projectKeys = {
[{ projectId }, "project-identity-memberships"] as const,
getProjectIdentityMembershipDetails: (projectId: string, identityId: string) =>
[{ projectId, identityId }, "project-identity-membership-details"] as const,
getProjectIdentityMembershipDetailsV2: (projectId: string, identityId: string) =>
[{ projectId, identityId }, "project-identity-membership-details"] as const,
// allows invalidation using above key without knowing params
getProjectIdentityMembershipsWithParams: ({ projectId, ...params }: TListProjectIdentitiesDTO) =>
[...projectKeys.getProjectIdentityMemberships(projectId), params] as const,
-18
View File
@@ -138,24 +138,6 @@ export type TUpdateWorkspaceUserRoleDTO = {
)[];
};
export type TUpdateWorkspaceIdentityRoleDTO = {
identityId: string;
projectId: string;
roles: (
| {
role: string;
isTemporary?: false;
}
| {
role: string;
isTemporary: true;
temporaryMode: ProjectUserMembershipTemporaryMode;
temporaryRange: string;
temporaryAccessStartTime: string;
}
)[];
};
export type TUpdateWorkspaceGroupRoleDTO = {
groupId: string;
projectId: string;
@@ -59,6 +59,8 @@ export type TViewSharedSecretResponse = {
tag: string;
accessType: SecretSharingAccessType;
orgName?: string;
expiresAt?: Date | string;
expiresAfterViews?: number | null;
};
};
+1
View File
@@ -0,0 +1 @@
export * from "./types";
+37
View File
@@ -0,0 +1,37 @@
import { IdentityAuthMethod } from "@app/hooks/api";
export enum TemporaryPermissionMode {
Relative = "relative"
}
export type TMetadata = {
key: string;
value: string;
};
export type TIdentity = {
id: string;
name: string;
orgId: string;
projectId: string | null;
createdAt: string;
updatedAt: string;
hasDeleteProtection: boolean;
authMethods: IdentityAuthMethod[];
activeLockoutAuthMethods: string[];
metadata?: Array<TMetadata & { id: string }>;
};
export type TRoles = Array<
| {
role: string;
isTemporary?: false;
}
| {
role: string;
isTemporary: true;
temporaryMode: TemporaryPermissionMode;
temporaryRange: string;
temporaryAccessStartTime: string;
}
>;
@@ -7,7 +7,8 @@ import { SubscriptionPlan } from "./types";
// import { Workspace } from './types';
export const subscriptionQueryKeys = {
getOrgSubsription: (orgID: string) => ["plan", { orgID }] as const
all: () => ["plan"] as const,
getOrgSubsription: (orgID: string) => [...subscriptionQueryKeys.all(), { orgID }] as const
};
export const fetchOrgSubscription = async (orgID: string, refreshCache: boolean = false) => {
@@ -48,6 +48,7 @@ export type SubscriptionPlan = {
gateway: boolean;
externalKms: boolean;
pkiEst: boolean;
pkiAcme: boolean;
pkiLegacyTemplates: boolean;
enforceMfa: boolean;
enforceGoogleSSO: boolean;
@@ -2,12 +2,13 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "@app/components/v2";
import { useProject, useProjectPermission } from "@app/context";
import { useOrganization, useProject, useProjectPermission } from "@app/context";
import { getProjectHomePage } from "@app/helpers/project";
import { useRemoveAssumeProjectPrivilege } from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
export const AssumePrivilegeModeBanner = () => {
const { isSubOrganization, currentOrg } = useOrganization();
const { currentProject } = useProject();
const exitAssumePrivilegeMode = useRemoveAssumeProjectPrivilege();
const { assumedPrivilegeDetails } = useProjectPermission();
@@ -36,7 +37,7 @@ export const AssumePrivilegeModeBanner = () => {
},
{
onSuccess: () => {
const url = getProjectHomePage(currentProject.type, currentProject.environments);
const url = `${getProjectHomePage(currentProject.type, currentProject.environments)}${isSubOrganization ? `?subOrganization=${currentOrg.slug}` : ""}`;
window.location.href = url.replace("$projectId", currentProject.id);
}
}
@@ -0,0 +1,163 @@
import { useEffect, useState } from "react";
import { faDownload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
FormControl,
Input,
Modal,
ModalContent,
Select,
SelectItem
} from "@app/components/v2";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
popUp: UsePopUpState<["certificateExport"]>;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["certificateExport"]>,
state?: boolean
) => void;
onFormatSelected: (
format: "pem" | "pkcs12",
serialNumber: string,
options?: ExportOptions
) => void;
};
export type CertificateExportFormat = "pem" | "pkcs12";
export type ExportOptions = {
pkcs12?: {
password: string;
alias: string;
};
};
export const CertificateExportModal = ({ popUp, handlePopUpToggle, onFormatSelected }: Props) => {
const [selectedFormat, setSelectedFormat] = useState<CertificateExportFormat>("pem");
const [pkcs12Options, setPkcs12Options] = useState({
password: "",
alias: ""
});
const serialNumber =
(popUp?.certificateExport?.data as { serialNumber: string })?.serialNumber || "";
// Reset form whenever the modal opens
useEffect(() => {
if (popUp?.certificateExport?.isOpen) {
setSelectedFormat("pem");
setPkcs12Options({
password: "",
alias: ""
});
}
}, [popUp?.certificateExport?.isOpen]);
const isFormValid = () => {
if (selectedFormat === "pkcs12") {
return pkcs12Options.password.length >= 6 && pkcs12Options.alias.trim() !== "";
}
return true;
};
const handleExport = () => {
if (serialNumber && isFormValid()) {
const options: ExportOptions = {};
if (selectedFormat === "pkcs12") {
options.pkcs12 = pkcs12Options;
}
onFormatSelected(selectedFormat, serialNumber, options);
handlePopUpToggle("certificateExport", false);
}
};
return (
<Modal
isOpen={popUp?.certificateExport?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("certificateExport", isOpen);
}}
>
<ModalContent title="Export Certificate">
<div className="space-y-4">
<p className="text-sm text-gray-400">Choose the format for exporting your certificate</p>
<FormControl
label="Export Format"
helperText={
selectedFormat === "pem"
? "Privacy Enhanced Mail - Text-based certificate format"
: "PKCS12 format - Binary keystore format compatible with Java applications"
}
>
<Select
className="w-full"
value={selectedFormat}
onValueChange={(value) => setSelectedFormat(value as CertificateExportFormat)}
>
<SelectItem value="pem">PEM Format</SelectItem>
<SelectItem value="pkcs12">PKCS12 Format</SelectItem>
</Select>
</FormControl>
{selectedFormat === "pkcs12" && (
<>
<FormControl
label="Keystore Password"
helperText={
pkcs12Options.password.length > 0 && pkcs12Options.password.length < 6
? undefined
: "Password to protect the PKCS12 keystore (minimum 6 characters)"
}
isError={pkcs12Options.password.length > 0 && pkcs12Options.password.length < 6}
errorText="Password must be at least 6 characters long"
>
<Input
placeholder="Enter keystore password"
value={pkcs12Options.password}
onChange={(e) =>
setPkcs12Options((prev) => ({ ...prev, password: e.target.value }))
}
type="password"
/>
</FormControl>
<FormControl
label="Certificate Alias"
helperText="Friendly name for the certificate in the keystore"
>
<Input
placeholder="Enter certificate alias"
value={pkcs12Options.alias}
onChange={(e) => setPkcs12Options((prev) => ({ ...prev, alias: e.target.value }))}
/>
</FormControl>
</>
)}
<div className="flex justify-end space-x-2 pt-4">
<Button
variant="outline_bg"
onClick={() => handlePopUpToggle("certificateExport", false)}
>
Cancel
</Button>
<Button
colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faDownload} />}
onClick={handleExport}
disabled={!serialNumber || !isFormValid()}
>
Export {selectedFormat.toUpperCase()}
</Button>
</div>
</div>
</ModalContent>
</Modal>
);
};
@@ -9,10 +9,11 @@ import {
ProjectPermissionSub,
useProject
} from "@app/context";
import { useDeleteCert } from "@app/hooks/api";
import { useDeleteCert, useDownloadCertPkcs12 } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { CertificateCertModal } from "./CertificateCertModal";
import { CertificateExportModal, ExportOptions } from "./CertificateExportModal";
import { CertificateImportModal } from "./CertificateImportModal";
import { CertificateIssuanceModal } from "./CertificateIssuanceModal";
import { CertificateManagePkiSyncsModal } from "./CertificateManagePkiSyncsModal";
@@ -24,11 +25,13 @@ import { CertificatesTable } from "./CertificatesTable";
export const CertificatesSection = () => {
const { currentProject } = useProject();
const { mutateAsync: deleteCert } = useDeleteCert();
const { mutateAsync: downloadCertPkcs12 } = useDownloadCertPkcs12();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"issueCertificate",
"certificateImport",
"certificateCert",
"certificateExport",
"deleteCertificate",
"revokeCertificate",
"manageRenewal",
@@ -49,6 +52,45 @@ export const CertificatesSection = () => {
handlePopUpClose("deleteCertificate");
};
const handleCertificateExport = async (
format: "pem" | "pkcs12",
serialNumber: string,
options?: ExportOptions
) => {
if (format === "pem") {
handlePopUpOpen("certificateCert", { serialNumber });
} else if (format === "pkcs12") {
if (!currentProject?.slug) return;
if (!options?.pkcs12?.password || !options?.pkcs12?.alias) {
createNotification({
text: "Password and alias are required for PKCS12 export",
type: "error"
});
return;
}
try {
await downloadCertPkcs12({
serialNumber,
projectSlug: currentProject.slug,
password: options.pkcs12.password,
alias: options.pkcs12.alias
});
createNotification({
text: "PKCS12 certificate downloaded successfully",
type: "success"
});
} catch (error: any) {
createNotification({
text: error?.message || "Failed to download PKCS12 certificate",
type: "error"
});
}
}
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
@@ -84,6 +126,11 @@ export const CertificatesSection = () => {
<CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateImportModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateCertModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateExportModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
onFormatSelected={handleCertificateExport}
/>
<CertificateManageRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateRevocationModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
@@ -68,6 +68,7 @@ type Props = {
"deleteCertificate",
"revokeCertificate",
"certificateCert",
"certificateExport",
"manageRenewal",
"renewCertificate",
"managePkiSyncs"
@@ -275,7 +276,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () =>
handlePopUpOpen("certificateCert", {
handlePopUpOpen("certificateExport", {
serialNumber: certificate.serialNumber
})
}
@@ -2,6 +2,7 @@ import { useState } from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { Button, DeleteActionModal } from "@app/components/v2";
import { useProjectPermission } from "@app/context";
@@ -9,6 +10,7 @@ import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { usePopUp } from "@app/hooks";
import {
TCertificateProfileWithDetails,
useDeleteCertificateProfile
@@ -29,6 +31,7 @@ export const CertificateProfilesTab = () => {
const [selectedProfile, setSelectedProfile] = useState<TCertificateProfileWithDetails | null>(
null
);
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
const deleteProfile = useDeleteCertificateProfile();
@@ -99,7 +102,17 @@ export const CertificateProfilesTab = () => {
onDeleteProfile={handleDeleteProfile}
/>
<CreateProfileModal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} />
<CreateProfileModal
isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
handlePopUpOpen={handlePopUpOpen}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
text="Your current plan does not include access to managing template enrollment options for ACME. To unlock this feature, please upgrade to Infisical Enterprise plan."
/>
{selectedProfile && (
<>
@@ -109,6 +122,7 @@ export const CertificateProfilesTab = () => {
setIsEditModalOpen(false);
setSelectedProfile(null);
}}
handlePopUpOpen={handlePopUpOpen}
profile={selectedProfile}
mode="edit"
/>
@@ -18,8 +18,7 @@ import {
TextArea,
Tooltip
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { useProject } from "@app/context";
import { useProject, useSubscription } from "@app/context";
import { useListCasByProjectId } from "@app/hooks/api/ca/queries";
import {
TCertificateProfileWithDetails,
@@ -29,6 +28,7 @@ import {
useUpdateCertificateProfile
} from "@app/hooks/api/certificateProfiles";
import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplates/queries";
import { UsePopUpState } from "@app/hooks/usePopUp";
const createSchema = z
.object({
@@ -151,12 +151,25 @@ export type FormData = z.infer<typeof createSchema>;
interface Props {
isOpen: boolean;
onClose: () => void;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["upgradePlan"]>,
data?: {
isEnterpriseFeature?: boolean;
}
) => void;
profile?: TCertificateProfileWithDetails;
mode?: "create" | "edit";
}
export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }: Props) => {
export const CreateProfileModal = ({
isOpen,
onClose,
handlePopUpOpen,
profile,
mode = "create"
}: Props) => {
const { currentProject } = useProject();
const { subscription } = useSubscription();
const { data: caData } = useListCasByProjectId(currentProject?.id || "");
const { data: templateData } = useListCertificateTemplatesV2({
@@ -248,6 +261,15 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
}, [isEdit, profile, reset]);
const onFormSubmit = async (data: FormData) => {
if (!isEdit && !subscription?.pkiAcme && data.enrollmentType === "acme") {
reset();
onClose();
handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true
});
return;
}
if (!currentProject?.id && !isEdit) return;
if (isEdit) {
@@ -467,7 +489,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
>
<SelectItem value="api">API</SelectItem>
<SelectItem value="est">EST</SelectItem>
{envConfig.ACME_FEATURE_ENABLED && <SelectItem value="acme">ACME</SelectItem>}
<SelectItem value="acme">ACME</SelectItem>
</Select>
</FormControl>
)}
@@ -10,7 +10,7 @@ import {
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { useDeleteCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/mutations";
import { TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types";
import { type TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types";
import { CreateTemplateModal } from "./CreateTemplateModal";
import { TemplateList } from "./TemplateList";
@@ -84,7 +84,12 @@ export const CertificateTemplatesV2Tab = () => {
<TemplateList onEditTemplate={handleEditTemplate} onDeleteTemplate={handleDeleteTemplate} />
<CreateTemplateModal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} />
<CreateTemplateModal
isOpen={isCreateModalOpen}
onClose={() => {
setIsCreateModalOpen(false);
}}
/>
{selectedTemplate && (
<>
@@ -36,13 +36,18 @@ import {
CertDurationUnit,
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType,
SAN_INCLUDE_OPTIONS,
SAN_TYPE_OPTIONS,
SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS,
SUBJECT_ATTRIBUTE_TYPE_OPTIONS
SUBJECT_ATTRIBUTE_TYPE_OPTIONS,
TEMPLATE_PRESET_IDS,
type TemplatePresetId
} from "./shared/certificate-constants";
import { CERTIFICATE_TEMPLATE_PRESETS } from "./shared/template-presets";
import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared";
export type FormData = TemplateFormData;
@@ -91,7 +96,7 @@ const SIGNATURE_ALGORITHMS = [
"SHA256-ECDSA",
"SHA384-ECDSA",
"SHA512-ECDSA"
];
] as const;
const KEY_ALGORITHMS = [
"RSA-2048",
@@ -100,7 +105,7 @@ const KEY_ALGORITHMS = [
"ECDSA-P256",
"ECDSA-P384",
"ECDSA-P521"
];
] as const;
export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }: Props) => {
const { currentProject } = useProject();
@@ -117,7 +122,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
subj.allowed.forEach((allowedValue) => {
attributes.push({
type: subj.type as CertSubjectAttributeType,
include: "optional",
include: CertSubjectAttributeInclude.OPTIONAL,
value: [allowedValue]
});
});
@@ -126,7 +131,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
subj.denied.forEach((deniedValue) => {
attributes.push({
type: subj.type as CertSubjectAttributeType,
include: "prohibit",
include: CertSubjectAttributeInclude.PROHIBIT,
value: [deniedValue]
});
});
@@ -141,7 +146,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.required.forEach((requiredValue) => {
subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType,
include: "mandatory",
include: CertSanInclude.MANDATORY,
value: [requiredValue]
});
});
@@ -150,7 +155,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.allowed.forEach((allowedValue) => {
subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType,
include: "optional",
include: CertSanInclude.OPTIONAL,
value: [allowedValue]
});
});
@@ -159,7 +164,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.denied.forEach((deniedValue) => {
subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType,
include: "prohibit",
include: CertSanInclude.PROHIBIT,
value: [deniedValue]
});
});
@@ -219,6 +224,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
};
return {
preset: TEMPLATE_PRESET_IDS.CUSTOM,
name: templateData.name || "",
description: templateData.description || "",
attributes,
@@ -231,25 +237,30 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
};
};
const getDefaultValues = (): FormData => ({
name: "",
description: "",
attributes: [],
keyUsages: { requiredUsages: [], optionalUsages: [] },
extendedKeyUsages: { requiredUsages: [], optionalUsages: [] },
subjectAlternativeNames: [],
validity: {
maxDuration: { value: 365, unit: CertDurationUnit.DAYS }
},
signatureAlgorithm: {
allowedAlgorithms: []
},
keyAlgorithm: {
allowedKeyTypes: []
}
});
const getDefaultValues = (): FormData & { preset: TemplatePresetId } => {
return {
preset: TEMPLATE_PRESET_IDS.CUSTOM,
name: "",
description: "",
attributes: [],
keyUsages: { requiredUsages: [], optionalUsages: [] },
extendedKeyUsages: { requiredUsages: [], optionalUsages: [] },
subjectAlternativeNames: [],
validity: {
maxDuration: { value: 365, unit: CertDurationUnit.DAYS }
},
signatureAlgorithm: {
allowedAlgorithms: []
},
keyAlgorithm: {
allowedKeyTypes: []
}
};
};
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<FormData>({
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<
FormData & { preset: TemplatePresetId }
>({
resolver: zodResolver(templateSchema),
defaultValues: getDefaultValues(),
mode: "onChange",
@@ -260,7 +271,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
useEffect(() => {
if (isEdit && template) {
const convertedData = convertApiToUiFormat(template);
reset(convertedData);
reset({ ...convertedData, preset: TEMPLATE_PRESET_IDS.CUSTOM });
} else if (!isEdit) {
reset(getDefaultValues());
}
@@ -273,6 +284,37 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: [],
optionalUsages: []
};
const watchedPreset = watch("preset") || TEMPLATE_PRESET_IDS.CUSTOM;
const handlePresetChange = (presetId: TemplatePresetId) => {
setValue("preset", presetId);
if (presetId === TEMPLATE_PRESET_IDS.CUSTOM) {
return;
}
const selectedPreset = CERTIFICATE_TEMPLATE_PRESETS.find((p) => p.id === presetId);
if (selectedPreset) {
if (selectedPreset.formData.keyUsages) {
setValue("keyUsages", selectedPreset.formData.keyUsages);
}
if (selectedPreset.formData.extendedKeyUsages) {
setValue("extendedKeyUsages", selectedPreset.formData.extendedKeyUsages);
}
if (selectedPreset.formData.attributes) {
setValue("attributes", selectedPreset.formData.attributes);
}
if (selectedPreset.formData.subjectAlternativeNames) {
setValue("subjectAlternativeNames", selectedPreset.formData.subjectAlternativeNames);
}
if (selectedPreset.formData.signatureAlgorithm) {
setValue("signatureAlgorithm", selectedPreset.formData.signatureAlgorithm);
}
if (selectedPreset.formData.keyAlgorithm) {
setValue("keyAlgorithm", selectedPreset.formData.keyAlgorithm);
}
}
};
const consolidateByType = <
T extends { type: string; allowed?: string[]; required?: string[]; denied?: string[] }
@@ -309,9 +351,17 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
data.attributes?.map((attr) => {
const result: AttributeTransform = { type: attr.type };
if (attr.include === "optional" && attr.value && attr.value.length > 0) {
if (
attr.include === CertSubjectAttributeInclude.OPTIONAL &&
attr.value &&
attr.value.length > 0
) {
result.allowed = attr.value;
} else if (attr.include === "prohibit" && attr.value && attr.value.length > 0) {
} else if (
attr.include === CertSubjectAttributeInclude.PROHIBIT &&
attr.value &&
attr.value.length > 0
) {
result.denied = attr.value;
}
@@ -322,11 +372,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
data.subjectAlternativeNames?.map((san) => {
const result: SanTransform = { type: san.type };
if (san.include === "mandatory" && san.value && san.value.length > 0) {
if (san.include === CertSanInclude.MANDATORY && san.value && san.value.length > 0) {
result.required = san.value;
} else if (san.include === "optional" && san.value && san.value.length > 0) {
} else if (san.include === CertSanInclude.OPTIONAL && san.value && san.value.length > 0) {
result.allowed = san.value;
} else if (san.include === "prohibit" && san.value && san.value.length > 0) {
} else if (san.include === CertSanInclude.PROHIBIT && san.value && san.value.length > 0) {
result.denied = san.value;
}
@@ -464,11 +514,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: ["*"]
};
setValue("attributes", [...watchedAttributes, newAttribute]);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const removeAttribute = (index: number) => {
const newAttributes = watchedAttributes.filter((_, i) => i !== index);
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const addSan = () => {
@@ -478,11 +536,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: ["*"]
};
setValue("subjectAlternativeNames", [...watchedSans, newSan]);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const removeSan = (index: number) => {
const newSans = watchedSans.filter((_, i) => i !== index);
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const handleKeyUsagesChange = (usages: {
@@ -493,6 +559,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: usages.requiredUsages,
optionalUsages: usages.optionalUsages
});
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const handleExtendedKeyUsagesChange = (usages: {
@@ -503,6 +573,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: usages.requiredUsages,
optionalUsages: usages.optionalUsages
});
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
return (
@@ -555,6 +629,33 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
</FormControl>
)}
/>
<Controller
control={control}
name="preset"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Template Preset"
isError={Boolean(error)}
errorText={error?.message}
>
<Select
value={field.value}
onValueChange={handlePresetChange}
className="w-full"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value={TEMPLATE_PRESET_IDS.CUSTOM}>Custom</SelectItem>
{CERTIFICATE_TEMPLATE_PRESETS.map((preset) => (
<SelectItem key={preset.id} value={preset.id}>
{preset.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
</div>
<AccordionItem value="attributes">
<AccordionTrigger>Subject Attributes</AccordionTrigger>
@@ -595,6 +696,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
type: value as CertSubjectAttributeType
};
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-48"
>
@@ -615,6 +720,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value as (typeof SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS)[number]
};
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-32"
>
@@ -635,6 +744,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: e.target.value.trim() ? [e.target.value.trim()] : []
};
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className={`flex-1 ${
attr.value && attr.value.length > 0 && attr.value[0] === ""
@@ -701,6 +814,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
type: value as CertSubjectAlternativeNameType
};
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-36"
>
@@ -720,6 +837,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
include: value as (typeof SAN_INCLUDE_OPTIONS)[number]
};
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-32"
>
@@ -740,6 +861,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: e.target.value.trim() ? [e.target.value.trim()] : []
};
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className={`flex-1 ${
san.value && san.value.length > 0 && san.value[0] === ""
@@ -150,8 +150,19 @@ export const SUBJECT_ATTRIBUTE_TYPE_OPTIONS = Object.values(CertSubjectAttribute
export const ATTRIBUTE_RULE_OPTIONS = Object.values(CertAttributeRule);
export const SAN_EFFECT_OPTIONS = Object.values(CertSanEffect);
export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = ["optional", "prohibit"] as const;
export const SAN_INCLUDE_OPTIONS = ["mandatory", "optional", "prohibit"] as const;
export enum CertSubjectAttributeInclude {
OPTIONAL = "optional",
PROHIBIT = "prohibit"
}
export enum CertSanInclude {
MANDATORY = "mandatory",
OPTIONAL = "optional",
PROHIBIT = "prohibit"
}
export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = Object.values(CertSubjectAttributeInclude);
export const SAN_INCLUDE_OPTIONS = Object.values(CertSanInclude);
export const USAGE_STATES = {
REQUIRED: "required",
@@ -239,3 +250,27 @@ export const mapTemplateKeyAlgorithmToApi = (templateFormat: string): string =>
};
return mapping[templateFormat] || templateFormat;
};
export const TEMPLATE_PRESET_IDS = {
CUSTOM: "custom",
TLS_SERVER: "tls-server",
TLS_CLIENT: "tls-client",
CODE_SIGNING: "code-signing",
DEVICE: "device",
USER: "user",
EMAIL_PROTECTION: "email-protection",
DUAL_PURPOSE_SERVER: "dual-purpose-server"
} as const;
export type TemplatePresetId = (typeof TEMPLATE_PRESET_IDS)[keyof typeof TEMPLATE_PRESET_IDS];
export const ALGORITHM_FAMILIES = {
ECDSA: {
signature: ["SHA256-ECDSA", "SHA384-ECDSA", "SHA512-ECDSA"] as const,
key: ["ECDSA-P256", "ECDSA-P384", "ECDSA-P521"] as const
},
RSA: {
signature: ["SHA256-RSA", "SHA384-RSA", "SHA512-RSA"] as const,
key: ["RSA-2048", "RSA-3072", "RSA-4096"] as const
}
} as const;
@@ -4,21 +4,22 @@ import {
CertDurationUnit,
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType,
SAN_INCLUDE_OPTIONS,
SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS
TEMPLATE_PRESET_IDS
} from "./certificate-constants";
export const uiAttributeSchema = z.object({
type: z.nativeEnum(CertSubjectAttributeType),
include: z.enum(SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS),
include: z.nativeEnum(CertSubjectAttributeInclude),
value: z.array(z.string().min(1, "Value cannot be empty"))
});
export const uiSanSchema = z.object({
type: z.nativeEnum(CertSubjectAlternativeNameType),
include: z.enum(SAN_INCLUDE_OPTIONS),
include: z.nativeEnum(CertSanInclude),
value: z.array(z.string().min(1, "Value cannot be empty"))
});
@@ -51,7 +52,21 @@ export const uiKeyAlgorithmSchema = z.object({
.min(1, "At least one key type must be selected")
});
export const uiPresetSchema = z
.enum([
TEMPLATE_PRESET_IDS.CUSTOM,
TEMPLATE_PRESET_IDS.TLS_SERVER,
TEMPLATE_PRESET_IDS.TLS_CLIENT,
TEMPLATE_PRESET_IDS.CODE_SIGNING,
TEMPLATE_PRESET_IDS.DEVICE,
TEMPLATE_PRESET_IDS.USER,
TEMPLATE_PRESET_IDS.EMAIL_PROTECTION,
TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER
])
.default(TEMPLATE_PRESET_IDS.CUSTOM);
export const templateSchema = z.object({
preset: uiPresetSchema,
name: z
.string()
.trim()
@@ -0,0 +1,385 @@
import {
ALGORITHM_FAMILIES,
CertDurationUnit,
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType,
TEMPLATE_PRESET_IDS,
type TemplatePresetId
} from "./certificate-constants";
import { TemplateFormData } from ".";
export interface CertificateTemplatePreset {
readonly id: TemplatePresetId;
readonly name: string;
readonly description: string;
readonly useCase: string;
readonly formData: Omit<TemplateFormData, "preset">;
}
export const CERTIFICATE_TEMPLATE_PRESETS: CertificateTemplatePreset[] = [
{
id: TEMPLATE_PRESET_IDS.TLS_SERVER,
name: "TLS Server Certificate",
description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.",
useCase: "Web servers, API endpoints, HTTPS services",
formData: {
name: "TLS Server Certificate",
description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_ENCIPHERMENT]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.SERVER_AUTH],
optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.IP_ADDRESS,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.TLS_CLIENT,
name: "TLS Client Certificate",
description: "Client certificate for mutual TLS authentication and API access.",
useCase: "Client authentication, mTLS, API authentication",
formData: {
name: "TLS Client Certificate",
description: "Client certificate for mutual TLS authentication and API access.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH],
optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.CODE_SIGNING,
name: "Code Signing Certificate",
description:
"Certificate for signing software, executables, and packages. Requires hardware security modules.",
useCase: "Software signing, executable authentication, package validation",
formData: {
name: "Code Signing Certificate",
description:
"Certificate for signing software, executables, and packages. Requires hardware security modules.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.CODE_SIGNING],
optionalUsages: [
CertExtendedKeyUsageType.CODE_SIGNING,
CertExtendedKeyUsageType.TIME_STAMPING
]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.DEVICE,
name: "Device Certificate",
description:
"Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.",
useCase: "Device authentication, IoT security, embedded systems",
formData: {
name: "Device Certificate",
description:
"Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH],
optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.SERVER_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.IP_ADDRESS,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.USER,
name: "User Certificate",
description:
"Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.",
useCase: "Personal authentication, smart cards, email protection",
formData: {
name: "User Certificate",
description:
"Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [
CertKeyUsageType.DIGITAL_SIGNATURE,
CertKeyUsageType.KEY_ENCIPHERMENT,
CertKeyUsageType.KEY_AGREEMENT
]
},
extendedKeyUsages: {
requiredUsages: [
CertExtendedKeyUsageType.CLIENT_AUTH,
CertExtendedKeyUsageType.EMAIL_PROTECTION
],
optionalUsages: [
CertExtendedKeyUsageType.CLIENT_AUTH,
CertExtendedKeyUsageType.EMAIL_PROTECTION
]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.EMAIL_PROTECTION,
name: "Email Protection Certificate",
description: "S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.",
useCase: "Email encryption, digital signing, secure messaging",
formData: {
name: "Email Protection Certificate",
description:
"S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [
CertKeyUsageType.DIGITAL_SIGNATURE,
CertKeyUsageType.KEY_ENCIPHERMENT,
CertKeyUsageType.KEY_AGREEMENT
]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION],
optionalUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER,
name: "Dual-Purpose Server Certificate",
description:
"Certificate for services requiring both server and client authentication capabilities",
useCase: "Microservices, service mesh, dual authentication",
formData: {
name: "Dual-Purpose Server Certificate",
description:
"Certificate for services requiring both server and client authentication capabilities",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [
CertKeyUsageType.DIGITAL_SIGNATURE,
CertKeyUsageType.KEY_ENCIPHERMENT,
CertKeyUsageType.KEY_AGREEMENT
]
},
extendedKeyUsages: {
requiredUsages: [
CertExtendedKeyUsageType.SERVER_AUTH,
CertExtendedKeyUsageType.CLIENT_AUTH
],
optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH, CertExtendedKeyUsageType.CLIENT_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.IP_ADDRESS,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
}
];
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -76,7 +77,9 @@ export const IdentityAliCloudAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityAliCloudAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAliCloudAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -144,7 +147,7 @@ export const IdentityAliCloudAuthForm = ({
if (data) {
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
allowedArns,
identityId,
accessTokenTTL: Number(accessTokenTTL),
@@ -154,7 +157,7 @@ export const IdentityAliCloudAuthForm = ({
});
} else {
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
allowedArns,
accessTokenTTL: Number(accessTokenTTL),
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -78,7 +79,9 @@ export const IdentityAwsAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -154,7 +157,7 @@ export const IdentityAwsAuthForm = ({
if (data) {
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
@@ -166,7 +169,7 @@ export const IdentityAwsAuthForm = ({
});
} else {
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
stsEndpoint: stsEndpoint || "",
allowedPrincipalArns: allowedPrincipalArns || "",
@@ -176,10 +179,8 @@ export const IdentityAwsAuthForm = ({
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
handlePopUpToggle("identityAuthMethod", false);
}
handlePopUpToggle("identityAuthMethod", false);
createNotification({
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
type: "success"
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -73,7 +74,9 @@ export const IdentityAzureAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityAzureAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAzureAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -150,7 +153,7 @@ export const IdentityAzureAuthForm = ({
if (data) {
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
tenantId,
resource,
@@ -162,7 +165,7 @@ export const IdentityAzureAuthForm = ({
});
} else {
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
tenantId: tenantId || "",
resource: resource || "",
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -76,7 +77,9 @@ export const IdentityGcpAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityGcpAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityGcpAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -160,7 +163,7 @@ export const IdentityGcpAuthForm = ({
if (data) {
await updateMutateAsync({
identityId,
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
type,
allowedServiceAccounts,
allowedProjects,
@@ -173,7 +176,7 @@ export const IdentityGcpAuthForm = ({
} else {
await addMutateAsync({
identityId,
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
type,
allowedServiceAccounts: allowedServiceAccounts || "",
allowedProjects: allowedProjects || "",
@@ -191,7 +194,6 @@ export const IdentityGcpAuthForm = ({
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
type: "success"
});
reset();
};
@@ -4,6 +4,7 @@ import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -109,7 +110,9 @@ export const IdentityJwtAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityJwtAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityJwtAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -227,7 +230,7 @@ export const IdentityJwtAuthForm = ({
if (data) {
await updateMutateAsync({
identityId,
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
configurationType,
jwksUrl,
jwksCaCert,
@@ -252,7 +255,7 @@ export const IdentityJwtAuthForm = ({
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
boundSubject,
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -266,7 +269,6 @@ export const IdentityJwtAuthForm = ({
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
type: "success"
});
reset();
};
@@ -4,6 +4,7 @@ import { faInfoCircle, faPlus, faXmark } from "@fortawesome/free-solid-svg-icons
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -117,7 +118,9 @@ export const IdentityKubernetesAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityKubernetesAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityKubernetesAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -318,7 +321,7 @@ export const IdentityKubernetesAuthForm = ({
if (data) {
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api
? {
kubernetesHost: kubernetesHost || ""
@@ -341,7 +344,7 @@ export const IdentityKubernetesAuthForm = ({
});
} else {
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api
? {
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faQuestionCircle, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import ms from "ms";
import { z } from "zod";
@@ -168,7 +169,9 @@ export const IdentityLdapAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityLdapAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityLdapAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -345,7 +348,7 @@ export const IdentityLdapAuthForm = ({
ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000;
const basePayload = {
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
searchFilter,
ldapCaCertificate,
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -84,7 +85,9 @@ export const IdentityOciAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityOciAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityOciAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -156,7 +159,7 @@ export const IdentityOciAuthForm = ({
if (data) {
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
tenancyOcid,
allowedUsernames,
identityId,
@@ -167,7 +170,7 @@ export const IdentityOciAuthForm = ({
});
} else {
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
tenancyOcid,
allowedUsernames: allowedUsernames || undefined,
@@ -184,7 +187,6 @@ export const IdentityOciAuthForm = ({
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
type: "success"
});
reset();
};
@@ -4,6 +4,7 @@ import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -96,7 +97,9 @@ export const IdentityOidcAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityOidcAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityOidcAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -211,7 +214,7 @@ export const IdentityOidcAuthForm = ({
if (data) {
await updateMutateAsync({
identityId,
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
oidcDiscoveryUrl,
caCert,
boundIssuer,
@@ -238,7 +241,7 @@ export const IdentityOidcAuthForm = ({
? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value]))
: undefined,
boundSubject,
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -252,7 +255,6 @@ export const IdentityOidcAuthForm = ({
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
type: "success"
});
reset();
};
@@ -1,5 +1,8 @@
import { faLink, faPlus } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion";
import { LinkIcon, PlusIcon } from "lucide-react";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
@@ -14,17 +17,23 @@ import {
} from "@app/context";
import { OrgPermissionMachineIdentityAuthTemplateActions } from "@app/context/OrgPermissionContext/types";
import { withPermission } from "@app/hoc";
import { useDeleteIdentity } from "@app/hooks/api";
import { useDeleteOrgIdentity } from "@app/hooks/api";
import { useDeleteIdentityAuthTemplate } from "@app/hooks/api/identityAuthTemplates";
import { usePopUp } from "@app/hooks/usePopUp";
import { IdentityAuthTemplateModal } from "./IdentityAuthTemplateModal";
import { IdentityAuthTemplatesTable } from "./IdentityAuthTemplatesTable";
import { IdentityLinkForm } from "./IdentityLinkForm";
import { IdentityModal } from "./IdentityModal";
import { IdentityTable } from "./IdentityTable";
import { IdentityTokenAuthTokenModal } from "./IdentityTokenAuthTokenModal";
import { MachineAuthTemplateUsagesModal } from "./MachineAuthTemplateUsagesModal";
import { OrgIdentityLinkForm } from "./OrgIdentityLinkForm";
import { OrgIdentityModal } from "./OrgIdentityModal";
enum IdentityWizardSteps {
SelectAction = "select-action",
LinkIdentity = "link-identity",
OrganizationIdentity = "project-identity"
}
export const IdentitySection = withPermission(
() => {
@@ -32,7 +41,9 @@ export const IdentitySection = withPermission(
const { currentOrg, isSubOrganization } = useOrganization();
const orgId = currentOrg?.id || "";
const { mutateAsync: deleteMutateAsync } = useDeleteIdentity();
const [wizardStep, setWizardStep] = useState(IdentityWizardSteps.SelectAction);
const { mutateAsync: deleteMutateAsync } = useDeleteOrgIdentity();
const { mutateAsync: deleteTemplateMutateAsync } = useDeleteIdentityAuthTemplate();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"identity",
@@ -46,7 +57,7 @@ export const IdentitySection = withPermission(
"editTemplate",
"deleteTemplate",
"viewUsages",
"linkIdentity"
"addOptions"
] as const);
const isMoreIdentitiesAllowed = subscription?.identityLimit
@@ -58,7 +69,7 @@ export const IdentitySection = withPermission(
const onDeleteIdentitySubmit = async (identityId: string) => {
await deleteMutateAsync({
identityId,
organizationId: orgId
orgId
});
createNotification({
@@ -91,50 +102,38 @@ export const IdentitySection = withPermission(
<p className="text-xl font-medium text-mineshaft-100">Identities</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" />
</div>
{isSubOrganization && (
<div className="flex items-center">
<OrgPermissionCan
I={OrgPermissionIdentityActions.Create}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Button
variant="plain"
colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faLink} />}
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
handlePopUpOpen("linkIdentity");
if (!isMoreIdentitiesAllowed && !isEnterprise) {
handlePopUpOpen("upgradePlan", {
description:
"You can add more identities if you upgrade your Infisical Pro plan."
});
return;
}
if (!isSubOrganization) {
setWizardStep(IdentityWizardSteps.OrganizationIdentity);
}
handlePopUpOpen("identity");
}}
isDisabled={!isAllowed}
>
Link Identity
Create Identity
</Button>
)}
</OrgPermissionCan>
)}
<OrgPermissionCan
I={OrgPermissionIdentityActions.Create}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (!isMoreIdentitiesAllowed && !isEnterprise) {
handlePopUpOpen("upgradePlan", {
text: "You have reached the maximum number of identities allowed on your current plan. Upgrade to Infisical Pro plan to add more identities."
});
return;
}
handlePopUpOpen("identity");
}}
isDisabled={!isAllowed}
>
Create Identity
</Button>
)}
</OrgPermissionCan>
</div>
</div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
</div>
@@ -173,7 +172,6 @@ export const IdentitySection = withPermission(
</div>
<IdentityAuthTemplatesTable handlePopUpOpen={handlePopUpOpen} />
</div>
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<IdentityAuthTemplateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<MachineAuthTemplateUsagesModal
isOpen={popUp.viewUsages.isOpen}
@@ -187,19 +185,99 @@ export const IdentitySection = withPermission(
?.name || ""
}
/>
<IdentityTokenAuthTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<Modal
isOpen={popUp.linkIdentity.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("linkIdentity", isOpen)}
isOpen={popUp.identity.isOpen}
onOpenChange={(open) => {
handlePopUpToggle("identity", open);
if (!open) {
setWizardStep(IdentityWizardSteps.SelectAction);
}
}}
>
<ModalContent
title="Assign Existing Identity"
subTitle="Assign an existing identity from your root organization to the sub organization. The identity will continue to be managed at its original scope."
bodyClassName="overflow-visible"
title="Add Identity"
subTitle={
isSubOrganization ? "Create a new identity or assign an existing identity" : undefined
}
>
<IdentityLinkForm onClose={() => handlePopUpClose("linkIdentity")} />
<AnimatePresence mode="wait">
{wizardStep === IdentityWizardSteps.SelectAction && (
<motion.div
key="select-type-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<div
className="cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(IdentityWizardSteps.OrganizationIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(IdentityWizardSteps.OrganizationIdentity);
}
}}
>
<div className="flex items-center gap-2">
<PlusIcon size="1rem" />
<div>Create New Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Create a new machine identity specifically for this sub-organization. This
identity will be managed at the sub-organization level.
</div>
</div>
<div
className="mt-4 cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(IdentityWizardSteps.LinkIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(IdentityWizardSteps.LinkIdentity);
}
}}
>
<div className="flex items-center gap-2">
<LinkIcon size="1rem" />
<div>Assign Existing Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Assign an existing identity from your parent organization. The identity will
continue to be managed at its original scope.
</div>
</div>
</motion.div>
)}
{wizardStep === IdentityWizardSteps.OrganizationIdentity && (
<motion.div
key="identity-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<OrgIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</motion.div>
)}
{wizardStep === IdentityWizardSteps.LinkIdentity && (
<motion.div
key="link-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<OrgIdentityLinkForm onClose={() => handlePopUpClose("identity")} />
</motion.div>
)}
</AnimatePresence>
</ModalContent>
</Modal>
<IdentityTokenAuthTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure you want to delete ${
@@ -2,7 +2,6 @@ import { useCallback, useState } from "react";
import {
faArrowDown,
faArrowUp,
faBuilding,
faCheckCircle,
faChevronRight,
faEdit,
@@ -46,6 +45,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { Badge, OrgIcon, SubOrgIcon } from "@app/components/v3";
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import {
getUserTablePreference,
@@ -56,8 +56,8 @@ import { usePagination, useResetPageHelper } from "@app/hooks";
import {
identityAuthToNameMap,
useGetOrgRoles,
useSearchIdentities,
useUpdateIdentity
useSearchOrgIdentityMemberships,
useUpdateOrgIdentity
} from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { OrgIdentityOrderBy } from "@app/hooks/api/organization/types";
@@ -110,9 +110,9 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const organizationId = currentOrg?.id || "";
const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
const { mutateAsync: updateMutateAsync } = useUpdateOrgIdentity();
const { data, isPending, isFetching } = useSearchIdentities({
const { data, isPending, isFetching } = useSearchOrgIdentityMemberships({
offset,
limit,
orderDirection,
@@ -293,6 +293,8 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
lastLoginAuthMethod,
lastLoginTime
}) => {
const isSubOrgIdentity = currentOrg.id === orgId;
return (
<Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
@@ -357,10 +359,19 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
</Td>
{isSubOrganization && (
<Td>
<p className="truncate">
<FontAwesomeIcon size="sm" className="mr-1.5" icon={faBuilding} />
{currentOrg.id === orgId ? "Sub Organization" : "Root Organization"}
</p>
<Badge variant={isSubOrgIdentity ? "sub-org" : "org"}>
{isSubOrgIdentity ? (
<>
<SubOrgIcon />
Sub-Organization
</>
) : (
<>
<OrgIcon />
Root Organization
</>
)}
</Badge>
</Td>
)}
<Td>
@@ -394,7 +405,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
}}
isDisabled={!isAllowed}
>
Edit Identity
Edit Identity {isSubOrgIdentity ? "" : "Membership"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -414,7 +425,9 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
isDisabled={!isAllowed}
icon={<FontAwesomeIcon icon={faTrash} />}
>
Delete Identity
{isSubOrgIdentity
? "Delete Identity"
: "Remove From Sub-Organization"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -71,7 +72,9 @@ export const IdentityTlsCertAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityTlsCertAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityTlsCertAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -141,7 +144,7 @@ export const IdentityTlsCertAuthForm = ({
if (data) {
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
caCertificate,
allowedCommonNames: allowedCommonNames || null,
identityId,
@@ -152,7 +155,7 @@ export const IdentityTlsCertAuthForm = ({
});
} else {
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
caCertificate,
allowedCommonNames: allowedCommonNames || undefined,
@@ -169,7 +172,6 @@ export const IdentityTlsCertAuthForm = ({
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
type: "success"
});
reset();
};
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -70,7 +71,9 @@ export const IdentityTokenAuthForm = ({
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { projectId } = useParams({
strict: false
});
const { mutateAsync: addMutateAsync } = useAddIdentityTokenAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityTokenAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
@@ -134,7 +137,7 @@ export const IdentityTokenAuthForm = ({
if (data) {
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
@@ -143,7 +146,7 @@ export const IdentityTokenAuthForm = ({
});
} else {
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "@tanstack/react-router";
import ms from "ms";
import { z } from "zod";
@@ -105,6 +106,9 @@ export const IdentityUniversalAuthForm = ({
identityId,
isUpdate
}: Props) => {
const { projectId } = useParams({
strict: false
});
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
@@ -232,7 +236,7 @@ export const IdentityUniversalAuthForm = ({
if (data) {
// update universal auth configuration
await updateMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
clientSecretTrustedIps,
accessTokenTTL: Number(accessTokenTTL),
@@ -249,7 +253,7 @@ export const IdentityUniversalAuthForm = ({
// create new universal auth configuration
await addMutateAsync({
organizationId: orgId,
...(projectId ? { projectId } : { organizationId: orgId }),
identityId,
clientSecretTrustedIps,
accessTokenTTL: Number(accessTokenTTL),
@@ -270,7 +274,6 @@ export const IdentityUniversalAuthForm = ({
text: `Successfully ${isUpdate ? "updated" : "created"} auth method`,
type: "success"
});
reset();
};
@@ -1,13 +1,15 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FilterableSelect, FormControl } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { useGetAvailableOrgIdentities, useGetOrgRoles } from "@app/hooks/api";
import { useGetOrgRoles } from "@app/hooks/api";
import { useCreateOrgIdentityMembership } from "@app/hooks/api/orgIdentityMembership";
import { orgIdentityMembershipQuery } from "@app/hooks/api/orgIdentityMembership/queries";
const schema = z
.object({
@@ -22,15 +24,26 @@ type Props = {
onClose: () => void;
};
export const IdentityLinkForm = ({ onClose }: Props) => {
export const OrgIdentityLinkForm = ({ onClose }: Props) => {
const navigate = useNavigate();
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { data: roles } = useGetOrgRoles(orgId);
// const [searchValue, setSearchValue] = useState("");
//
// const [debouncedSearchValue] = useDebounce(searchValue);
const { mutateAsync: createMutateAsync } = useCreateOrgIdentityMembership();
const { data: rootOrgIdentities, isPending: isRootOrgLoading } = useGetAvailableOrgIdentities();
// TODO: name filter needs to be implemented on backend
const { data: rootOrgIdentities, isPending: isRootOrgLoading } = useQuery({
...orgIdentityMembershipQuery.listAvailable({
// identityName: debouncedSearchValue
}),
placeholderData: (prev) => prev
});
const {
control,
@@ -69,6 +82,7 @@ export const IdentityLinkForm = ({ onClose }: Props) => {
value={value}
onChange={onChange}
placeholder="Select identity..."
// onInputChange={setSearchValue}
options={rootOrgIdentities}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
@@ -94,7 +108,6 @@ export const IdentityLinkForm = ({ onClose }: Props) => {
placeholder="Select role..."
getOptionValue={(option) => option.slug}
getOptionLabel={(option) => option.name}
// menuPortalTarget={document.body}
/>
</FormControl>
)}
@@ -14,13 +14,11 @@ import {
FormLabel,
IconButton,
Input,
Modal,
ModalContent,
Switch
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { findOrgMembershipRole } from "@app/helpers/roles";
import { useCreateIdentity, useGetOrgRoles, useUpdateIdentity } from "@app/hooks/api";
import { useCreateOrgIdentity, useGetOrgRoles, useUpdateOrgIdentity } from "@app/hooks/api";
import { useAddIdentityUniversalAuth } from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -47,7 +45,7 @@ type Props = {
handlePopUpToggle: (popUpName: keyof UsePopUpState<["identity"]>, state?: boolean) => void;
};
export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
export const OrgIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
const navigate = useNavigate();
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
@@ -55,8 +53,8 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
const { data: roles } = useGetOrgRoles(orgId);
const isOrgIdentity = popUp?.identity?.data ? orgId === popUp?.identity?.data?.orgId : true;
const { mutateAsync: createMutateAsync } = useCreateIdentity();
const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
const { mutateAsync: createMutateAsync } = useCreateOrgIdentity();
const { mutateAsync: updateMutateAsync } = useUpdateOrgIdentity();
const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth();
const {
@@ -173,75 +171,66 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
};
return (
<Modal
isOpen={popUp?.identity?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("identity", isOpen);
reset();
}}
>
<ModalContent
bodyClassName="overflow-visible"
title={`${popUp?.identity?.data ? "Update" : "Create"} Identity`}
>
<form onSubmit={handleSubmit(onFormSubmit)}>
{isOrgIdentity && (
<Controller
control={control}
defaultValue=""
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
className="mb-4"
label="Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="Machine 1" />
</FormControl>
)}
/>
<form onSubmit={handleSubmit(onFormSubmit)}>
{isOrgIdentity && (
<Controller
control={control}
defaultValue=""
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
className="mb-4"
label="Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="Machine 1" />
</FormControl>
)}
<Controller
control={control}
name="role"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl
label={`${popUp?.identity?.data ? "Update" : ""} Role`}
errorText={error?.message}
isError={Boolean(error)}
/>
)}
<Controller
control={control}
name="role"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl
label={`${popUp?.identity?.data ? "Update" : ""} Role`}
errorText={error?.message}
isError={Boolean(error)}
>
<FilterableSelect
placeholder="Select role..."
options={roles}
onChange={onChange}
value={value}
getOptionValue={(option) => option.slug}
getOptionLabel={(option) => option.name}
/>
</FormControl>
)}
/>
{isOrgIdentity && (
<Controller
control={control}
name="hasDeleteProtection"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<Switch
className="mr-2 ml-0 bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
containerClassName="flex-row-reverse w-fit"
id="delete-protection-enabled"
thumbClassName="bg-mineshaft-800"
onCheckedChange={onChange}
isChecked={value}
>
<FilterableSelect
placeholder="Select role..."
options={roles}
onChange={onChange}
value={value}
getOptionValue={(option) => option.slug}
getOptionLabel={(option) => option.name}
/>
</FormControl>
)}
/>
{isOrgIdentity && (
<Controller
control={control}
name="hasDeleteProtection"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<Switch
className="mr-2 ml-0 bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
containerClassName="flex-row-reverse w-fit"
id="delete-protection-enabled"
thumbClassName="bg-mineshaft-800"
onCheckedChange={onChange}
isChecked={value}
>
<p>Delete Protection {value ? "Enabled" : "Disabled"}</p>
</Switch>
</FormControl>
)}
/>
<p>Delete Protection {value ? "Enabled" : "Disabled"}</p>
</Switch>
</FormControl>
)}
/>
)}
{isOrgIdentity && (
<>
<div>
<FormLabel label="Metadata" />
</div>
@@ -303,26 +292,26 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
</Button>
</div>
</div>
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{popUp?.identity?.data ? "Update" : "Create"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("identity", false)}
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
</>
)}
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{popUp?.identity?.data ? "Update" : "Create"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("identity", false)}
>
Cancel
</Button>
</div>
</form>
);
};
@@ -4,8 +4,10 @@ import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tab } from "@headlessui/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
FormControl,
@@ -18,8 +20,11 @@ import {
TextArea,
Tooltip
} from "@app/components/v2";
import { OrgPermissionSubjects, useSubscription } from "@app/context";
import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { DistinguishedNameRegex, UserPrincipalNameRegex } from "@app/helpers/string";
import { gatewaysQueryKeys } from "@app/hooks/api";
import {
LdapConnectionMethod,
LdapConnectionProvider,
@@ -84,6 +89,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
defaultValues: appConnection ?? {
app: AppConnection.LDAP,
method: LdapConnectionMethod.SimpleBind,
gatewayId: null,
credentials: {
provider: LdapConnectionProvider.ActiveDirectory,
url: "",
@@ -104,6 +110,8 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
const selectedProvider = watch("credentials.provider");
const sslEnabled = watch("credentials.url")?.startsWith("ldaps://") ?? false;
const { subscription } = useSubscription();
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
return (
<FormProvider {...form}>
@@ -114,6 +122,57 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
}}
>
{!isUpdate && <GenericAppConnectionsFields />}
{subscription.gateway && (
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<div className="grid grid-cols-2 items-center gap-2">
<Controller
name="method"
@@ -7,16 +7,21 @@ import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { DeleteActionModal, PageHeader } from "@app/components/v2";
import { Button, DeleteActionModal, Modal, ModalContent, PageHeader } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useDeleteIdentity, useGetIdentityById } from "@app/hooks/api";
import {
OrgPermissionActions,
OrgPermissionIdentityActions,
OrgPermissionSubjects,
useOrganization
} from "@app/context";
import { useDeleteOrgIdentity, useGetOrgIdentityMembershipById } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal";
import { OrgAccessControlTabSections } from "@app/types/org";
import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
import { IdentityModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal";
import { OrgIdentityModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/OrgIdentityModal";
import {
IdentityAuthenticationSection,
IdentityDetailsSection,
@@ -31,8 +36,8 @@ const Page = () => {
const identityId = params.identityId as string;
const { currentOrg, isSubOrganization } = useOrganization();
const orgId = currentOrg?.id || "";
const { data } = useGetIdentityById(identityId);
const { mutateAsync: deleteIdentity } = useDeleteIdentity();
const { data } = useGetOrgIdentityMembershipById(identityId);
const { mutateAsync: deleteIdentity, isPending: isDeletingIdentity } = useDeleteOrgIdentity();
const isAuthHidden = orgId !== data?.identity?.orgId;
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
@@ -46,7 +51,7 @@ const Page = () => {
const onDeleteIdentitySubmit = async (id: string) => {
await deleteIdentity({
identityId: id,
organizationId: orgId
orgId
});
createNotification({
@@ -81,7 +86,36 @@ const Page = () => {
scope={isSubOrganization ? "namespace" : "org"}
description={`${isSubOrganization ? "Sub-" : ""}Organization Identity`}
title={data.identity.name}
/>
>
<div className="flex items-center gap-2">
{isSubOrganization && data.identity.orgId !== currentOrg.id && (
<OrgPermissionCan
I={OrgPermissionActions.Delete}
a={OrgPermissionSubjects.Identity}
renderTooltip
allowedLabel="Remove from sub-organization"
>
{(isAllowed) => (
<Button
colorSchema="danger"
variant="outline_bg"
size="xs"
isDisabled={!isAllowed}
isLoading={isDeletingIdentity}
onClick={() =>
handlePopUpOpen("deleteIdentity", {
identityId: data.identity.id,
name: data.identity.name
})
}
>
Unlink Identity
</Button>
)}
</OrgPermissionCan>
)}
</div>
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
<IdentityDetailsSection
@@ -100,7 +134,17 @@ const Page = () => {
</div>
</div>
)}
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<Modal
isOpen={popUp?.identity?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("identity", isOpen)}
>
<ModalContent
bodyClassName="overflow-visible"
title={`${popUp?.identity?.data ? "Update" : "Create"} Identity`}
>
<OrgIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</ModalContent>
</Modal>
<IdentityAuthMethodModal
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
@@ -4,7 +4,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, Tooltip } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import { IdentityAuthMethod, identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
import {
IdentityAuthMethod,
identityAuthToNameMap,
useGetOrgIdentityMembershipById
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -16,7 +20,7 @@ type Props = {
};
export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: Props) => {
const { data, refetch } = useGetIdentityById(identityId);
const { data, refetch } = useGetOrgIdentityMembershipById(identityId);
return data ? (
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
@@ -7,9 +7,9 @@ import { Button, IconButton, Tooltip } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import { useTimedReset } from "@app/hooks";
import {
useGetIdentityById,
useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets
useGetIdentityUniversalAuthClientSecrets,
useGetOrgIdentityMembershipById
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -30,7 +30,7 @@ export const IdentityClientSecrets = ({ identityId, handlePopUpOpen }: Props) =>
initialState: "Copy Client ID to clipboard"
});
const { data } = useGetIdentityById(identityId);
const { data } = useGetOrgIdentityMembershipById(identityId);
const { data: identityUniversalAuth } = useGetIdentityUniversalAuth(identityId);
const { data: clientSecrets } = useGetIdentityUniversalAuthClientSecrets(identityId);
return (
@@ -11,7 +11,7 @@ import {
IconButton,
Tooltip
} from "@app/components/v2";
import { useGetIdentityById, useGetIdentityTokensTokenAuth } from "@app/hooks/api";
import { useGetIdentityTokensTokenAuth, useGetOrgIdentityMembershipById } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -23,7 +23,7 @@ type Props = {
};
export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
const { data } = useGetIdentityById(identityId);
const { data } = useGetOrgIdentityMembershipById(identityId);
const { data: tokens } = useGetIdentityTokensTokenAuth(identityId);
return (
<div>
@@ -23,7 +23,7 @@ import {
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useTimedReset } from "@app/hooks";
import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
import { identityAuthToNameMap, useGetOrgIdentityMembershipById } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -41,7 +41,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
});
const { isSubOrganization } = useOrganization();
const { data } = useGetIdentityById(identityId);
const { data } = useGetOrgIdentityMembershipById(identityId);
return data ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
@@ -86,7 +86,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
}}
disabled={!isAllowed}
>
Edit Identity
{isOrgIdentity ? "Edit Identity" : "Edit Identity Role"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -110,7 +110,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
icon={<FontAwesomeIcon icon={faTrash} />}
disabled={!isAllowed}
>
Delete Identity
{!isOrgIdentity ? "Remove From Sub-Organization" : "Delete Identity"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -14,7 +14,7 @@ import {
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useAddIdentityToWorkspace,
useCreateProjectIdentityMembership,
useGetIdentityProjectMemberships,
useGetProjectRoles,
useGetUserProjects,
@@ -45,7 +45,7 @@ type Props = {
const Content = ({ identityId, handlePopUpToggle }: Omit<Props, "popUp">) => {
const { currentOrg } = useOrganization();
const { data: workspaces = [] } = useGetUserProjects();
const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace();
const { mutateAsync: addIdentityToWorkspace } = useCreateProjectIdentityMembership();
const {
control,
@@ -9,7 +9,7 @@ import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
import { getProjectBaseURL } from "@app/helpers/project";
import { formatProjectRoleName } from "@app/helpers/roles";
import { useGetUserProjects } from "@app/hooks/api";
import { IdentityMembership } from "@app/hooks/api/identities/types";
import { IdentityProjectMembershipV1 } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
export enum TabSections {
@@ -20,7 +20,7 @@ export enum TabSections {
}
type Props = {
membership: IdentityMembership;
membership: IdentityProjectMembershipV1;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["removeIdentityFromProject"]>,
data?: object
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, IconButton } from "@app/components/v2";
import { useDeleteIdentityFromWorkspace } from "@app/hooks/api";
import { useDeleteProjectIdentityMembership } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { IdentityAddToProjectModal } from "./IdentityAddToProjectModal";
@@ -14,7 +14,7 @@ type Props = {
};
export const IdentityProjectsSection = ({ identityId }: Props) => {
const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace();
const { mutateAsync: deleteMutateAsync } = useDeleteProjectIdentityMembership();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addIdentityToProject",
@@ -1,11 +1,18 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { UseMutationResult } from "@tanstack/react-query";
import { useParams } from "@tanstack/react-router";
import ms from "ms";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
@@ -31,7 +38,11 @@ export const LockoutFields = ({
const [lockedOutState, setLockedOutState] = useState(lockedOut);
const clearLockouts = async () => {
const { projectId } = useParams({
strict: false
});
async function clearLockouts() {
const deleted = await mutateAsync({ identityId });
createNotification({
text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`,
@@ -39,13 +50,23 @@ export const LockoutFields = ({
});
setLockedOutState(false);
onResetAllLockouts();
};
}
return (
<>
<div className="col-span-2 mt-3 flex justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Lockout Options</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={projectId ? ProjectPermissionIdentityActions.Edit : OrgPermissionIdentityActions.Edit}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Button
isDisabled={!isAllowed || !lockedOutState || isPending}
@@ -57,7 +78,7 @@ export const LockoutFields = ({
Reset All Lockouts
</Button>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</div>
<IdentityAuthFieldDisplay label="Lockout Threshold">
{data.lockoutThreshold}
@@ -1,10 +1,12 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { faBan, faEdit, faKey, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useParams } from "@tanstack/react-router";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
@@ -20,7 +22,12 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useRevokeIdentityTokenAuthToken } from "@app/hooks/api";
import { IdentityAccessToken } from "@app/hooks/api/identities/types";
@@ -37,6 +44,10 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
"revokeToken"
] as const);
const { projectId } = useParams({
strict: false
});
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(5);
@@ -68,7 +79,17 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
<div className="col-span-2 mt-3">
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Access Tokens</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={projectId ? ProjectPermissionIdentityActions.Edit : OrgPermissionIdentityActions.Edit}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Button
size="xs"
@@ -84,7 +105,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
Add Token
</Button>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</div>
<TableContainer className="mt-4 rounded-none border-none">
<Table>
@@ -132,9 +153,20 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
</Td>
<Td>
<div className="flex items-center gap-2">
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Edit Token" : "Access Restricted"}>
@@ -155,11 +187,22 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</VariablePermissionCan>
{!isAccessTokenRevoked && (
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Revoke Token" : "Access Restricted"}>
@@ -181,7 +224,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</VariablePermissionCan>
)}
</div>
</Td>
@@ -1,10 +1,12 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { faKey, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useParams } from "@tanstack/react-router";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
@@ -20,7 +22,12 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useRevokeIdentityUniversalAuthClientSecret } from "@app/hooks/api";
import { ClientSecretData } from "@app/hooks/api/identities/types";
@@ -37,6 +44,10 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
"clientSecret"
] as const);
const { projectId } = useParams({
strict: false
});
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(5);
@@ -60,7 +71,17 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
<div className="col-span-2">
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Client Secrets</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={projectId ? ProjectPermissionIdentityActions.Edit : OrgPermissionIdentityActions.Edit}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
@@ -76,7 +97,7 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
Add Client Secret
</Button>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</div>
<TableContainer className="mt-4 rounded-none border-none">
<Table>
@@ -120,9 +141,20 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
{expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"}
</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Delete Secret" : "Access Restricted"}>
@@ -143,7 +175,7 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</Td>
</Tr>
);
@@ -49,6 +49,7 @@ export const ViewIdentityAliCloudAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -1,3 +1,5 @@
import { useParams } from "@tanstack/react-router";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, Modal, ModalContent } from "@app/components/v2";
@@ -46,8 +48,7 @@ type Props = {
type TRevokeOptions = {
identityId: string;
organizationId: string;
};
} & ({ projectId: string } | { organizationId: string });
export const Content = ({
identityId,
@@ -61,7 +62,9 @@ export const Content = ({
>) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { projectId } = useParams({
strict: false
});
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"revokeAuthMethod",
"upgradePlan",
@@ -142,7 +145,11 @@ export const Content = ({
const handleDeleteAuthMethod = async () => {
await revokeMethod({
identityId,
organizationId: orgId
...(projectId
? { projectId }
: {
organizationId: orgId
})
});
createNotification({
@@ -46,6 +46,7 @@ export const ViewIdentityAwsAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -46,6 +46,7 @@ export const ViewIdentityAzureAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -1,8 +1,10 @@
import { ReactNode } from "react";
import { subject } from "@casl/ability";
import { faChevronDown, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useParams } from "@tanstack/react-router";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import {
Button,
DropdownMenu,
@@ -10,15 +12,25 @@ import {
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
type Props = {
children: ReactNode;
onEdit: VoidFunction;
onDelete: VoidFunction;
identityId: string;
};
export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props) => {
export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit, identityId }: Props) => {
const { projectId } = useParams({
strict: false
});
return (
<div className="flex flex-col gap-4">
<div>
@@ -36,9 +48,20 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="mt-3 min-w-[120px]" align="end">
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<DropdownMenuItem
@@ -49,10 +72,21 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionIdentityActions.Delete}
a={OrgPermissionSubjects.Identity}
</VariablePermissionCan>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Delete
: OrgPermissionIdentityActions.Delete
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<DropdownMenuItem
@@ -63,7 +97,7 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
Delete
</DropdownMenuItem>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div>
@@ -46,6 +46,7 @@ export const ViewIdentityGcpAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -49,6 +49,7 @@ export const ViewIdentityJwtAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -59,6 +59,7 @@ export const ViewIdentityKubernetesAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -52,6 +52,7 @@ export const ViewIdentityLdapAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -46,6 +46,7 @@ export const ViewIdentityOciAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -48,6 +48,7 @@ export const ViewIdentityOidcAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -51,6 +51,7 @@ export const ViewIdentityTlsCertAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -49,6 +49,7 @@ export const ViewIdentityTokenAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -66,6 +66,7 @@ export const ViewIdentityUniversalAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
{Number(data.accessTokenPeriod) > 0 ? (
<IdentityAuthFieldDisplay label="Access Token Period (seconds)">
@@ -26,9 +26,21 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => {
return [
...resourceOptions,
// We are temporarily showing these resources so that we can gauge interest before committing
{ name: "OracleDB", resource: PamResourceType.OracleDB },
{ name: "SQLite", resource: PamResourceType.SQLite },
{ name: "Microsoft SQL Server", resource: PamResourceType.MsSQL },
{ name: "MongoDB", resource: PamResourceType.MongoDB },
{ name: "Cassandra", resource: PamResourceType.Cassandra },
{ name: "CockroachDB", resource: PamResourceType.CockroachDB },
{ name: "DynamoDB", resource: PamResourceType.DynamoDB },
{ name: "Snowflake", resource: PamResourceType.Snowflake },
{ name: "Elasticsearch", resource: PamResourceType.Elasticsearch },
{ name: "Redis", resource: PamResourceType.Redis },
{ name: "RDP", resource: PamResourceType.RDP },
{ name: "SSH", resource: PamResourceType.SSH },
{ name: "Kubernetes", resource: PamResourceType.Kubernetes }
{ name: "Kubernetes", resource: PamResourceType.Kubernetes },
{ name: "MCP", resource: PamResourceType.MCP },
{ name: "Web Application", resource: PamResourceType.WebApp }
];
}, [resourceOptions]);
@@ -38,11 +50,13 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => {
const filteredOptions = useMemo(
() =>
appendedResourceOptions?.filter(
({ name, resource }) =>
name.toLowerCase().includes(search.trim().toLowerCase()) ||
resource.toLowerCase().includes(search.trim().toLowerCase())
) ?? [],
appendedResourceOptions
?.filter(
({ name, resource }) =>
name.toLowerCase().includes(search.trim().toLowerCase()) ||
resource.toLowerCase().includes(search.trim().toLowerCase())
)
.sort((a, b) => a.name.localeCompare(b.name)) ?? [],
[appendedResourceOptions, search]
);
@@ -65,7 +79,16 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => {
if (
resource === PamResourceType.RDP ||
resource === PamResourceType.SSH ||
resource === PamResourceType.Kubernetes
resource === PamResourceType.Kubernetes ||
resource === PamResourceType.MCP ||
resource === PamResourceType.Redis ||
resource === PamResourceType.MongoDB ||
resource === PamResourceType.WebApp ||
resource === PamResourceType.Cassandra ||
resource === PamResourceType.CockroachDB ||
resource === PamResourceType.Elasticsearch ||
resource === PamResourceType.Snowflake ||
resource === PamResourceType.DynamoDB
) {
handlePopUpOpen("upgradePlan", {
text: "Your current plan does not include access to this resource type. To unlock this feature, please upgrade to Infisical Enterprise plan.",
@@ -105,17 +128,16 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => {
onClick={() => handleResourceSelect(option.resource)}
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
>
<div className="relative">
<div className="relative my-auto">
<img
src={`/images/integrations/${image}`}
style={{
width: `${size}px`
}}
className="mt-auto"
alt={`${name} logo`}
/>
</div>
<div className="mt-auto max-w-xs text-center text-xs font-medium text-gray-300 duration-200 group-hover:text-gray-200">
<div className="max-w-xs text-center text-xs font-medium text-gray-300 duration-200 group-hover:text-gray-200">
{name}
</div>
</button>
@@ -1,3 +1,4 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import {
faArrowDown,
@@ -11,7 +12,8 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { format } from "date-fns";
import { AnimatePresence, motion } from "framer-motion";
import { LinkIcon, PlusIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
@@ -29,6 +31,8 @@ import {
HoverCardTrigger,
IconButton,
Input,
Modal,
ModalContent,
Pagination,
Spinner,
Table,
@@ -42,8 +46,20 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3";
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
import { Blur } from "@app/components/v2/Blur";
import {
Badge,
DocumentationLinkBadge,
OrgIcon,
ProjectIcon,
SubOrgIcon
} from "@app/components/v3";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useOrganization,
useProject
} from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { formatProjectRoleName } from "@app/helpers/roles";
import {
@@ -53,19 +69,33 @@ import {
} from "@app/helpers/userTablePreferences";
import { withProjectPermission } from "@app/hoc";
import { usePagination, useResetPageHelper } from "@app/hooks";
import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
import {
useDeleteProjectIdentity,
useDeleteProjectIdentityMembership,
useListProjectIdentityMemberships
} from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { ProjectIdentityOrderBy } from "@app/hooks/api/projects/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { ProjectIdentityModal } from "@app/pages/project/AccessControlPage/components/IdentityTab/components/ProjectIdentityModal";
import { IdentityModal } from "./components/IdentityModal";
import { ProjectLinkIdentityModal } from "./components/ProjectLinkIdentityModal";
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
enum WizardSteps {
SelectAction = "select-action",
LinkIdentity = "link-identity",
ProjectIdentity = "project-identity"
}
export const IdentityTab = withProjectPermission(
() => {
const { currentProject, projectId } = useProject();
const navigate = useNavigate();
const { isSubOrganization, currentOrg } = useOrganization();
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectAction);
const {
offset,
@@ -90,7 +120,7 @@ export const IdentityTab = withProjectPermission(
setUserTablePreference("projectIdentityTable", PreferenceKey.PerPage, newPerPage);
};
const { data, isPending, isFetching } = useGetWorkspaceIdentityMemberships(
const { data, isPending, isFetching } = useListProjectIdentityMemberships(
{
projectId,
offset,
@@ -110,28 +140,41 @@ export const IdentityTab = withProjectPermission(
setPage
});
const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace();
const { mutateAsync: deleteMembershipMutateAsync } = useDeleteProjectIdentityMembership();
const { mutateAsync: deleteProjectIdentity } = useDeleteProjectIdentity();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"identity",
"createIdentity",
"deleteIdentity",
"upgradePlan"
"upgradePlan",
"addOptions"
] as const);
const onRemoveIdentitySubmit = async (identityId: string) => {
await deleteMutateAsync({
identityId,
projectId
});
const onRemoveIdentitySubmit = async (identityId: string, isProjectIdentity: boolean) => {
if (isProjectIdentity) {
await deleteProjectIdentity({
identityId,
projectId
});
createNotification({
text: "Successfully removed identity from project",
type: "success"
});
createNotification({
text: "Successfully deleted project identity",
type: "success"
});
} else {
await deleteMembershipMutateAsync({
identityId,
projectId
});
createNotification({
text: "Successfully removed identity from project",
type: "success"
});
}
handlePopUpClose("deleteIdentity");
};
const handleSort = (column: ProjectIdentityOrderBy) => {
if (column === orderBy) {
setOrderDirection((prev) =>
@@ -144,6 +187,12 @@ export const IdentityTab = withProjectPermission(
setOrderDirection(OrderByDirection.ASC);
};
const noAccessIdentityCount = Math.max(
(page * perPage > totalCount ? totalCount % perPage : perPage) -
(data?.identityMemberships?.length || 0),
0
);
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
@@ -151,22 +200,23 @@ export const IdentityTab = withProjectPermission(
<p className="text-xl font-medium text-mineshaft-100">Identities</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" />
</div>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("identity")}
isDisabled={!isAllowed}
>
Add Identity
</Button>
)}
</ProjectPermissionCan>
<div className="flex items-center">
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<Button
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("createIdentity")}
isDisabled={!isAllowed}
>
Create Identity
</Button>
)}
</ProjectPermissionCan>
</div>
</div>
<Input
containerClassName="mb-4"
@@ -202,7 +252,7 @@ export const IdentityTab = withProjectPermission(
</div>
</Th>
<Th className="w-1/3">Role</Th>
<Th>Added on</Th>
<Th>Managed by</Th>
<Th className="w-5">{isFetching ? <Spinner size="xs" /> : null}</Th>
</Tr>
</THead>
@@ -213,9 +263,8 @@ export const IdentityTab = withProjectPermission(
data.identityMemberships.length > 0 &&
data.identityMemberships.map((identityMember) => {
const {
identity: { id, name },
roles,
createdAt
identity: { id, name, projectId: identityProjectId, orgId: identityOrgId },
roles
} = identityMember;
return (
<Tr
@@ -339,7 +388,36 @@ export const IdentityTab = withProjectPermission(
)}
</div>
</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td>
<Badge
variant={
// eslint-disable-next-line no-nested-ternary
identityProjectId
? "project"
: isSubOrganization && currentOrg.id === identityOrgId
? "sub-org"
: "org"
}
>
{/* eslint-disable-next-line no-nested-ternary */}
{identityProjectId ? (
<>
<ProjectIcon />
Project
</>
) : isSubOrganization && currentOrg.id === identityOrgId ? (
<>
<SubOrgIcon />
Sub-Organization
</>
) : (
<>
<OrgIcon />
Organization
</>
)}
</Badge>
</Td>
<Td className="flex justify-end space-x-2">
<Tooltip className="max-w-sm text-center" content="Options">
<DropdownMenu>
@@ -369,11 +447,12 @@ export const IdentityTab = withProjectPermission(
evt.preventDefault();
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
name,
isProjectIdentity: Boolean(identityProjectId)
});
}}
>
Remove Identity From Project
{identityProjectId ? "Delete Identity" : "Remove From Project"}
</DropdownMenuItem>
)}
</ProjectPermissionCan>
@@ -384,6 +463,20 @@ export const IdentityTab = withProjectPermission(
</Tr>
);
})}
{!isPending &&
data &&
data?.totalCount !== 0 &&
Array.from(Array(noAccessIdentityCount)).map((_e, i) => (
<Tr key={`hid-identity-${i + 1}`}>
<Td>No Access</Td>
<Td colSpan={3}>
<Blur
className="w-min"
tooltipText="You do not have permission to read this identity."
/>
</Td>
</Tr>
))}
</TBody>
</Table>
{!isPending && data && totalCount > 0 && (
@@ -395,18 +488,113 @@ export const IdentityTab = withProjectPermission(
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && data && data?.identityMemberships.length === 0 && (
<EmptyState
title={
debouncedSearch.trim().length > 0
? "No identities match search filter"
: "No identities have been added to this project"
}
icon={faServer}
/>
)}
{!isPending &&
data &&
data?.identityMemberships.length === 0 &&
data?.totalCount === 0 && (
<EmptyState
title={
debouncedSearch.trim().length > 0
? "No identities match search filter"
: "No identities have been added to this project"
}
icon={faServer}
/>
)}
</TableContainer>
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<Modal
isOpen={popUp.createIdentity.isOpen}
onOpenChange={(open) => {
handlePopUpToggle("createIdentity", open);
if (!open) setWizardStep(WizardSteps.SelectAction);
}}
>
<ModalContent
bodyClassName="overflow-visible"
title="Add Project Identity"
subTitle="Create a new identity or assign an existing identity"
>
<AnimatePresence mode="wait">
{wizardStep === WizardSteps.SelectAction && (
<motion.div
key="select-type-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<div
className="cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(WizardSteps.ProjectIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(WizardSteps.ProjectIdentity);
}
}}
>
<div className="flex items-center gap-2">
<PlusIcon size="1rem" />
<div>Create New Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Create a new machine identity specifically for this project. This identity
will be managed at the project-level.
</div>
</div>
<div
className="mt-4 cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(WizardSteps.LinkIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(WizardSteps.LinkIdentity);
}
}}
>
<div className="flex items-center gap-2">
<LinkIcon size="1rem" />
<div>Assign Existing Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Assign an existing identity from your organization. The identity will continue
to be managed at its original scope.
</div>
</div>
</motion.div>
)}
{wizardStep === WizardSteps.ProjectIdentity && (
<motion.div
key="identity-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<ProjectIdentityModal
onClose={() => {
handlePopUpClose("createIdentity");
setWizardStep(WizardSteps.SelectAction);
}}
/>
</motion.div>
)}
{wizardStep === WizardSteps.LinkIdentity && (
<motion.div
key="link-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<ProjectLinkIdentityModal handlePopUpToggle={handlePopUpToggle} />
</motion.div>
)}
</AnimatePresence>
</ModalContent>
</Modal>
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure you want to remove ${
@@ -416,7 +604,8 @@ export const IdentityTab = withProjectPermission(
deleteKey="confirm"
onDeleteApproved={() =>
onRemoveIdentitySubmit(
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
popUp?.deleteIdentity?.data?.identityId,
popUp?.deleteIdentity?.data?.isProjectIdentity
)
}
/>

Some files were not shown because too many files have changed in this diff Show More