improvements: address feedback

This commit is contained in:
Scott Wilson
2025-12-08 16:37:00 -08:00
parent d5948b3246
commit 77ec24c323
42 changed files with 599 additions and 682 deletions
@@ -0,0 +1,79 @@
/* eslint-disable react/prop-types */
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "../../utils";
function UnstableAccordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return (
<AccordionPrimitive.Root
data-slot="accordion"
className="border border-border bg-container"
{...props}
/>
);
}
function UnstableAccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b border-border last:border-b-0", className)}
{...props}
/>
);
}
function UnstableAccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"flex min-h-12 flex-1 items-center gap-4 border-border bg-container px-4 text-left text-sm font-medium",
"transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
"disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
"cursor-pointer hover:bg-foreground/5",
"data-[state=open]:bg-foreground/5",
className
)}
{...props}
>
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 translate-y-0 text-label transition-transform duration-200" />
{children}
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
);
}
function UnstableAccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="overflow-hidden text-sm transition data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("p-6", className)}>{children}</div>
</AccordionPrimitive.Content>
);
}
export {
UnstableAccordion,
UnstableAccordionContent,
UnstableAccordionItem,
UnstableAccordionTrigger
};
@@ -0,0 +1 @@
export * from "./Accordion";
@@ -25,8 +25,8 @@ const badgeVariants = cva(
}, },
variant: { variant: {
ghost: "text-foreground border-none", ghost: "text-foreground border-none",
default: "bg-foreground text-background [a&,button&]:hover:bg-primary/35", default: "bg-label text-background border-label [a&,button&]:hover:bg-primary/35",
outline: "text-foreground border-foreground border", outline: "text-label border-label border",
neutral: "bg-neutral/15 border-neutral/10 text-neutral [a&,button&]:hover:bg-neutral/35", neutral: "bg-neutral/15 border-neutral/10 text-neutral [a&,button&]:hover:bg-neutral/35",
success: "bg-success/15 border-success/10 text-success [a&,button&]:hover:bg-success/35", success: "bg-success/15 border-success/10 text-success [a&,button&]:hover:bg-success/35",
info: "bg-info/15 border-info/10 border text-info [a&,button&]:hover:bg-info/35", info: "bg-info/15 border-info/10 border text-info [a&,button&]:hover:bg-info/35",
@@ -8,7 +8,10 @@ function UnstableCard({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card" data-slot="card"
className={cn("flex h-fit flex-col gap-6 rounded-[6px] text-foreground shadow-sm", className)} className={cn(
"flex h-fit flex-col gap-6 rounded-md border border-border bg-card p-5 text-foreground shadow-sm",
className
)}
{...props} {...props}
/> />
); );
@@ -19,7 +22,8 @@ function UnstableCardHeader({ className, ...props }: React.ComponentProps<"div">
<div <div
data-slot="card-header" data-slot="card-header"
className={cn( className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-center gap-1 border-mineshaft-400/60 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-center gap-1",
"border-border has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className className
)} )}
{...props} {...props}
@@ -31,10 +35,7 @@ function UnstableCardTitle({ className, ...props }: React.ComponentProps<"div">)
return ( return (
<div <div
data-slot="card-title" data-slot="card-title"
className={cn( className={cn("text-lg leading-none font-semibold", className)}
"flex items-center gap-1.5 leading-none font-semibold [&>svg]:inline-block [&>svg]:size-[18px]",
className
)}
{...props} {...props}
/> />
); );
@@ -42,14 +43,7 @@ function UnstableCardTitle({ className, ...props }: React.ComponentProps<"div">)
function CardDescription({ className, ...props }: React.ComponentProps<"div">) { function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div data-slot="card-description" className={cn("text-sm text-accent", className)} {...props} />
data-slot="card-description"
className={cn(
"flex items-center gap-1 text-sm text-accent [&>svg]:inline-block [&>svg]:size-[12px]",
className
)}
{...props}
/>
); );
} }
@@ -36,7 +36,8 @@ function UnstableDropdownMenuContent({
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)", "max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)",
"z-50 overflow-x-hidden overflow-y-auto rounded-[6px] border border-border/50 bg-popover p-1 text-xs text-foreground shadow-md", "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
"z-50 overflow-x-hidden overflow-y-auto rounded-[6px] border border-border bg-popover p-1 text-xs text-foreground shadow-md",
className className
)} )}
{...props} {...props}
@@ -70,7 +70,7 @@ function UnstableEmptyDescription({ className, ...props }: React.ComponentProps<
<div <div
data-slot="empty-description" data-slot="empty-description"
className={cn( className={cn(
"text-xs/relaxed text-muted [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", "text-xs/relaxed text-muted [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-project",
className className
)} )}
{...props} {...props}
@@ -35,8 +35,8 @@ const iconButtonVariants = cva(
"border-danger/75 bg-danger/40 text-foreground hover:bg-danger/50 hover:border-danger" "border-danger/75 bg-danger/40 text-foreground hover:bg-danger/50 hover:border-danger"
}, },
size: { size: {
xs: "h-6 w-6 [&>svg]:size-4 rounded-[5px] [&>svg]:stroke-[1.75]", xs: "h-7 w-7 [&>svg]:size-3.5 [&>svg]:stroke-[1.75]",
sm: "h-8 w-8 [&>svg]:size-5 [&>svg]:stroke-[1.5]", sm: "h-8 w-8 [&>svg]:size-4 [&>svg]:stroke-[1.5]",
md: "h-9 w-9 [&>svg]:size-6 [&>svg]:stroke-[1.5]", md: "h-9 w-9 [&>svg]:size-6 [&>svg]:stroke-[1.5]",
lg: "h-10 w-10 [&>svg]:size-7 [&>svg]:stroke-[1.5]" lg: "h-10 w-10 [&>svg]:size-7 [&>svg]:stroke-[1.5]"
}, },
@@ -8,7 +8,7 @@ function UnstableTable({ className, ...props }: React.ComponentProps<"table">) {
return ( return (
<div <div
data-slot="table-container" data-slot="table-container"
className="relative w-full overflow-x-auto border border-border/50 bg-mineshaft-800/50" className="relative w-full overflow-x-auto border border-border bg-container"
> >
<table <table
data-slot="table" data-slot="table"
@@ -39,10 +39,7 @@ function UnstableTableFooter({ className, ...props }: React.ComponentProps<"tfoo
return ( return (
<tfoot <tfoot
data-slot="table-footer" data-slot="table-footer"
className={cn( className={cn("border-t border-border font-medium [&>tr]:last:border-b-0", className)}
"border-t border-border/50 bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props} {...props}
/> />
); );
@@ -53,7 +50,7 @@ function UnstableTableRow({ className, ...props }: React.ComponentProps<"tr">) {
<tr <tr
data-slot="table-row" data-slot="table-row"
className={cn( className={cn(
"border-b border-border/50 transition-colors hover:bg-foreground/5 data-[state=selected]:bg-foreground/5", "border-b border-border transition-colors hover:bg-foreground/5 data-[state=selected]:bg-foreground/5",
className className
)} )}
{...props} {...props}
@@ -66,7 +63,7 @@ function UnstableTableHead({ className, ...props }: React.ComponentProps<"th">)
<th <th
data-slot="table-head" data-slot="table-head"
className={cn( className={cn(
"h-[30px] border-x-0 border-t-0 border-b border-border/50 px-3 text-left align-middle text-xs whitespace-nowrap text-accent text-mineshaft-400 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "h-[30px] border-x-0 border-t-0 border-b border-border px-3 text-left align-middle text-xs whitespace-nowrap text-accent [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className
)} )}
{...props} {...props}
@@ -91,7 +88,7 @@ function UnstableTableCaption({ className, ...props }: React.ComponentProps<"cap
return ( return (
<caption <caption
data-slot="table-caption" data-slot="table-caption"
className={cn("mt-4 text-sm text-muted-foreground", className)} className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props} {...props}
/> />
); );
@@ -1,3 +1,4 @@
export * from "./Accordion";
export * from "./Alert"; export * from "./Alert";
export * from "./Badge"; export * from "./Badge";
export * from "./Button"; export * from "./Button";
+1 -1
View File
@@ -18,7 +18,7 @@ export type TIdentity = {
updatedAt: string; updatedAt: string;
hasDeleteProtection: boolean; hasDeleteProtection: boolean;
authMethods: IdentityAuthMethod[]; authMethods: IdentityAuthMethod[];
activeLockoutAuthMethods: string[]; activeLockoutAuthMethods: IdentityAuthMethod[];
metadata?: Array<TMetadata & { id: string }>; metadata?: Array<TMetadata & { id: string }>;
}; };
+4 -4
View File
@@ -49,14 +49,14 @@
--color-sub-org: #96ff59; --color-sub-org: #96ff59;
--color-project: #e0ed34; --color-project: #e0ed34;
--color-neutral: #adaeb0; --color-neutral: #adaeb0;
--color-border: #323439; --color-border: #2b2c30;
--color-label: #adaeb0; --color-label: #adaeb0;
--color-muted: #707174; --color-muted: #707174;
--color-popover: #111419; --color-popover: #141617;
--color-ring: #2d2f33; --color-ring: #2d2f33;
--color-container: #16181a; --color-card: #16181a;
--color-accent: #7d7f80; --color-accent: #7d7f80;
--color-muted-foreground: ; --color-container: #1a1c1e;
/*legacy color schema */ /*legacy color schema */
--color-org-v1: #30b3ff; --color-org-v1: #30b3ff;
@@ -17,7 +17,6 @@ import {
} from "@app/context"; } from "@app/context";
import { useDeleteOrgIdentity, useGetOrgIdentityMembershipById } from "@app/hooks/api"; import { useDeleteOrgIdentity, useGetOrgIdentityMembershipById } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal";
import { OrgAccessControlTabSections } from "@app/types/org"; import { OrgAccessControlTabSections } from "@app/types/org";
import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal"; import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
@@ -125,14 +124,16 @@ const Page = () => {
identityId={identityId} identityId={identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
/> />
</div>
<div className="flex flex-1 flex-col gap-y-4">
{!isAuthHidden && ( {!isAuthHidden && (
<IdentityAuthenticationSection <IdentityAuthenticationSection
identityId={identityId} identityId={identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
/> />
)} )}
<IdentityProjectsSection identityId={identityId} />
</div> </div>
<IdentityProjectsSection identityId={identityId} />
</div> </div>
</div> </div>
)} )}
@@ -171,14 +172,6 @@ const Page = () => {
) )
} }
/> />
<ViewIdentityAuthModal
isOpen={popUp.viewAuthMethod.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)}
authMethod={popUp.viewAuthMethod.data?.authMethod}
lockedOut={popUp.viewAuthMethod.data?.lockedOut || false}
identityId={identityId}
onResetAllLockouts={popUp.viewAuthMethod.data?.refetchIdentity}
/>
</div> </div>
); );
}; };
@@ -1,16 +1,14 @@
import { faCog, faLock, faPlus } from "@fortawesome/free-solid-svg-icons"; import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button, Tooltip } from "@app/components/v2"; import { Button } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import { import { IdentityAuthMethod, useGetOrgIdentityMembershipById } from "@app/hooks/api";
IdentityAuthMethod,
identityAuthToNameMap,
useGetOrgIdentityMembershipById
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { ViewIdentityAuth } from "../ViewIdentityAuth";
type Props = { type Props = {
identityId: string; identityId: string;
handlePopUpOpen: ( handlePopUpOpen: (
@@ -23,37 +21,42 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P
const { data, refetch } = useGetOrgIdentityMembershipById(identityId); const { data, refetch } = useGetOrgIdentityMembershipById(identityId);
return data ? ( return data ? (
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <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"> <div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-medium text-mineshaft-100">Authentication</h3> <h3 className="text-lg font-medium text-mineshaft-100">Authentication</h3>
{!Object.values(IdentityAuthMethod).every((method) =>
data.identity.authMethods.includes(method)
) && (
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
onClick={() => {
handlePopUpOpen("identityAuthMethod", {
identityId,
name: data.identity.name,
allAuthMethods: data.identity.authMethods
});
}}
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
{data.identity.authMethods.length ? "Add" : "Create"} Auth Method
</Button>
)}
</OrgPermissionCan>
)}
</div> </div>
{data.identity.authMethods.length > 0 ? ( {data.identity.authMethods.length > 0 ? (
<div className="flex flex-col divide-y divide-mineshaft-400/50"> <ViewIdentityAuth
{data.identity.authMethods.map((authMethod) => ( activeLockoutAuthMethods={data.identity.activeLockoutAuthMethods}
<button identityId={identityId}
key={authMethod} authMethods={data.identity.authMethods}
onClick={() => onResetAllLockouts={refetch}
handlePopUpOpen("viewAuthMethod", { />
authMethod,
lockedOut: data.identity.activeLockoutAuthMethods.includes(authMethod),
refetchIdentity: refetch
})
}
type="button"
className="flex w-full items-center justify-between bg-mineshaft-900 px-4 py-2 text-sm hover:bg-mineshaft-700 data-[state=open]:bg-mineshaft-600"
>
<span>{identityAuthToNameMap[authMethod]}</span>
<div className="flex gap-2">
{data.identity.activeLockoutAuthMethods.includes(authMethod) && (
<Tooltip content="Auth method has active lockouts">
<FontAwesomeIcon icon={faLock} size="xs" className="text-red-400/50" />
</Tooltip>
)}
<FontAwesomeIcon icon={faCog} size="xs" className="text-mineshaft-400" />
</div>
</button>
))}
</div>
) : ( ) : (
<div className="w-full space-y-2 pt-2"> <div className="w-full space-y-2 pt-2">
<p className="text-sm text-mineshaft-300"> <p className="text-sm text-mineshaft-300">
@@ -61,30 +64,6 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P
</p> </p>
</div> </div>
)} )}
{!Object.values(IdentityAuthMethod).every((method) =>
data.identity.authMethods.includes(method)
) && (
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
onClick={() => {
handlePopUpOpen("identityAuthMethod", {
identityId,
name: data.identity.name,
allAuthMethods: data.identity.authMethods
});
}}
variant="outline_bg"
className="mt-3 w-full"
size="xs"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
{data.identity.authMethods.length ? "Add" : "Create"} Auth Method
</Button>
)}
</OrgPermissionCan>
)}
</div> </div>
) : ( ) : (
<div /> <div />
@@ -0,0 +1,24 @@
import { ReactNode } from "react";
import { Detail, DetailLabel, DetailValue } from "@app/components/v3";
type Props = {
label: string;
children: ReactNode;
className?: string;
};
export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => {
return (
<Detail className={className}>
<DetailLabel>{label}</DetailLabel>
<DetailValue>
{children ? (
<p className="break-words">{children}</p>
) : (
<p className="text-muted italic">Not set</p>
)}
</DetailValue>
</Detail>
);
};
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2"; import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityAliCloudAuth } from "@app/hooks/api"; import { useGetIdentityAliCloudAuth } from "@app/hooks/api";
import { IdentityAliCloudAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityAliCloudAuthContent = ({ export const ViewIdentityAliCloudAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityAliCloudAuth(identityId); const { data, isPending } = useGetIdentityAliCloudAuth(identityId);
@@ -34,23 +31,8 @@ export const ViewIdentityAliCloudAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityAliCloudAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -0,0 +1,276 @@
import { useParams } from "@tanstack/react-router";
import { EllipsisIcon, LockIcon } from "lucide-react";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, Modal, ModalContent, Tooltip } from "@app/components/v2";
import {
Badge,
UnstableAccordion,
UnstableAccordionContent,
UnstableAccordionItem,
UnstableAccordionTrigger,
UnstableDropdownMenu,
UnstableDropdownMenuContent,
UnstableDropdownMenuItem,
UnstableDropdownMenuTrigger,
UnstableIconButton
} from "@app/components/v3";
import { useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks";
import {
IdentityAuthMethod,
identityAuthToNameMap,
useDeleteIdentityAliCloudAuth,
useDeleteIdentityAwsAuth,
useDeleteIdentityAzureAuth,
useDeleteIdentityGcpAuth,
useDeleteIdentityJwtAuth,
useDeleteIdentityKubernetesAuth,
useDeleteIdentityLdapAuth,
useDeleteIdentityOciAuth,
useDeleteIdentityOidcAuth,
useDeleteIdentityTlsCertAuth,
useDeleteIdentityTokenAuth,
useDeleteIdentityUniversalAuth
} from "@app/hooks/api";
import { IdentityAliCloudAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm";
import { IdentityAwsAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm";
import { IdentityAzureAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm";
import { IdentityGcpAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm";
import { IdentityJwtAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm";
import { IdentityKubernetesAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm";
import { IdentityLdapAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm";
import { IdentityOciAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm";
import { IdentityOidcAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm";
import { IdentityTlsCertAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm";
import { IdentityTokenAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm";
import { IdentityUniversalAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm";
import { ViewIdentityAliCloudAuthContent } from "./ViewIdentityAliCloudAuthContent";
import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent";
import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
import { ViewIdentityLdapAuthContent } from "./ViewIdentityLdapAuthContent";
import { ViewIdentityOciAuthContent } from "./ViewIdentityOciAuthContent";
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
import { ViewIdentityTlsCertAuthContent } from "./ViewIdentityTlsCertAuthContent";
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
type Props = {
identityId: string;
authMethods: IdentityAuthMethod[];
onResetAllLockouts: () => void;
activeLockoutAuthMethods: IdentityAuthMethod[];
};
const AuthMethodComponentMap = {
[IdentityAuthMethod.UNIVERSAL_AUTH]: ViewIdentityUniversalAuthContent,
[IdentityAuthMethod.TOKEN_AUTH]: ViewIdentityTokenAuthContent,
[IdentityAuthMethod.TLS_CERT_AUTH]: ViewIdentityTlsCertAuthContent,
[IdentityAuthMethod.KUBERNETES_AUTH]: ViewIdentityKubernetesAuthContent,
[IdentityAuthMethod.LDAP_AUTH]: ViewIdentityLdapAuthContent,
[IdentityAuthMethod.OCI_AUTH]: ViewIdentityOciAuthContent,
[IdentityAuthMethod.OIDC_AUTH]: ViewIdentityOidcAuthContent,
[IdentityAuthMethod.GCP_AUTH]: ViewIdentityGcpAuthContent,
[IdentityAuthMethod.AWS_AUTH]: ViewIdentityAwsAuthContent,
[IdentityAuthMethod.ALICLOUD_AUTH]: ViewIdentityAliCloudAuthContent,
[IdentityAuthMethod.AZURE_AUTH]: ViewIdentityAzureAuthContent,
[IdentityAuthMethod.JWT_AUTH]: ViewIdentityJwtAuthContent
};
const EditAuthMethodMap = {
[IdentityAuthMethod.KUBERNETES_AUTH]: IdentityKubernetesAuthForm,
[IdentityAuthMethod.GCP_AUTH]: IdentityGcpAuthForm,
[IdentityAuthMethod.TLS_CERT_AUTH]: IdentityTlsCertAuthForm,
[IdentityAuthMethod.AWS_AUTH]: IdentityAwsAuthForm,
[IdentityAuthMethod.AZURE_AUTH]: IdentityAzureAuthForm,
[IdentityAuthMethod.ALICLOUD_AUTH]: IdentityAliCloudAuthForm,
[IdentityAuthMethod.UNIVERSAL_AUTH]: IdentityUniversalAuthForm,
[IdentityAuthMethod.TOKEN_AUTH]: IdentityTokenAuthForm,
[IdentityAuthMethod.OCI_AUTH]: IdentityOciAuthForm,
[IdentityAuthMethod.OIDC_AUTH]: IdentityOidcAuthForm,
[IdentityAuthMethod.JWT_AUTH]: IdentityJwtAuthForm,
[IdentityAuthMethod.LDAP_AUTH]: IdentityLdapAuthForm
};
export const Content = ({
identityId,
authMethods,
onResetAllLockouts,
activeLockoutAuthMethods
}: Pick<
Props,
"authMethods" | "identityId" | "onResetAllLockouts" | "activeLockoutAuthMethods"
>) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { projectId } = useParams({
strict: false
});
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"revokeAuthMethod",
"identityAuthMethod",
"upgradePlan"
] as const);
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
const { mutateAsync: revokeTlsCertAuth } = useDeleteIdentityTlsCertAuth();
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
const { mutateAsync: revokeAliCloudAuth } = useDeleteIdentityAliCloudAuth();
const { mutateAsync: revokeOciAuth } = useDeleteIdentityOciAuth();
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
const RemoveAuthMethodMap = {
[IdentityAuthMethod.KUBERNETES_AUTH]: revokeKubernetesAuth,
[IdentityAuthMethod.GCP_AUTH]: revokeGcpAuth,
[IdentityAuthMethod.TLS_CERT_AUTH]: revokeTlsCertAuth,
[IdentityAuthMethod.AWS_AUTH]: revokeAwsAuth,
[IdentityAuthMethod.AZURE_AUTH]: revokeAzureAuth,
[IdentityAuthMethod.ALICLOUD_AUTH]: revokeAliCloudAuth,
[IdentityAuthMethod.UNIVERSAL_AUTH]: revokeUniversalAuth,
[IdentityAuthMethod.TOKEN_AUTH]: revokeTokenAuth,
[IdentityAuthMethod.OCI_AUTH]: revokeOciAuth,
[IdentityAuthMethod.OIDC_AUTH]: revokeOidcAuth,
[IdentityAuthMethod.JWT_AUTH]: revokeJwtAuth,
[IdentityAuthMethod.LDAP_AUTH]: revokeLdapAuth
};
const handleDeleteAuthMethod = async (authMethod: IdentityAuthMethod) => {
await RemoveAuthMethodMap[authMethod]({
identityId,
...(projectId
? { projectId }
: {
organizationId: orgId
})
});
createNotification({
text: "Successfully removed auth method",
type: "success"
});
handlePopUpToggle("revokeAuthMethod", false);
};
const EditForm = popUp.identityAuthMethod?.data
? EditAuthMethodMap[popUp.identityAuthMethod.data as IdentityAuthMethod]
: null;
return (
<>
<UnstableAccordion type={authMethods.length === 1 ? "single" : "multiple"} collapsible>
{authMethods.map((authMethod) => {
const Component = AuthMethodComponentMap[authMethod];
return (
<UnstableAccordionItem key={authMethod} value={authMethod}>
<UnstableAccordionTrigger>
<span className="mr-auto">{identityAuthToNameMap[authMethod]}</span>
{activeLockoutAuthMethods?.includes(authMethod) && (
<Tooltip content="Auth method has active lockouts">
<Badge isSquare variant="danger">
<LockIcon />
</Badge>
</Tooltip>
)}
<UnstableDropdownMenu>
<UnstableDropdownMenuTrigger asChild>
<UnstableIconButton variant="ghost" size="xs">
<EllipsisIcon />
</UnstableIconButton>
</UnstableDropdownMenuTrigger>
<UnstableDropdownMenuContent align="end">
<UnstableDropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("identityAuthMethod", authMethod);
}}
>
Edit Auth Method
</UnstableDropdownMenuItem>
<UnstableDropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("revokeAuthMethod", authMethod);
}}
variant="danger"
>
Remove Auth Method
</UnstableDropdownMenuItem>
</UnstableDropdownMenuContent>
</UnstableDropdownMenu>
</UnstableAccordionTrigger>
<UnstableAccordionContent>
<Component
identityId={identityId}
onEdit={() => handlePopUpOpen("identityAuthMethod", authMethod)}
onDelete={() => handlePopUpOpen("revokeAuthMethod", authMethod)}
onResetAllLockouts={onResetAllLockouts}
lockedOut={activeLockoutAuthMethods?.includes(authMethod)}
/>
</UnstableAccordionContent>
</UnstableAccordionItem>
);
})}
</UnstableAccordion>
<DeleteActionModal
isOpen={popUp?.revokeAuthMethod?.isOpen}
title={`Are you sure you want to remove ${popUp?.revokeAuthMethod?.data ? identityAuthToNameMap[popUp?.revokeAuthMethod?.data as IdentityAuthMethod] : "this auth method"} on this identity?`}
onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
deleteKey="confirm"
buttonText="Remove"
onDeleteApproved={() =>
handleDeleteAuthMethod(popUp?.revokeAuthMethod?.data as IdentityAuthMethod)
}
/>
<Modal
isOpen={popUp?.identityAuthMethod?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("identityAuthMethod", isOpen)}
>
<ModalContent
title={`Edit ${popUp?.identityAuthMethod?.data ? identityAuthToNameMap[popUp?.identityAuthMethod?.data as IdentityAuthMethod] : "this auth method"}`}
>
{EditForm && (
<EditForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
)}
</ModalContent>
</Modal>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`}
/>
</>
);
};
export const ViewIdentityAuth = ({
authMethods,
identityId,
onResetAllLockouts,
activeLockoutAuthMethods
}: Props) => {
return (
<Content
identityId={identityId}
authMethods={authMethods}
activeLockoutAuthMethods={activeLockoutAuthMethods}
onResetAllLockouts={() => onResetAllLockouts()}
/>
);
};
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2"; import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityAwsAuth } from "@app/hooks/api"; import { useGetIdentityAwsAuth } from "@app/hooks/api";
import { IdentityAwsAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityAwsAuthContent = ({ export const ViewIdentityAwsAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityAwsAuth(identityId); const { data, isPending } = useGetIdentityAwsAuth(identityId);
@@ -31,23 +28,8 @@ export const ViewIdentityAwsAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityAwsAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2"; import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityAzureAuth } from "@app/hooks/api"; import { useGetIdentityAzureAuth } from "@app/hooks/api";
import { IdentityAzureAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityAzureAuthContent = ({ export const ViewIdentityAzureAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityAzureAuth(identityId); const { data, isPending } = useGetIdentityAzureAuth(identityId);
@@ -31,23 +28,8 @@ export const ViewIdentityAzureAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityAzureAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2"; import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityGcpAuth } from "@app/hooks/api"; import { useGetIdentityGcpAuth } from "@app/hooks/api";
import { IdentityGcpAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityGcpAuthContent = ({ export const ViewIdentityGcpAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityGcpAuth(identityId); const { data, isPending } = useGetIdentityGcpAuth(identityId);
@@ -31,23 +28,8 @@ export const ViewIdentityGcpAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityGcpAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -5,18 +5,15 @@ import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3"; import { Badge } from "@app/components/v3";
import { useGetIdentityJwtAuth } from "@app/hooks/api"; import { useGetIdentityJwtAuth } from "@app/hooks/api";
import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums"; import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums";
import { IdentityJwtAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm"; import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth/ViewIdentityContentWrapper";
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
export const ViewIdentityJwtAuthContent = ({ export const ViewIdentityJwtAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityJwtAuth(identityId); const { data, isPending } = useGetIdentityJwtAuth(identityId);
@@ -34,23 +31,8 @@ export const ViewIdentityJwtAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityJwtAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -6,7 +6,6 @@ import { EyeIcon } from "lucide-react";
import { EmptyState, Spinner, Tooltip } from "@app/components/v2"; import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3"; import { Badge } from "@app/components/v3";
import { gatewaysQueryKeys, useGetIdentityKubernetesAuth } from "@app/hooks/api"; import { gatewaysQueryKeys, useGetIdentityKubernetesAuth } from "@app/hooks/api";
import { IdentityKubernetesAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
@@ -14,10 +13,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityKubernetesAuthContent = ({ export const ViewIdentityKubernetesAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data: gateways } = useQuery(gatewaysQueryKeys.list()); const { data: gateways } = useQuery(gatewaysQueryKeys.list());
@@ -44,23 +41,8 @@ export const ViewIdentityKubernetesAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityKubernetesAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -4,8 +4,7 @@ import { EyeIcon } from "lucide-react";
import { EmptyState, Spinner, Tooltip } from "@app/components/v2"; import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3"; import { Badge } from "@app/components/v3";
import { useClearIdentityLdapAuthLockouts, useGetIdentityLdapAuth } from "@app/hooks/api"; import { useClearIdentityLdapAuthLockouts, useGetIdentityLdapAuth } from "@app/hooks/api";
import { IdentityLdapAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm"; import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth/ViewIdentityContentWrapper";
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { LockoutFields } from "./IdentityAuthLockoutFields"; import { LockoutFields } from "./IdentityAuthLockoutFields";
@@ -13,10 +12,8 @@ import { ViewAuthMethodProps } from "./types";
export const ViewIdentityLdapAuthContent = ({ export const ViewIdentityLdapAuthContent = ({
identityId, identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete, onDelete,
popUp, onEdit,
lockedOut, lockedOut,
onResetAllLockouts onResetAllLockouts
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
@@ -37,23 +34,8 @@ export const ViewIdentityLdapAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityLdapAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2"; import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityOciAuth } from "@app/hooks/api"; import { useGetIdentityOciAuth } from "@app/hooks/api";
import { IdentityOciAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityOciAuthContent = ({ export const ViewIdentityOciAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityOciAuth(identityId); const { data, isPending } = useGetIdentityOciAuth(identityId);
@@ -31,23 +28,8 @@ export const ViewIdentityOciAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityOciAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -4,18 +4,15 @@ import { EyeIcon } from "lucide-react";
import { EmptyState, Spinner, Tooltip } from "@app/components/v2"; import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3"; import { Badge } from "@app/components/v3";
import { useGetIdentityOidcAuth } from "@app/hooks/api"; import { useGetIdentityOidcAuth } from "@app/hooks/api";
import { IdentityOidcAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm"; import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth/ViewIdentityContentWrapper";
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
export const ViewIdentityOidcAuthContent = ({ export const ViewIdentityOidcAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityOidcAuth(identityId); const { data, isPending } = useGetIdentityOidcAuth(identityId);
@@ -33,23 +30,8 @@ export const ViewIdentityOidcAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityOidcAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -4,7 +4,6 @@ import { EyeIcon } from "lucide-react";
import { EmptyState, Spinner, Tooltip } from "@app/components/v2"; import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3"; import { Badge } from "@app/components/v3";
import { useGetIdentityTlsCertAuth } from "@app/hooks/api"; import { useGetIdentityTlsCertAuth } from "@app/hooks/api";
import { IdentityTlsCertAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types"; import { ViewAuthMethodProps } from "./types";
@@ -12,10 +11,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityTlsCertAuthContent = ({ export const ViewIdentityTlsCertAuthContent = ({
identityId, identityId,
handlePopUpToggle, onEdit,
handlePopUpOpen, onDelete
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityTlsCertAuth(identityId); const { data, isPending } = useGetIdentityTlsCertAuth(identityId);
@@ -36,23 +33,8 @@ export const ViewIdentityTlsCertAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityTlsCertAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2"; import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityTokenAuth, useGetIdentityTokensTokenAuth } from "@app/hooks/api"; import { useGetIdentityTokenAuth, useGetIdentityTokensTokenAuth } from "@app/hooks/api";
import { IdentityTokenAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { IdentityTokenAuthTokensTable } from "./IdentityTokenAuthTokensTable"; import { IdentityTokenAuthTokensTable } from "./IdentityTokenAuthTokensTable";
@@ -11,10 +10,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityTokenAuthContent = ({ export const ViewIdentityTokenAuthContent = ({
identityId, identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete, onDelete,
popUp onEdit
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityTokenAuth(identityId); const { data, isPending } = useGetIdentityTokenAuth(identityId);
const { data: tokens = [], isPending: clientSecretsPending } = const { data: tokens = [], isPending: clientSecretsPending } =
@@ -34,23 +31,8 @@ export const ViewIdentityTokenAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityTokenAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -8,7 +8,6 @@ import {
useGetIdentityUniversalAuth, useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets useGetIdentityUniversalAuthClientSecrets
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityUniversalAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { LockoutFields } from "./IdentityAuthLockoutFields"; import { LockoutFields } from "./IdentityAuthLockoutFields";
@@ -18,10 +17,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityUniversalAuthContent = ({ export const ViewIdentityUniversalAuthContent = ({
identityId, identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete, onDelete,
popUp, onEdit,
lockedOut, lockedOut,
onResetAllLockouts onResetAllLockouts
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
@@ -51,23 +48,8 @@ export const ViewIdentityUniversalAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityUniversalAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper <ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
{Number(data.accessTokenPeriod) > 0 ? ( {Number(data.accessTokenPeriod) > 0 ? (
<IdentityAuthFieldDisplay label="Access Token Period (seconds)"> <IdentityAuthFieldDisplay label="Access Token Period (seconds)">
{data.accessTokenPeriod} {data.accessTokenPeriod}
@@ -0,0 +1 @@
export * from "./ViewIdentityAuth";
@@ -0,0 +1,7 @@
export type ViewAuthMethodProps = {
identityId: string;
onDelete: () => void;
onEdit: () => void;
lockedOut: boolean;
onResetAllLockouts: () => void;
};
@@ -1,20 +0,0 @@
import { ReactNode } from "react";
type Props = {
label: string;
children: ReactNode;
className?: string;
};
export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => {
return (
<div className={className}>
<span className="text-sm text-mineshaft-400">{label}</span>
{children ? (
<p className="text-base leading-4 break-words">{children}</p>
) : (
<p className="text-base leading-4 text-bunker-400 italic">Not set</p>
)}
</div>
);
};
@@ -1,214 +0,0 @@
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";
import { useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks";
import {
IdentityAuthMethod,
identityAuthToNameMap,
useDeleteIdentityAliCloudAuth,
useDeleteIdentityAwsAuth,
useDeleteIdentityAzureAuth,
useDeleteIdentityGcpAuth,
useDeleteIdentityJwtAuth,
useDeleteIdentityKubernetesAuth,
useDeleteIdentityLdapAuth,
useDeleteIdentityOciAuth,
useDeleteIdentityOidcAuth,
useDeleteIdentityTlsCertAuth,
useDeleteIdentityTokenAuth,
useDeleteIdentityUniversalAuth
} from "@app/hooks/api";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityAliCloudAuthContent } from "./ViewIdentityAliCloudAuthContent";
import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent";
import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
import { ViewIdentityLdapAuthContent } from "./ViewIdentityLdapAuthContent";
import { ViewIdentityOciAuthContent } from "./ViewIdentityOciAuthContent";
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
import { ViewIdentityTlsCertAuthContent } from "./ViewIdentityTlsCertAuthContent";
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
type Props = {
identityId: string;
authMethod?: IdentityAuthMethod;
lockedOut: boolean;
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
onDeleteAuthMethod: () => void;
onResetAllLockouts: () => void;
};
type TRevokeOptions = {
identityId: string;
} & ({ projectId: string } | { organizationId: string });
export const Content = ({
identityId,
authMethod,
lockedOut,
onDeleteAuthMethod,
onResetAllLockouts
}: Pick<
Props,
"authMethod" | "lockedOut" | "identityId" | "onDeleteAuthMethod" | "onResetAllLockouts"
>) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { projectId } = useParams({
strict: false
});
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"revokeAuthMethod",
"upgradePlan",
"identityAuthMethod"
] as const);
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
const { mutateAsync: revokeTlsCertAuth } = useDeleteIdentityTlsCertAuth();
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
const { mutateAsync: revokeAliCloudAuth } = useDeleteIdentityAliCloudAuth();
const { mutateAsync: revokeOciAuth } = useDeleteIdentityOciAuth();
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
let Component: (props: ViewAuthMethodProps) => JSX.Element;
let revokeMethod: (revokeOptions: TRevokeOptions) => Promise<any>;
const handleDelete = () => handlePopUpOpen("revokeAuthMethod");
switch (authMethod) {
case IdentityAuthMethod.UNIVERSAL_AUTH:
revokeMethod = revokeUniversalAuth;
Component = ViewIdentityUniversalAuthContent;
break;
case IdentityAuthMethod.TOKEN_AUTH:
revokeMethod = revokeTokenAuth;
Component = ViewIdentityTokenAuthContent;
break;
case IdentityAuthMethod.KUBERNETES_AUTH:
revokeMethod = revokeKubernetesAuth;
Component = ViewIdentityKubernetesAuthContent;
break;
case IdentityAuthMethod.GCP_AUTH:
revokeMethod = revokeGcpAuth;
Component = ViewIdentityGcpAuthContent;
break;
case IdentityAuthMethod.TLS_CERT_AUTH:
revokeMethod = revokeTlsCertAuth;
Component = ViewIdentityTlsCertAuthContent;
break;
case IdentityAuthMethod.AWS_AUTH:
revokeMethod = revokeAwsAuth;
Component = ViewIdentityAwsAuthContent;
break;
case IdentityAuthMethod.AZURE_AUTH:
revokeMethod = revokeAzureAuth;
Component = ViewIdentityAzureAuthContent;
break;
case IdentityAuthMethod.OCI_AUTH:
revokeMethod = revokeOciAuth;
Component = ViewIdentityOciAuthContent;
break;
case IdentityAuthMethod.ALICLOUD_AUTH:
revokeMethod = revokeAliCloudAuth;
Component = ViewIdentityAliCloudAuthContent;
break;
case IdentityAuthMethod.OIDC_AUTH:
revokeMethod = revokeOidcAuth;
Component = ViewIdentityOidcAuthContent;
break;
case IdentityAuthMethod.JWT_AUTH:
revokeMethod = revokeJwtAuth;
Component = ViewIdentityJwtAuthContent;
break;
case IdentityAuthMethod.LDAP_AUTH:
revokeMethod = revokeLdapAuth;
Component = ViewIdentityLdapAuthContent;
break;
default:
throw new Error(`Unhandled Auth Method: ${authMethod}`);
}
const handleDeleteAuthMethod = async () => {
await revokeMethod({
identityId,
...(projectId
? { projectId }
: {
organizationId: orgId
})
});
createNotification({
text: "Successfully removed auth method",
type: "success"
});
handlePopUpToggle("revokeAuthMethod", false);
onDeleteAuthMethod();
};
return (
<>
<Component
identityId={identityId}
onDelete={handleDelete}
onResetAllLockouts={onResetAllLockouts}
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
lockedOut={lockedOut}
/>
<DeleteActionModal
isOpen={popUp?.revokeAuthMethod?.isOpen}
title={`Are you sure you want to remove ${identityAuthToNameMap[authMethod]} on this identity?`}
onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
deleteKey="confirm"
buttonText="Remove"
onDeleteApproved={handleDeleteAuthMethod}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`}
/>
</>
);
};
export const ViewIdentityAuthModal = ({
isOpen,
onOpenChange,
authMethod,
identityId,
lockedOut,
onResetAllLockouts
}: Omit<Props, "onDeleteAuthMethod">) => {
if (!identityId || !authMethod) return null;
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent className="max-w-2xl" title={identityAuthToNameMap[authMethod]}>
<Content
identityId={identityId}
authMethod={authMethod}
lockedOut={lockedOut}
onDeleteAuthMethod={() => onOpenChange(false)}
onResetAllLockouts={() => onResetAllLockouts()}
/>
</ModalContent>
</Modal>
);
};
@@ -1 +0,0 @@
export * from "./ViewIdentityAuthModal";
@@ -1,14 +0,0 @@
import { UsePopUpState } from "@app/hooks/usePopUp";
export type ViewAuthMethodProps = {
identityId: string;
onDelete: () => void;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan", "identityAuthMethod"]>) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean
) => void;
popUp: UsePopUpState<["revokeAuthMethod", "upgradePlan", "identityAuthMethod"]>;
lockedOut: boolean;
onResetAllLockouts: () => void;
};
@@ -179,14 +179,13 @@ const Page = () => {
search={{ search={{
selectedTab: ProjectAccessControlTabs.Identities selectedTab: ProjectAccessControlTabs.Identities
}} }}
className="mb-3 flex w-fit items-center gap-x-1 text-sm text-mineshaft-400 transition duration-100 hover:text-mineshaft-400/80" className="mb-4 flex w-fit items-center gap-x-1 text-sm text-mineshaft-400 transition duration-100 hover:text-mineshaft-400/80"
> >
<ChevronLeftIcon size={16} /> <ChevronLeftIcon size={16} />
Project Machine Identities Project Machine Identities
</Link> </Link>
<PageHeader <PageHeader
scope={currentProject.type} scope={currentProject.type}
className="mb-20"
description={`Configure and manage${isProjectIdentity ? " machine identity and " : " "}project access control`} description={`Configure and manage${isProjectIdentity ? " machine identity and " : " "}project access control`}
title={identityMembershipDetails.identity.name} title={identityMembershipDetails.identity.name}
> >
@@ -256,14 +255,14 @@ const Page = () => {
</UnstableDropdownMenuContent> </UnstableDropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</PageHeader> </PageHeader>
<div className="flex flex-col gap-20 lg:flex-row"> <div className="flex flex-col gap-5 lg:flex-row">
<ProjectIdentityDetailsSection <ProjectIdentityDetailsSection
identity={identity || { ...identityMembershipDetails?.identity, projectId: "" }} identity={identity || { ...identityMembershipDetails?.identity, projectId: "" }}
isOrgIdentity={!isProjectIdentity} isOrgIdentity={!isProjectIdentity}
membership={identityMembershipDetails!} membership={identityMembershipDetails!}
/> />
<div className="flex flex-1 flex-col gap-y-20"> <div className="flex flex-1 flex-col gap-y-5">
{identity ? ( {identity ? (
<ProjectIdentityAuthenticationSection <ProjectIdentityAuthenticationSection
identity={identity} identity={identity}
@@ -81,7 +81,9 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe
return ( return (
<> <>
<UnstableCard> <UnstableCard>
<UnstableCardHeader className="border-b"> <UnstableCardHeader
// className="border-b"
>
<UnstableCardTitle>Project Additional Privileges</UnstableCardTitle> <UnstableCardTitle>Project Additional Privileges</UnstableCardTitle>
<UnstableCardDescription> <UnstableCardDescription>
Assign one-off policies to this machine identity Assign one-off policies to this machine identity
@@ -96,7 +98,7 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe
> >
{(isAllowed) => ( {(isAllowed) => (
<UnstableButton <UnstableButton
variant="project" variant="outline"
size="xs" size="xs"
onClick={() => { onClick={() => {
handlePopUpOpen("modifyPrivilege"); handlePopUpOpen("modifyPrivilege");
@@ -96,7 +96,9 @@ export const IdentityRoleDetailsSection = ({
return ( return (
<> <>
<UnstableCard> <UnstableCard>
<UnstableCardHeader className="border-b"> <UnstableCardHeader
// className="border-b"
>
<UnstableCardTitle>Project Roles</UnstableCardTitle> <UnstableCardTitle>Project Roles</UnstableCardTitle>
<UnstableCardDescription> <UnstableCardDescription>
Manage roles assigned to this machine identity Manage roles assigned to this machine identity
@@ -112,7 +114,7 @@ export const IdentityRoleDetailsSection = ({
{(isAllowed) => ( {(isAllowed) => (
<UnstableButton <UnstableButton
size="xs" size="xs"
variant="project" variant="outline"
onClick={() => { onClick={() => {
handlePopUpOpen("modifyRole"); handlePopUpOpen("modifyRole");
}} }}
@@ -1,11 +1,11 @@
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { EllipsisIcon, LockIcon, PlusIcon } from "lucide-react"; import { PlusIcon } from "lucide-react";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { Tooltip } from "@app/components/v2"; import { DeleteActionModal } from "@app/components/v2";
import { import {
Badge,
UnstableButton, UnstableButton,
UnstableCard, UnstableCard,
UnstableCardAction, UnstableCardAction,
@@ -17,20 +17,29 @@ import {
UnstableEmptyContent, UnstableEmptyContent,
UnstableEmptyDescription, UnstableEmptyDescription,
UnstableEmptyHeader, UnstableEmptyHeader,
UnstableEmptyTitle, UnstableEmptyTitle
UnstableIconButton,
UnstableTable,
UnstableTableBody,
UnstableTableCell,
UnstableTableHead,
UnstableTableHeader,
UnstableTableRow
} from "@app/components/v3"; } from "@app/components/v3";
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/context";
import { IdentityAuthMethod, identityAuthToNameMap, TProjectIdentity } from "@app/hooks/api"; import {
IdentityAuthMethod,
identityAuthToNameMap,
TProjectIdentity,
useDeleteIdentityAliCloudAuth,
useDeleteIdentityAwsAuth,
useDeleteIdentityAzureAuth,
useDeleteIdentityGcpAuth,
useDeleteIdentityJwtAuth,
useDeleteIdentityKubernetesAuth,
useDeleteIdentityLdapAuth,
useDeleteIdentityOciAuth,
useDeleteIdentityOidcAuth,
useDeleteIdentityTlsCertAuth,
useDeleteIdentityTokenAuth,
useDeleteIdentityUniversalAuth
} from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { IdentityAuthMethodModal } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal"; import { IdentityAuthMethodModal } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal"; import { ViewIdentityAuth } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth";
type Props = { type Props = {
identity: TProjectIdentity; identity: TProjectIdentity;
@@ -39,17 +48,58 @@ type Props = {
export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity }: Props) => { export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity }: Props) => {
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([ const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([
"viewAuthMethod",
"identityAuthMethod", "identityAuthMethod",
"upgradePlan" "upgradePlan",
"revokeAuthMethod"
]); ]);
const hasAuthMethods = Boolean(identity.authMethods.length); const hasAuthMethods = Boolean(identity.authMethods.length);
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
const { mutateAsync: revokeTlsCertAuth } = useDeleteIdentityTlsCertAuth();
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
const { mutateAsync: revokeAliCloudAuth } = useDeleteIdentityAliCloudAuth();
const { mutateAsync: revokeOciAuth } = useDeleteIdentityOciAuth();
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
const RemoveAuthMap = {
[IdentityAuthMethod.KUBERNETES_AUTH]: revokeKubernetesAuth,
[IdentityAuthMethod.GCP_AUTH]: revokeGcpAuth,
[IdentityAuthMethod.TLS_CERT_AUTH]: revokeTlsCertAuth,
[IdentityAuthMethod.AWS_AUTH]: revokeAwsAuth,
[IdentityAuthMethod.AZURE_AUTH]: revokeAzureAuth,
[IdentityAuthMethod.ALICLOUD_AUTH]: revokeAliCloudAuth,
[IdentityAuthMethod.UNIVERSAL_AUTH]: revokeUniversalAuth,
[IdentityAuthMethod.TOKEN_AUTH]: revokeTokenAuth,
[IdentityAuthMethod.OCI_AUTH]: revokeOciAuth,
[IdentityAuthMethod.OIDC_AUTH]: revokeOidcAuth,
[IdentityAuthMethod.JWT_AUTH]: revokeJwtAuth,
[IdentityAuthMethod.LDAP_AUTH]: revokeLdapAuth
};
const handleDeleteAuthMethod = async (authMethod: IdentityAuthMethod) => {
await RemoveAuthMap[authMethod]({
identityId: identity.id,
projectId: identity.projectId!
});
createNotification({
text: "Successfully removed auth method",
type: "success"
});
handlePopUpToggle("revokeAuthMethod", false);
};
return ( return (
<> <>
<UnstableCard> <UnstableCard>
<UnstableCardHeader className="border-b"> <UnstableCardHeader>
<UnstableCardTitle>Authentication</UnstableCardTitle> <UnstableCardTitle>Authentication</UnstableCardTitle>
<UnstableCardDescription>Configure authentication methods</UnstableCardDescription> <UnstableCardDescription>Configure authentication methods</UnstableCardDescription>
{hasAuthMethods && {hasAuthMethods &&
@@ -65,7 +115,7 @@ export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity
> >
{(isAllowed) => ( {(isAllowed) => (
<UnstableButton <UnstableButton
variant="project" variant="outline"
isFullWidth isFullWidth
size="xs" size="xs"
isDisabled={!isAllowed} isDisabled={!isAllowed}
@@ -87,43 +137,12 @@ export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity
</UnstableCardHeader> </UnstableCardHeader>
<UnstableCardContent> <UnstableCardContent>
{identity.authMethods.length > 0 ? ( {identity.authMethods.length > 0 ? (
<UnstableTable> <ViewIdentityAuth
<UnstableTableHeader> authMethods={identity.authMethods}
<UnstableTableHead className="w-full">Method</UnstableTableHead> identityId={identity.id}
<UnstableTableHead className="w-5" /> onResetAllLockouts={refetchIdentity}
</UnstableTableHeader> activeLockoutAuthMethods={identity.activeLockoutAuthMethods}
<UnstableTableBody> />
{identity.authMethods.map((authMethod) => (
<UnstableTableRow
key={authMethod}
className="cursor-pointer"
onClick={() =>
handlePopUpOpen("viewAuthMethod", {
authMethod,
lockedOut: identity.activeLockoutAuthMethods?.includes(authMethod) ?? false,
refetchIdentity
})
}
>
<UnstableTableCell>{identityAuthToNameMap[authMethod]}</UnstableTableCell>
<UnstableTableCell>
<div className="flex items-center gap-2">
{identity.activeLockoutAuthMethods?.includes(authMethod) && (
<Tooltip content="Auth method has active lockouts">
<Badge isSquare variant="danger">
<LockIcon />
</Badge>
</Tooltip>
)}
<UnstableIconButton variant="ghost" size="xs">
<EllipsisIcon />
</UnstableIconButton>
</div>
</UnstableTableCell>
</UnstableTableRow>
))}
</UnstableTableBody>
</UnstableTable>
) : ( ) : (
<UnstableEmpty className="rounded-sm border bg-mineshaft-800/50"> <UnstableEmpty className="rounded-sm border bg-mineshaft-800/50">
<UnstableEmptyHeader> <UnstableEmptyHeader>
@@ -175,13 +194,15 @@ export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity
text={(popUp.upgradePlan?.data as { description: string })?.description} text={(popUp.upgradePlan?.data as { description: string })?.description}
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
/> />
<ViewIdentityAuthModal <DeleteActionModal
isOpen={popUp.viewAuthMethod.isOpen} isOpen={popUp?.revokeAuthMethod?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)} title={`Are you sure you want to remove ${popUp?.revokeAuthMethod?.data ? identityAuthToNameMap[popUp.revokeAuthMethod.data as IdentityAuthMethod] : "this auth method"} on this identity?`}
authMethod={popUp.viewAuthMethod.data?.authMethod} onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
lockedOut={popUp.viewAuthMethod.data?.lockedOut || false} deleteKey="confirm"
identityId={identity.id} buttonText="Remove"
onResetAllLockouts={popUp.viewAuthMethod.data?.refetchIdentity} onDeleteApproved={() =>
handleDeleteAuthMethod(popUp.revokeAuthMethod.data as IdentityAuthMethod)
}
/> />
</> </>
); );
@@ -12,7 +12,6 @@ import {
DetailValue, DetailValue,
OrgIcon, OrgIcon,
ProjectIcon, ProjectIcon,
UnstableButton,
UnstableButtonGroup, UnstableButtonGroup,
UnstableCard, UnstableCard,
UnstableCardAction, UnstableCardAction,
@@ -44,8 +43,10 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
return ( return (
<> <>
<UnstableCard className="w-full max-w-84"> <UnstableCard className="w-full max-w-[22rem]">
<UnstableCardHeader className="border-b"> <UnstableCardHeader
// className="border-b"
>
<UnstableCardTitle>Details</UnstableCardTitle> <UnstableCardTitle>Details</UnstableCardTitle>
<UnstableCardDescription>Machine identity details</UnstableCardDescription> <UnstableCardDescription>Machine identity details</UnstableCardDescription>
{!isOrgIdentity && ( {!isOrgIdentity && (
@@ -57,17 +58,16 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
})} })}
> >
{(isAllowed) => ( {(isAllowed) => (
<UnstableButton <UnstableIconButton
isDisabled={!isAllowed} isDisabled={!isAllowed}
onClick={() => { onClick={() => {
handlePopUpOpen("editIdentity"); handlePopUpOpen("editIdentity");
}} }}
size="xs" size="xs"
variant="project" variant="outline"
> >
<PencilIcon /> <PencilIcon />
Edit Details </UnstableIconButton>
</UnstableButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</UnstableCardAction> </UnstableCardAction>
@@ -92,6 +92,7 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
variant="ghost" variant="ghost"
size="xs" size="xs"
> >
{/* TODO(scott): color this should be a button variant */}
{isCopyingId ? <CheckIcon /> : <ClipboardListIcon className="text-label" />} {isCopyingId ? <CheckIcon /> : <ClipboardListIcon className="text-label" />}
</UnstableIconButton> </UnstableIconButton>
</Tooltip> </Tooltip>
@@ -118,9 +119,13 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
<DetailValue className="flex flex-wrap gap-2"> <DetailValue className="flex flex-wrap gap-2">
{identity?.metadata?.length ? ( {identity?.metadata?.length ? (
identity.metadata?.map((el) => ( identity.metadata?.map((el) => (
<UnstableButtonGroup key={el.id}> <UnstableButtonGroup className="min-w-0" key={el.id}>
<Badge>{el.key}</Badge> <Badge isTruncatable>
<Badge variant="outline">{el.value}</Badge> <span>{el.key}</span>
</Badge>
<Badge variant="outline" isTruncatable>
<span>{el.value}</span>
</Badge>
</UnstableButtonGroup> </UnstableButtonGroup>
)) ))
) : ( ) : (