diff --git a/frontend/src/hooks/api/reactQuery.tsx b/frontend/src/hooks/api/reactQuery.tsx index d6ffc9378..f6ae0ca76 100644 --- a/frontend/src/hooks/api/reactQuery.tsx +++ b/frontend/src/hooks/api/reactQuery.tsx @@ -182,7 +182,7 @@ export const queryClient = new QueryClient({ createNotification({ title: "Bad Request", type: "error", - text: `${serverResponse.message}${serverResponse.message.endsWith(".") ? "" : "."}`, + text: `${serverResponse.message}${serverResponse.message?.endsWith(".") ? "" : "."}`, copyActions: [ { value: serverResponse.reqId, diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx index 70128fa1a..27d2f727a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx @@ -7,10 +7,10 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; import { IdentityAuthMethodModalContent } from "./IdentityAuthMethodModalContent"; type Props = { - popUp: UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>; + popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>, state?: boolean ) => void; }; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx index 859b21883..5010b2cb6 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx @@ -4,30 +4,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; -import { createNotification } from "@app/components/notifications"; -import { - Badge, - DeleteActionModal, - FormControl, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { - useDeleteIdentityAwsAuth, - useDeleteIdentityAzureAuth, - useDeleteIdentityGcpAuth, - useDeleteIdentityKubernetesAuth, - useDeleteIdentityOidcAuth, - useDeleteIdentityTokenAuth, - useDeleteIdentityUniversalAuth -} from "@app/hooks/api"; -import { - IdentityAuthMethod, - identityAuthToNameMap, - useDeleteIdentityJwtAuth -} from "@app/hooks/api/identities"; +import { Badge, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm"; @@ -40,10 +18,10 @@ import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm"; import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm"; type Props = { - popUp: UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>; + popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>, state?: boolean ) => void; @@ -56,13 +34,7 @@ type Props = { setSelectedAuthMethod: (authMethod: IdentityAuthMethod) => void; }; -type TRevokeOptions = { - identityId: string; - organizationId: string; -}; - type TRevokeMethods = { - revokeMethod: (revokeOptions: TRevokeOptions) => Promise; render: () => JSX.Element; }; @@ -96,18 +68,6 @@ export const IdentityAuthMethodModalContent = ({ initialAuthMethod, setSelectedAuthMethod }: Props) => { - const { currentOrg } = useOrganization(); - const orgId = currentOrg?.id || ""; - - const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth(); - const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth(); - const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth(); - const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth(); - const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth(); - const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth(); - const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth(); - const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth(); - const { control, watch } = useForm({ resolver: zodResolver(schema), defaultValues: async () => { @@ -149,7 +109,6 @@ export const IdentityAuthMethodModalContent = ({ const methodMap: Record = { [IdentityAuthMethod.UNIVERSAL_AUTH]: { - revokeMethod: revokeUniversalAuth, render: () => ( ( ( ( ( ( ( ( handlePopUpToggle("upgradePlan", isOpen)} text="You can use IP allowlisting if you switch to Infisical's Pro plan." /> - handlePopUpToggle("revokeAuthMethod", isOpen)} - deleteKey="confirm" - buttonText="Remove" - onDeleteApproved={async () => { - if (!identityAuthMethodData.authMethod || !orgId || !selectedMethodItem) { - return; - } - - try { - await selectedMethodItem.revokeMethod({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - - createNotification({ - text: "Successfully removed auth method", - type: "success" - }); - - handlePopUpToggle("revokeAuthMethod", false); - handlePopUpToggle("identityAuthMethod", false); - } catch { - createNotification({ - text: "Failed to remove auth method", - type: "error" - }); - } - }} - /> ); }; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx index 59528e3a7..9c73f50b4 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx @@ -49,7 +49,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData: { @@ -360,17 +360,6 @@ export const IdentityAwsAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx index 7af879888..e2491c261 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx @@ -44,7 +44,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData: { @@ -356,17 +356,6 @@ export const IdentityAzureAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx index 0cccb8213..71a171eac 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx @@ -45,7 +45,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData: { @@ -389,17 +389,6 @@ export const IdentityGcpAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx index e975b272d..d1cdfe6a7 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx @@ -85,7 +85,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData: { @@ -671,17 +671,6 @@ export const IdentityJwtAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index 8234d5ea7..18b9d90cb 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -47,7 +47,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData: { @@ -429,17 +429,6 @@ export const IdentityKubernetesAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx index 01d1d0b44..b1687feea 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx @@ -48,7 +48,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData: { @@ -510,17 +510,6 @@ export const IdentityOidcAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx index 8511780f9..bcd3573cb 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx @@ -39,7 +39,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData?: { @@ -268,17 +268,6 @@ export const IdentityTokenAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal.tsx deleted file mode 100644 index 67f0a576f..000000000 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal.tsx +++ /dev/null @@ -1,344 +0,0 @@ -import { useEffect, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { faCheck, faCopy, faKey, faXmark } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { format } from "date-fns"; -import { z } from "zod"; - -import { createNotification } from "@app/components/notifications"; -import { - Button, - // DeleteActionModal, - EmptyState, - FormControl, - IconButton, - Input, - Modal, - ModalContent, - Table, - TableContainer, - TableSkeleton, - TBody, - Td, - Th, - THead, - Tr -} from "@app/components/v2"; -import { useToggle } from "@app/hooks"; -import { - useCreateIdentityUniversalAuthClientSecret, - useGetIdentityUniversalAuth, - useGetIdentityUniversalAuthClientSecrets -} from "@app/hooks/api"; -import { UsePopUpState } from "@app/hooks/usePopUp"; - -const schema = z.object({ - description: z.string(), - ttl: z - .string() - .refine((value) => Number(value) <= 315360000, "TTL cannot be greater than 315360000"), - numUsesLimit: z.string() -}); - -export type FormData = z.infer; - -type Props = { - popUp: UsePopUpState<["universalAuthClientSecret", "revokeClientSecret"]>; - handlePopUpOpen: ( - popUpName: keyof UsePopUpState<["revokeClientSecret"]>, - data?: { - clientSecretPrefix: string; - clientSecretId: string; - } - ) => void; - handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["universalAuthClientSecret", "revokeClientSecret"]>, - state?: boolean - ) => void; -}; - -export const IdentityUniversalAuthClientSecretModal = ({ - popUp, - handlePopUpOpen, - handlePopUpToggle -}: Props) => { - const { t } = useTranslation(); - - const [token, setToken] = useState(""); - const [isClientSecretCopied, setIsClientSecretCopied] = useToggle(false); - const [isClientIdCopied, setIsClientIdCopied] = useToggle(false); - - const popUpData = popUp?.universalAuthClientSecret?.data as { - identityId?: string; - name?: string; - }; - - const { data, isPending } = useGetIdentityUniversalAuthClientSecrets(popUpData?.identityId ?? ""); - const { data: identityUniversalAuth } = useGetIdentityUniversalAuth(popUpData?.identityId ?? ""); - - const { mutateAsync: createClientSecretMutateAsync } = - useCreateIdentityUniversalAuthClientSecret(); - - const { - control, - handleSubmit, - reset, - formState: { isSubmitting } - } = useForm({ - resolver: zodResolver(schema), - defaultValues: { - description: "", - ttl: "", - numUsesLimit: "" - } - }); - - useEffect(() => { - let timer: NodeJS.Timeout; - if (isClientSecretCopied) { - timer = setTimeout(() => setIsClientSecretCopied.off(), 2000); - } - - return () => clearTimeout(timer); - }, [isClientSecretCopied]); - - useEffect(() => { - let timer: NodeJS.Timeout; - if (isClientIdCopied) { - timer = setTimeout(() => setIsClientIdCopied.off(), 2000); - } - - return () => clearTimeout(timer); - }, [isClientIdCopied]); - - const onFormSubmit = async ({ description, ttl, numUsesLimit }: FormData) => { - try { - if (!popUpData?.identityId) return; - - const { clientSecret } = await createClientSecretMutateAsync({ - identityId: popUpData.identityId, - description, - ttl: Number(ttl), - numUsesLimit: Number(numUsesLimit) - }); - - setToken(clientSecret); - - createNotification({ - text: "Successfully created client secret", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create client secret", - type: "error" - }); - } - }; - - const hasToken = Boolean(token); - - return ( - { - handlePopUpToggle("universalAuthClientSecret", isOpen); - reset(); - setToken(""); - }} - > - -

Client ID

-
-

{identityUniversalAuth?.clientId ?? ""}

- { - navigator.clipboard.writeText(identityUniversalAuth?.clientId ?? ""); - setIsClientIdCopied.on(); - }} - > - - - {t("common.click-to-copy")} - - -
-

New Client Secret

- {hasToken ? ( -
-
-

We will only show this secret once

- -
-
-

{token}

- { - navigator.clipboard.writeText(token); - setIsClientSecretCopied.on(); - }} - > - - - {t("common.click-to-copy")} - - -
-
- ) : ( -
- ( - - - - )} - /> -
- ( - -
- -
-
- )} - /> - ( - -
- - -
-
- )} - /> -
- - )} -

Client Secrets

- - - - - - - - - - - - {isPending && } - {!isPending && - data && - data.length > 0 && - data.map( - ({ - id, - description, - clientSecretTTL, - clientSecretPrefix, - clientSecretNumUses, - clientSecretNumUsesLimit, - createdAt - }) => { - let expiresAt; - if (clientSecretTTL > 0) { - expiresAt = new Date(new Date(createdAt).getTime() + clientSecretTTL * 1000); - } - - return ( - - - - - - - - ); - } - )} - {!isPending && data && data?.length === 0 && ( - - - - )} - -
DescriptionNum UsesExpires AtClient Secret -
{description === "" ? "-" : description}{`${clientSecretNumUses}${ - clientSecretNumUsesLimit ? `/${clientSecretNumUsesLimit}` : "" - }`}{expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"}{`${clientSecretPrefix}****`} - { - handlePopUpOpen("revokeClientSecret", { - clientSecretPrefix, - clientSecretId: id - }); - }} - size="lg" - colorSchema="primary" - variant="plain" - ariaLabel="update" - > - - -
- -
-
-
-
- ); -}; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx index 6738378ac..8199ac4c1 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx @@ -52,7 +52,7 @@ export type FormData = z.infer; type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean ) => void; identityAuthMethodData?: { @@ -393,17 +393,6 @@ export const IdentityUniversalAuthForm = ({ Cancel - {isUpdate && ( - - )} ); diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 5fff4d474..bfc50359c 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "@tanstack/react-router"; @@ -19,27 +18,17 @@ import { } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { - IdentityAuthMethod, - useDeleteIdentity, - useGetIdentityById, - useRevokeIdentityTokenAuthToken, - useRevokeIdentityUniversalAuthClientSecret -} from "@app/hooks/api"; -import { Identity } from "@app/hooks/api/identities/types"; +import { useDeleteIdentity, useGetIdentityById } 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 { IdentityUniversalAuthClientSecretModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal"; import { IdentityAuthenticationSection, - IdentityClientSecretModal, IdentityDetailsSection, - IdentityProjectsSection, - IdentityTokenListModal, - IdentityTokenModal + IdentityProjectsSection } from "./components"; const Page = () => { @@ -52,25 +41,13 @@ const Page = () => { const orgId = currentOrg?.id || ""; const { data } = useGetIdentityById(identityId); const { mutateAsync: deleteIdentity } = useDeleteIdentity(); - const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken(); - const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); - - const [selectedAuthMethod, setSelectedAuthMethod] = useState< - Identity["authMethods"][number] | null - >(null); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "identity", "deleteIdentity", "identityAuthMethod", - "revokeAuthMethod", - "token", - "tokenList", - "revokeToken", - "clientSecret", - "revokeClientSecret", - "universalAuthClientSecret", // list of client secrets - "upgradePlan" + "upgradePlan", + "viewAuthMethod" ] as const); const onDeleteIdentitySubmit = async (id: string) => { @@ -104,63 +81,6 @@ const Page = () => { } }; - const onRevokeTokenSubmit = async ({ - identityId: parentIdentityId, - tokenId, - name - }: { - identityId: string; - tokenId: string; - name: string; - }) => { - try { - await revokeToken({ - identityId: parentIdentityId, - tokenId - }); - - handlePopUpClose("revokeToken"); - - createNotification({ - text: `Successfully revoked token ${name ?? ""}`, - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete identity"; - - createNotification({ - text, - type: "error" - }); - } - }; - - const onDeleteClientSecretSubmit = async ({ clientSecretId }: { clientSecretId: string }) => { - try { - if (!data?.identity.id || selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) return; - - await revokeClientSecret({ - identityId: data?.identity.id, - clientSecretId - }); - - handlePopUpToggle("revokeClientSecret", false); - - createNotification({ - text: "Successfully deleted client secret", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete client secret", - type: "error" - }); - } - }; - return (
{data && ( @@ -244,8 +164,6 @@ const Page = () => {
@@ -260,18 +178,6 @@ const Page = () => { handlePopUpOpen={handlePopUpOpen} handlePopUpToggle={handlePopUpToggle} /> - - - - handlePopUpToggle("upgradePlan", isOpen)} @@ -290,41 +196,19 @@ const Page = () => { ) } /> - handlePopUpToggle("revokeToken", isOpen)} - deleteKey="confirm" - onDeleteApproved={() => { - const revokeTokenData = popUp?.revokeToken?.data as { - identityId: string; - tokenId: string; - name: string; - }; - - return onRevokeTokenSubmit(revokeTokenData); - }} - /> - handlePopUpToggle("revokeClientSecret", isOpen)} - deleteKey="confirm" - onDeleteApproved={() => { - const deleteClientSecretData = popUp?.revokeClientSecret?.data as { - clientSecretId: string; - clientSecretPrefix: string; - }; - - return onDeleteClientSecretSubmit({ - clientSecretId: deleteClientSecretData.clientSecretId - }); - }} + handlePopUpToggle("viewAuthMethod", isOpen)} + authMethod={popUp.viewAuthMethod.data} + identityId={identityId} + onEditAuthMethod={(authMethod) => + handlePopUpOpen("identityAuthMethod", { + identityId, + name: data?.identity.name, + allAuthMethods: data?.identity.authMethods, + authMethod + }) + } />
); diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx index 23f72d7da..544852dcb 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx @@ -1,155 +1,66 @@ -import { useEffect } from "react"; -import { faPencil, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { faCog, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { OrgPermissionCan } from "@app/components/permissions"; -import { Button, IconButton, Select, SelectItem, Tooltip } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; -import { useGetIdentityById } from "@app/hooks/api"; -import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities"; -import { Identity } from "@app/hooks/api/identities/types"; +import { Button } from "@app/components/v2"; +import { IdentityAuthMethod, identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; -import { IdentityClientSecrets } from "./IdentityClientSecrets"; -import { IdentityTokens } from "./IdentityTokens"; - type Props = { identityId: string; - setSelectedAuthMethod: (authMethod: Identity["authMethods"][number] | null) => void; - selectedAuthMethod: Identity["authMethods"][number] | null; handlePopUpOpen: ( - popUpName: keyof UsePopUpState< - [ - "clientSecret", - "identityAuthMethod", - "revokeClientSecret", - "token", - "revokeToken", - "universalAuthClientSecret", - "tokenList" - ] - >, - data?: object + popUpName: keyof UsePopUpState<["identityAuthMethod", "viewAuthMethod"]>, + data?: object | IdentityAuthMethod ) => void; }; -export const IdentityAuthenticationSection = ({ - identityId, - setSelectedAuthMethod, - selectedAuthMethod, - handlePopUpOpen -}: Props) => { +export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: Props) => { const { data } = useGetIdentityById(identityId); - useEffect(() => { - if (!data?.identity) return; - - if (data.identity.authMethods?.length) { - setSelectedAuthMethod(data.identity.authMethods[0]); - } - - // eslint-disable-next-line consistent-return - return () => setSelectedAuthMethod(null); - }, [data?.identity]); - return data ? (

Authentication

- - - {(isAllowed) => { - return ( - - - handlePopUpOpen("identityAuthMethod", { - identityId, - name: data.identity.name, - allAuthMethods: data.identity.authMethods - }) - } - > - - - - ); - }} -
{data.identity.authMethods.length > 0 ? ( - <> -
-
-

Auth Method

-
-
-
- -
-
- - { - handlePopUpOpen("identityAuthMethod", { - identityId, - name: data.identity.name, - authMethod: selectedAuthMethod, - allAuthMethods: data.identity.authMethods - }); - }} - ariaLabel="copy icon" - variant="plain" - className="group relative" - > - - - {" "} -
-
-
- {selectedAuthMethod === IdentityAuthMethod.UNIVERSAL_AUTH && ( - - )} - {selectedAuthMethod === IdentityAuthMethod.TOKEN_AUTH && ( - - )} - +
+ {data.identity.authMethods.map((authMethod) => ( + + ))} +
) : (

No authentication methods configured. Get started by creating a new auth method.

-
)} + {!Object.values(IdentityAuthMethod).every((method) => + data.identity.authMethods.includes(method) + ) && ( + + )}
) : (
diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityTokens.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityTokens.tsx index 18b61b0ca..30c0b7b97 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityTokens.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityTokens.tsx @@ -1,4 +1,4 @@ -import { faEllipsis, faKey } from "@fortawesome/free-solid-svg-icons"; +import { faEllipsisVertical, faKey } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; @@ -8,6 +8,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + IconButton, Tooltip } from "@app/components/v2"; import { useGetIdentityById, useGetIdentityTokensTokenAuth } from "@app/hooks/api"; @@ -27,10 +28,13 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => { return (
{tokens?.length ? ( -
-

{`Access Tokens (${tokens.length})`}

+
+

{`Access Tokens (${tokens.length})`}

-
-
-

{token}

- { - navigator.clipboard.writeText(token); - setIsClientSecretCopied.on(); - }} - > - - - {t("common.click-to-copy")} - - -
-
- ) : ( -
- ( - -
- - -
-
- )} - /> - - )} -

Tokens

- - - - - - - - - - - - {isPending && } - {!isPending && - tokens?.map( - ({ - id, - createdAt, - name, - accessTokenNumUses, - accessTokenNumUsesLimit, - accessTokenMaxTTL, - isAccessTokenRevoked - }) => { - const expiresAt = new Date( - new Date(createdAt).getTime() + accessTokenMaxTTL * 1000 - ); - - return ( - - - - - - - - ); - } - )} - {!isPending && data && data?.length === 0 && ( - - - - )} - -
nameNum UsesCreated AtMax Expires At -
{name === "" ? "-" : name}{`${accessTokenNumUses}${ - accessTokenNumUsesLimit ? `/${accessTokenNumUsesLimit}` : "" - }`}{format(new Date(createdAt), "yyyy-MM-dd")} - {isAccessTokenRevoked ? "Revoked" : `${format(expiresAt, "yyyy-MM-dd")}`} - - {!isAccessTokenRevoked && ( - { - handlePopUpOpen("revokeToken", { - identityId: popUpData?.identityId, - tokenId: id, - name - }); - }} - size="lg" - colorSchema="primary" - variant="plain" - ariaLabel="update" - > - - - )} -
- -
-
- - - ); -}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthFieldDisplay.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthFieldDisplay.tsx new file mode 100644 index 000000000..df2ce3a8c --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthFieldDisplay.tsx @@ -0,0 +1,20 @@ +import { ReactNode } from "react"; + +type Props = { + label: string; + children: ReactNode; + className?: string; +}; + +export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => { + return ( +
+ {label} + {children ? ( +

{children}

+ ) : ( +

Not set

+ )} +
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx new file mode 100644 index 000000000..bf7fda68b --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx @@ -0,0 +1,216 @@ +import { useState } from "react"; +import { faBan, faEdit, faKey, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + DeleteActionModal, + EmptyState, + IconButton, + Pagination, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tooltip, + Tr +} from "@app/components/v2"; +import { usePopUp } from "@app/hooks"; +import { useRevokeIdentityTokenAuthToken } from "@app/hooks/api"; +import { IdentityAccessToken } from "@app/hooks/api/identities/types"; +import { IdentityTokenModal } from "@app/pages/organization/IdentityDetailsByIDPage/components"; + +type Props = { + tokens: IdentityAccessToken[]; + identityId: string; +}; + +export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => { + const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ + "token", + "revokeToken" + ] as const); + + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(5); + + const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken(); + + const onRevokeTokenSubmit = async ({ + identityId: parentIdentityId, + tokenId, + name + }: { + identityId: string; + tokenId: string; + name: string; + }) => { + try { + await revokeToken({ + identityId: parentIdentityId, + tokenId + }); + + handlePopUpClose("revokeToken"); + + createNotification({ + text: `Successfully revoked token ${name ?? ""}`, + type: "success" + }); + } catch (err) { + console.error(err); + const error = err as any; + const text = error?.response?.data?.message ?? "Failed to revoke token"; + + createNotification({ + text, + type: "error" + }); + } + }; + + return ( +
+
+ Access Tokens + +
+ + + {Boolean(tokens?.length) && ( + + + + + + + + )} + + {tokens + .slice((page - 1) * perPage, perPage * page) + .map( + ({ + createdAt, + isAccessTokenRevoked, + name, + accessTokenTTL, + accessTokenNumUsesLimit, + accessTokenNumUses, + id + }) => { + let expiresAt; + if (accessTokenTTL > 0) { + expiresAt = new Date(new Date(createdAt).getTime() + accessTokenTTL * 1000); + } + + return ( + + + + + + + ); + } + )} + +
NameNumber of UsesExpires +
{name || "-"} + {`${accessTokenNumUses}${accessTokenNumUsesLimit ? `/${accessTokenNumUsesLimit}` : ""}`} + + {/* eslint-disable-next-line no-nested-ternary */} + {isAccessTokenRevoked + ? "Revoked" + : expiresAt + ? format(expiresAt, "yyyy-MM-dd") + : "-"} + +
+ + { + handlePopUpOpen("token", { + identityId, + tokenId: id, + name + }); + }} + size="xs" + variant="plain" + ariaLabel="Edit token" + > + + + + {!isAccessTokenRevoked && ( + + { + handlePopUpOpen("revokeToken", { + identityId, + tokenId: id, + name + }); + }} + size="xs" + colorSchema="danger" + variant="plain" + ariaLabel="Revoke token" + > + + + + )} +
+
+ {!tokens?.length && ( + + )} + {tokens.length > 0 && ( + setPage(newPage)} + onChangePerPage={(newPerPage) => setPerPage(newPerPage)} + /> + )} +
+ handlePopUpToggle("revokeToken", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => { + const revokeTokenData = popUp?.revokeToken?.data as { + identityId: string; + tokenId: string; + name: string; + }; + + return onRevokeTokenSubmit(revokeTokenData); + }} + /> + +
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx new file mode 100644 index 000000000..626446e5d --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx @@ -0,0 +1,181 @@ +import { useState } from "react"; +import { faKey, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + DeleteActionModal, + EmptyState, + IconButton, + Pagination, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tooltip, + Tr +} from "@app/components/v2"; +import { usePopUp } from "@app/hooks"; +import { useRevokeIdentityUniversalAuthClientSecret } from "@app/hooks/api"; +import { ClientSecretData } from "@app/hooks/api/identities/types"; +import { IdentityClientSecretModal } from "@app/pages/organization/IdentityDetailsByIDPage/components"; + +type Props = { + clientSecrets: ClientSecretData[]; + identityId: string; +}; + +export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identityId }: Props) => { + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ + "revokeClientSecret", + "clientSecret" + ] as const); + + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(5); + + const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); + + const onDeleteClientSecretSubmit = async (clientSecretId: string) => { + try { + await revokeClientSecret({ + identityId, + clientSecretId + }); + + handlePopUpToggle("revokeClientSecret", false); + + createNotification({ + text: "Successfully deleted client secret", + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to delete client secret", + type: "error" + }); + } + }; + + return ( +
+
+ Client Secrets + +
+ + + {Boolean(clientSecrets?.length) && ( + + + + + + + + + )} + + {clientSecrets + .slice((page - 1) * perPage, perPage * page) + .map( + ({ + createdAt, + clientSecretTTL, + description, + clientSecretNumUses, + clientSecretPrefix, + clientSecretNumUsesLimit, + id + }) => { + let expiresAt; + if (clientSecretTTL > 0) { + expiresAt = new Date(new Date(createdAt).getTime() + clientSecretTTL * 1000); + } + + return ( + + + + + + + + ); + } + )} + +
SecretDescriptionNumber of UsesExpires +
{clientSecretPrefix}***{description || "-"} + {`${clientSecretNumUses}${clientSecretNumUsesLimit ? `/${clientSecretNumUsesLimit}` : ""}`} + + {expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"} + + + { + handlePopUpOpen("revokeClientSecret", { + clientSecretPrefix, + clientSecretId: id + }); + }} + size="xs" + colorSchema="danger" + variant="plain" + ariaLabel="Delete secret" + > + + + +
+ {!clientSecrets?.length && ( + + )} + {clientSecrets.length > 0 && ( + setPage(newPage)} + onChangePerPage={(newPerPage) => setPerPage(newPerPage)} + /> + )} +
+ handlePopUpToggle("revokeClientSecret", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => { + const deleteClientSecretData = popUp?.revokeClientSecret?.data as { + clientSecretId: string; + clientSecretPrefix: string; + }; + + return onDeleteClientSecretSubmit(deleteClientSecretData.clientSecretId); + }} + /> + +
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx new file mode 100644 index 000000000..bb67fcb5c --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx @@ -0,0 +1,166 @@ +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, + useDeleteIdentityAwsAuth, + useDeleteIdentityAzureAuth, + useDeleteIdentityGcpAuth, + useDeleteIdentityJwtAuth, + useDeleteIdentityKubernetesAuth, + useDeleteIdentityOidcAuth, + useDeleteIdentityTokenAuth, + useDeleteIdentityUniversalAuth +} from "@app/hooks/api"; +import { ViewIdentityJwtAuthContent } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityJwtAuthContent"; +import { ViewIdentityOidcAuthContent } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent"; + +import { ViewAuthMethodProps } from "./types"; +import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent"; +import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent"; +import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent"; +import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent"; +import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent"; +import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent"; + +type Props = { + identityId: string; + authMethod?: IdentityAuthMethod; + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + onEditAuthMethod: (authMethod: IdentityAuthMethod) => void; + onDeleteAuthMethod: () => void; +}; + +type TRevokeOptions = { + identityId: string; + organizationId: string; +}; + +export const Content = ({ + identityId, + authMethod, + onEditAuthMethod, + onDeleteAuthMethod +}: Pick) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["revokeAuthMethod"] as const); + + const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth(); + const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth(); + const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth(); + const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth(); + const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth(); + const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth(); + const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth(); + const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth(); + + let Component: (props: ViewAuthMethodProps) => JSX.Element; + let revokeMethod: (revokeOptions: TRevokeOptions) => Promise; + + 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.AWS_AUTH: + revokeMethod = revokeAwsAuth; + Component = ViewIdentityAwsAuthContent; + break; + case IdentityAuthMethod.AZURE_AUTH: + revokeMethod = revokeAzureAuth; + Component = ViewIdentityAzureAuthContent; + break; + case IdentityAuthMethod.OIDC_AUTH: + revokeMethod = revokeOidcAuth; + Component = ViewIdentityOidcAuthContent; + break; + case IdentityAuthMethod.JWT_AUTH: + revokeMethod = revokeJwtAuth; + Component = ViewIdentityJwtAuthContent; + break; + default: + throw new Error(`Unhandled Auth Method: ${authMethod}`); + } + + const handleDeleteAuthMethod = async () => { + try { + await revokeMethod({ + identityId, + organizationId: orgId + }); + + createNotification({ + text: "Successfully removed auth method", + type: "success" + }); + + handlePopUpToggle("revokeAuthMethod", false); + onDeleteAuthMethod(); + } catch { + createNotification({ + text: "Failed to remove auth method", + type: "error" + }); + } + }; + + return ( + <> + onEditAuthMethod(authMethod)} + /> + handlePopUpToggle("revokeAuthMethod", isOpen)} + deleteKey="confirm" + buttonText="Remove" + onDeleteApproved={handleDeleteAuthMethod} + /> + + ); +}; + +export const ViewIdentityAuthModal = ({ + isOpen, + onOpenChange, + authMethod, + identityId, + onEditAuthMethod +}: Omit) => { + if (!identityId || !authMethod) return null; + + return ( + + + onOpenChange(false)} + /> + + + ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAwsAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAwsAuthContent.tsx new file mode 100644 index 000000000..755dca904 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAwsAuthContent.tsx @@ -0,0 +1,62 @@ +import { faBan } from "@fortawesome/free-solid-svg-icons"; + +import { EmptyState, Spinner } from "@app/components/v2"; +import { useGetIdentityAwsAuth } from "@app/hooks/api"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { ViewAuthMethodProps } from "./types"; +import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper"; + +export const ViewIdentityAwsAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityAwsAuth(identityId); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.allowedPrincipalArns + ?.split(",") + .map((arn) => arn.trim()) + .join(", ")} + + + {data.allowedAccountIds + ?.split(",") + .map((id) => id.trim()) + .join(", ")} + + + {data.stsEndpoint} + + + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + + + ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAzureAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAzureAuthContent.tsx new file mode 100644 index 000000000..931b43132 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAzureAuthContent.tsx @@ -0,0 +1,59 @@ +import { faBan } from "@fortawesome/free-solid-svg-icons"; + +import { EmptyState, Spinner } from "@app/components/v2"; +import { useGetIdentityAzureAuth } from "@app/hooks/api"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { ViewAuthMethodProps } from "./types"; +import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper"; + +export const ViewIdentityAzureAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityAzureAuth(identityId); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.tenantId} + + + {data.resource} + + + {data.allowedServicePrincipalIds + ?.split(",") + .map((id) => id.trim()) + .join(", ")} + + + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + + + ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper.tsx new file mode 100644 index 000000000..7fd84ab21 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper.tsx @@ -0,0 +1,42 @@ +import { ReactNode } from "react"; +import { faEdit, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Button } from "@app/components/v2"; + +type Props = { + children: ReactNode; + onEdit: VoidFunction; + onDelete: VoidFunction; +}; + +export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props) => { + return ( +
+
+
+ Details +
+ + +
+
+
{children}
+
+
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityGcpAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityGcpAuthContent.tsx new file mode 100644 index 000000000..105430c63 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityGcpAuthContent.tsx @@ -0,0 +1,72 @@ +import { faBan } from "@fortawesome/free-solid-svg-icons"; + +import { EmptyState, Spinner } from "@app/components/v2"; +import { useGetIdentityGcpAuth } from "@app/hooks/api"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { ViewAuthMethodProps } from "./types"; +import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper"; + +export const ViewIdentityGcpAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityGcpAuth(identityId); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.type === "gce" ? "GCP ID Token Auth" : "GCP IAM Auth"} + + + {data.allowedServiceAccounts + ?.split(",") + .map((account) => account.trim()) + .join(", ")} + + {data.type === "gce" && ( + <> + + {data.allowedProjects + ?.split(",") + .map((project) => project.trim()) + .join(", ")} + + + {data.allowedZones + ?.split(",") + .map((zone) => zone.trim()) + .join(", ")} + + + )} + + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + + + ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityJwtAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityJwtAuthContent.tsx new file mode 100644 index 000000000..8c9129940 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityJwtAuthContent.tsx @@ -0,0 +1,132 @@ +import { faBan, faEye } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2"; +import { useGetIdentityJwtAuth } from "@app/hooks/api"; +import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums"; +import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { ViewAuthMethodProps } from "./types"; + +export const ViewIdentityJwtAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityJwtAuth(identityId); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.configurationType === IdentityJwtConfigurationType.JWKS ? "JWKS" : "Static"} + + {data.configurationType === IdentityJwtConfigurationType.JWKS ? ( + <> + + {data.jwksCaCert}

+ } + > +
+ + + Reveal + +
+
+
+ + {data.jwksUrl} + + + ) : ( + +
+ {data.publicKeys.length && + data.publicKeys.map((key, index) => ( + + {key} +

+ } + > +
+ + + Key {index + 1} + +
+
+ ))} +
+
+ )} + + {data.boundIssuer} + + + {data.boundSubject} + + + {data.boundAudiences + ?.split(",") + .map((name) => name.trim()) + .join(", ")} + + + {Object.keys(data.boundClaims).length && ( + + {JSON.stringify(data.boundClaims, null, 2)} + + } + > +
+ + + Reveal + +
+
+ )} +
+ + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + +
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityKubernetesAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityKubernetesAuthContent.tsx new file mode 100644 index 000000000..69a6a9566 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityKubernetesAuthContent.tsx @@ -0,0 +1,102 @@ +import { faBan, faEye } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2"; +import { useGetIdentityKubernetesAuth } from "@app/hooks/api"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { ViewAuthMethodProps } from "./types"; +import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper"; + +export const ViewIdentityKubernetesAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityKubernetesAuth(identityId); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.kubernetesHost} + + + {data.tokenReviewerJwt}

+ } + > +
+ + + Reveal + +
+
+
+ + {data.allowedNames + ?.split(",") + .map((name) => name.trim()) + .join(", ")} + + + {data.allowedNamespaces + ?.split(",") + .map((namespace) => namespace.trim()) + .join(", ")} + + + {data.allowedAudience} + + + {data.caCert}

} + > +
+ + + Reveal + +
+
+
+ + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + +
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx new file mode 100644 index 000000000..f0b1105f9 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx @@ -0,0 +1,97 @@ +import { faBan, faEye } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2"; +import { useGetIdentityOidcAuth } from "@app/hooks/api"; +import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { ViewAuthMethodProps } from "./types"; + +export const ViewIdentityOidcAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityOidcAuth(identityId); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.oidcDiscoveryUrl} + + + {data.boundIssuer} + + + {data.caCert}

} + > +
+ + + Reveal + +
+
+
+ + {data.boundSubject} + + + {data.boundAudiences + ?.split(",") + .map((name) => name.trim()) + .join(", ")} + + + {Object.keys(data.boundClaims).length && ( + + {JSON.stringify(data.boundClaims, null, 2)} + + } + > +
+ + + Reveal + +
+
+ )} +
+ + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + +
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityTokenAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityTokenAuthContent.tsx new file mode 100644 index 000000000..ecd677284 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityTokenAuthContent.tsx @@ -0,0 +1,51 @@ +import { faBan } from "@fortawesome/free-solid-svg-icons"; + +import { EmptyState, Spinner } from "@app/components/v2"; +import { useGetIdentityTokenAuth, useGetIdentityTokensTokenAuth } from "@app/hooks/api"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { IdentityTokenAuthTokensTable } from "./IdentityTokenAuthTokensTable"; +import { ViewAuthMethodProps } from "./types"; +import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper"; + +export const ViewIdentityTokenAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityTokenAuth(identityId); + const { data: tokens = [], isPending: clientSecretsPending } = + useGetIdentityTokensTokenAuth(identityId); + + if (isPending || clientSecretsPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + + + + ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx new file mode 100644 index 000000000..a3112bf4b --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx @@ -0,0 +1,89 @@ +import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2"; +import { useTimedReset } from "@app/hooks"; +import { + useGetIdentityUniversalAuth, + useGetIdentityUniversalAuthClientSecrets +} from "@app/hooks/api"; + +import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; +import { IdentityUniversalAuthClientSecretsTable } from "./IdentityUniversalAuthClientSecretsTable"; +import { ViewAuthMethodProps } from "./types"; +import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper"; + +export const ViewIdentityUniversalAuthContent = ({ + identityId, + onEdit, + onDelete +}: ViewAuthMethodProps) => { + const { data, isPending } = useGetIdentityUniversalAuth(identityId); + const { data: clientSecrets = [], isPending: clientSecretsPending } = + useGetIdentityUniversalAuthClientSecrets(identityId); + + const [copyTextClientId, isCopyingClientId, setCopyTextClientId] = useTimedReset({ + initialState: "Copy Client ID to clipboard" + }); + + if (isPending || clientSecretsPending) { + return ( +
+ +
+ ); + } + + if (!data) { + return ( + + ); + } + + return ( + + + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + {data.accessTokenNumUsesLimit} + + + {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} + + + {data.clientSecretTrustedIps.map((ip) => ip.ipAddress).join(", ")} + +
+
+ Client ID +
+
+ {data.clientId} + + { + navigator.clipboard.writeText(data.clientId); + setCopyTextClientId("Copied"); + }} + > + + + +
+
+ +
+ ); +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/index.ts b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/index.ts new file mode 100644 index 000000000..2da1344a8 --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/index.ts @@ -0,0 +1 @@ +export * from "./ViewIdentityAuthModal"; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/types/index.ts b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/types/index.ts new file mode 100644 index 000000000..0e2f882ec --- /dev/null +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/types/index.ts @@ -0,0 +1,5 @@ +export type ViewAuthMethodProps = { + identityId: string; + onEdit: () => void; + onDelete: () => void; +}; diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/index.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/index.tsx index 8fcbcfc21..c9ad2f414 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/index.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/index.tsx @@ -2,5 +2,4 @@ export { IdentityAuthenticationSection } from "./IdentityAuthenticationSection/I export { IdentityClientSecretModal } from "./IdentityClientSecretModal"; export { IdentityDetailsSection } from "./IdentityDetailsSection"; export { IdentityProjectsSection } from "./IdentityProjectsSection/IdentityProjectsSection"; -export { IdentityTokenListModal } from "./IdentityTokenListModal"; export { IdentityTokenModal } from "./IdentityTokenModal";