improvements: address feedback

This commit is contained in:
Scott Wilson
2025-01-24 12:55:56 -08:00
parent 94aed485a5
commit 99c9b644df
27 changed files with 2374 additions and 1994 deletions
@@ -923,7 +923,7 @@ export const useAddIdentityTokenAuth = () => {
}); });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) }); queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId) queryKey: identitiesKeys.getIdentityTokenAuth(identityId)
}); });
} }
}); });
@@ -959,7 +959,7 @@ export const useUpdateIdentityTokenAuth = () => {
}); });
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) }); queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId) queryKey: identitiesKeys.getIdentityTokenAuth(identityId)
}); });
} }
}); });
@@ -34,7 +34,7 @@ export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpTog
title={ title={
isSelectedAuthAlreadyConfigured isSelectedAuthAlreadyConfigured
? `Edit ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}` ? `Edit ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}`
: `Create new ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}` : `Add ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}`
} }
> >
<IdentityAuthMethodModalContent <IdentityAuthMethodModalContent
@@ -111,7 +111,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.UNIVERSAL_AUTH]: { [IdentityAuthMethod.UNIVERSAL_AUTH]: {
render: () => ( render: () => (
<IdentityUniversalAuthForm <IdentityUniversalAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -121,7 +121,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.OIDC_AUTH]: { [IdentityAuthMethod.OIDC_AUTH]: {
render: () => ( render: () => (
<IdentityOidcAuthForm <IdentityOidcAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -131,7 +131,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.TOKEN_AUTH]: { [IdentityAuthMethod.TOKEN_AUTH]: {
render: () => ( render: () => (
<IdentityTokenAuthForm <IdentityTokenAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -141,7 +141,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.AZURE_AUTH]: { [IdentityAuthMethod.AZURE_AUTH]: {
render: () => ( render: () => (
<IdentityAzureAuthForm <IdentityAzureAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -151,7 +151,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.GCP_AUTH]: { [IdentityAuthMethod.GCP_AUTH]: {
render: () => ( render: () => (
<IdentityGcpAuthForm <IdentityGcpAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -161,7 +161,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.KUBERNETES_AUTH]: { [IdentityAuthMethod.KUBERNETES_AUTH]: {
render: () => ( render: () => (
<IdentityKubernetesAuthForm <IdentityKubernetesAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -171,7 +171,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.AWS_AUTH]: { [IdentityAuthMethod.AWS_AUTH]: {
render: () => ( render: () => (
<IdentityAwsAuthForm <IdentityAwsAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -181,7 +181,7 @@ export const IdentityAuthMethodModalContent = ({
[IdentityAuthMethod.JWT_AUTH]: { [IdentityAuthMethod.JWT_AUTH]: {
render: () => ( render: () => (
<IdentityJwtAuthForm <IdentityJwtAuthForm
identityAuthMethodData={identityAuthMethodData} identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -6,17 +6,27 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2"; import {
Button,
FormControl,
IconButton,
Input,
Tab,
TabList,
TabPanel,
Tabs
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import {
useAddIdentityAwsAuth, useAddIdentityAwsAuth,
useGetIdentityAwsAuth, useGetIdentityAwsAuth,
useUpdateIdentityAwsAuth useUpdateIdentityAwsAuth
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z const schema = z
.object({ .object({
stsEndpoint: z.string(), stsEndpoint: z.string(),
@@ -52,18 +62,15 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityAwsAuthForm = ({ export const IdentityAwsAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@@ -71,11 +78,9 @@ export const IdentityAwsAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityAwsAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityAwsAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -143,7 +148,7 @@ export const IdentityAwsAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) return; if (!identityId) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
@@ -151,7 +156,7 @@ export const IdentityAwsAuthForm = ({
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
allowedAccountIds, allowedAccountIds,
identityId: identityAuthMethodData.identityId, identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -160,7 +165,7 @@ export const IdentityAwsAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
stsEndpoint: stsEndpoint || "", stsEndpoint: stsEndpoint || "",
allowedPrincipalArns: allowedPrincipalArns || "", allowedPrincipalArns: allowedPrincipalArns || "",
allowedAccountIds: allowedAccountIds || "", allowedAccountIds: allowedAccountIds || "",
@@ -188,178 +193,194 @@ export const IdentityAwsAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
defaultValue="2592000" ["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
name="allowedPrincipalArns" ? IdentityFormTab.Advanced
render={({ field, fieldState: { error } }) => ( : IdentityFormTab.Configuration
<FormControl );
label="Allowed Principal ARNs" })}
isError={Boolean(error)} >
errorText={error?.message} <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
> <TabList>
<Input <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
{...field} <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
placeholder="arn:aws:iam::123456789012:role/MyRoleName, arn:aws:iam::123456789012:user/MyUserName..." </TabList>
type="text" <TabPanel value={IdentityFormTab.Configuration}>
/>
</FormControl>
)}
/>
<Controller
control={control}
name="allowedAccountIds"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Account IDs"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="123456789012, ..." />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="https://sts.amazonaws.com/"
name="stsEndpoint"
render={({ field, fieldState: { error } }) => (
<FormControl label="STS Endpoint" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} placeholder="https://sts.amazonaws.com/" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`} defaultValue="2592000"
defaultValue="0.0.0.0/0" name="allowedPrincipalArns"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl label="Allowed Principal ARNs"
className="mb-0 flex-grow" isError={Boolean(error)}
label={index === 0 ? "Access Token Trusted IPs" : undefined} errorText={error?.message}
isError={Boolean(error)} >
errorText={error?.message} <Input
> {...field}
<Input placeholder="arn:aws:iam::123456789012:role/MyRoleName, arn:aws:iam::123456789012:user/MyUserName..."
value={field.value} type="text"
onChange={(e) => { />
if (subscription?.ipAllowlisting) { </FormControl>
field.onChange(e); )}
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/> />
<IconButton <Controller
onClick={() => { control={control}
if (subscription?.ipAllowlisting) { name="allowedAccountIds"
removeAccessTokenTrustedIp(index); render={({ field, fieldState: { error } }) => (
return; <FormControl
} label="Allowed Account IDs"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="123456789012, ..." />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="https://sts.amazonaws.com/"
name="stsEndpoint"
render={({ field, fieldState: { error } }) => (
<FormControl label="STS Endpoint" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} placeholder="https://sts.amazonaws.com/" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
}} }}
size="lg" placeholder="123.456.789.0"
colorSchema="danger" />
variant="plain" </FormControl>
ariaLabel="update" );
className="p-3" }}
> />
<FontAwesomeIcon icon={faXmark} /> <IconButton
</IconButton> onClick={() => {
</div> if (subscription?.ipAllowlisting) {
))} removeAccessTokenTrustedIp(index);
<div className="my-4 ml-1"> return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => { size="sm"
if (subscription?.ipAllowlisting) { type="submit"
appendAccessTokenTrustedIp({ isLoading={isSubmitting}
ipAddress: "0.0.0.0/0" isDisabled={isSubmitting}
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address {isUpdate ? "Update" : "Add"}
</Button> </Button>
</div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{!isUpdate ? "Create" : "Edit"}
</Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
variant="plain" variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)} onClick={() => handlePopUpToggle("identityAuthMethod", false)}
> >
Cancel Cancel
</Button> </Button>
</div>
</div> </div>
</form> </form>
); );
@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -6,17 +6,27 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2"; import {
Button,
FormControl,
IconButton,
Input,
Tab,
TabList,
TabPanel,
Tabs
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import {
useAddIdentityAzureAuth, useAddIdentityAzureAuth,
useGetIdentityAzureAuth, useGetIdentityAzureAuth,
useUpdateIdentityAzureAuth useUpdateIdentityAzureAuth
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z const schema = z
.object({ .object({
tenantId: z.string().min(1), tenantId: z.string().min(1),
@@ -47,18 +57,15 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityAzureAuthForm = ({ export const IdentityAzureAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@@ -66,11 +73,9 @@ export const IdentityAzureAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityAzureAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityAzureAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAzureAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityAzureAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityAzureAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityAzureAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -139,12 +144,12 @@ export const IdentityAzureAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) return; if (!identityId) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
tenantId, tenantId,
resource, resource,
allowedServicePrincipalIds, allowedServicePrincipalIds,
@@ -156,7 +161,7 @@ export const IdentityAzureAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
tenantId: tenantId || "", tenantId: tenantId || "",
resource: resource || "", resource: resource || "",
allowedServicePrincipalIds: allowedServicePrincipalIds || "", allowedServicePrincipalIds: allowedServicePrincipalIds || "",
@@ -184,178 +189,194 @@ export const IdentityAzureAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
defaultValue="2592000" ["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
name="tenantId" ? IdentityFormTab.Advanced
render={({ field, fieldState: { error } }) => ( : IdentityFormTab.Configuration
<FormControl );
label="Tenant ID" })}
isError={Boolean(error)} >
errorText={error?.message} <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
isRequired <TabList>
> <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Input {...field} placeholder="00000000-0000-0000-0000-000000000000" type="text" /> <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</FormControl> </TabList>
)} <TabPanel value={IdentityFormTab.Configuration}>
/>
<Controller
control={control}
name="resource"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Resource / Audience"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="https://management.azure.com/" />
</FormControl>
)}
/>
<Controller
control={control}
name="allowedServicePrincipalIds"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Service Principal IDs"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="00000000-0000-0000-0000-000000000000, ..." />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`} defaultValue="2592000"
defaultValue="0.0.0.0/0" name="tenantId"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl label="Tenant ID"
className="mb-0 flex-grow" isError={Boolean(error)}
label={index === 0 ? "Access Token Trusted IPs" : undefined} errorText={error?.message}
isError={Boolean(error)} isRequired
errorText={error?.message} >
> <Input {...field} placeholder="00000000-0000-0000-0000-000000000000" type="text" />
<Input </FormControl>
value={field.value} )}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/> />
<IconButton <Controller
onClick={() => { control={control}
if (subscription?.ipAllowlisting) { name="resource"
removeAccessTokenTrustedIp(index); render={({ field, fieldState: { error } }) => (
return; <FormControl
} label="Resource / Audience"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="https://management.azure.com/" />
</FormControl>
)}
/>
<Controller
control={control}
name="allowedServicePrincipalIds"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Service Principal IDs"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="00000000-0000-0000-0000-000000000000, ..." />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
}} }}
size="lg" placeholder="123.456.789.0"
colorSchema="danger" />
variant="plain" </FormControl>
ariaLabel="update" );
className="p-3" }}
> />
<FontAwesomeIcon icon={faXmark} /> <IconButton
</IconButton> onClick={() => {
</div> if (subscription?.ipAllowlisting) {
))} removeAccessTokenTrustedIp(index);
<div className="my-4 ml-1"> return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => { size="sm"
if (subscription?.ipAllowlisting) { type="submit"
appendAccessTokenTrustedIp({ isLoading={isSubmitting}
ipAddress: "0.0.0.0/0" isDisabled={isSubmitting}
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address {isUpdate ? "Update" : "Add"}
</Button> </Button>
</div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{!isUpdate ? "Create" : "Edit"}
</Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
variant="plain" variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)} onClick={() => handlePopUpToggle("identityAuthMethod", false)}
> >
Cancel Cancel
</Button> </Button>
</div>
</div> </div>
</form> </form>
); );
@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -6,17 +6,29 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2"; import {
Button,
FormControl,
IconButton,
Input,
Select,
SelectItem,
Tab,
TabList,
TabPanel,
Tabs
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import {
useAddIdentityGcpAuth, useAddIdentityGcpAuth,
useGetIdentityGcpAuth, useGetIdentityGcpAuth,
useUpdateIdentityGcpAuth useUpdateIdentityGcpAuth
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z const schema = z
.object({ .object({
type: z.enum(["iam", "gce"]), type: z.enum(["iam", "gce"]),
@@ -48,18 +60,15 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityGcpAuthForm = ({ export const IdentityGcpAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@@ -67,11 +76,9 @@ export const IdentityGcpAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityGcpAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityGcpAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityGcpAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityGcpAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityGcpAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityGcpAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -146,11 +153,11 @@ export const IdentityGcpAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) return; if (!identityId) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
identityId: identityAuthMethodData.identityId, identityId,
organizationId: orgId, organizationId: orgId,
type, type,
allowedServiceAccounts, allowedServiceAccounts,
@@ -163,7 +170,7 @@ export const IdentityGcpAuthForm = ({
}); });
} else { } else {
await addMutateAsync({ await addMutateAsync({
identityId: identityAuthMethodData.identityId, identityId,
organizationId: orgId, organizationId: orgId,
type, type,
allowedServiceAccounts: allowedServiceAccounts || "", allowedServiceAccounts: allowedServiceAccounts || "",
@@ -193,202 +200,222 @@ export const IdentityGcpAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
name="type" ["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( ? IdentityFormTab.Advanced
<FormControl label="Type" isError={Boolean(error)} errorText={error?.message}> : IdentityFormTab.Configuration
<Select );
defaultValue={field.value} })}
{...field} >
onValueChange={(e) => onChange(e)} <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
className="w-full" <TabList>
> <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<SelectItem value="gce" key="gce"> <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
GCP ID Token Auth (Recommended) </TabList>
</SelectItem> <TabPanel value={IdentityFormTab.Configuration}>
<SelectItem value="iam" key="iam">
GCP IAM Auth
</SelectItem>
</Select>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="allowedServiceAccounts"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Service Account Emails"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="[email protected], [email protected]"
type="text"
/>
</FormControl>
)}
/>
{watchedType === "gce" && (
<Controller
control={control}
name="allowedProjects"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Projects"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="my-gcp-project, ..." />
</FormControl>
)}
/>
)}
{watchedType === "gce" && (
<Controller
control={control}
name="allowedZones"
render={({ field, fieldState: { error } }) => (
<FormControl label="Allowed Zones" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} placeholder="us-west2-a, us-central1-a, ..." />
</FormControl>
)}
/>
)}
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`} name="type"
defaultValue="0.0.0.0/0" render={({ field: { onChange, ...field }, fieldState: { error } }) => (
render={({ field, fieldState: { error } }) => { <FormControl label="Type" isError={Boolean(error)} errorText={error?.message}>
return ( <Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
<SelectItem value="gce" key="gce">
GCP ID Token Auth (Recommended)
</SelectItem>
<SelectItem value="iam" key="iam">
GCP IAM Auth
</SelectItem>
</Select>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="allowedServiceAccounts"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Service Account Emails"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="[email protected], [email protected]"
type="text"
/>
</FormControl>
)}
/>
{watchedType === "gce" && (
<Controller
control={control}
name="allowedProjects"
render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
className="mb-0 flex-grow" label="Allowed Projects"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
<Input <Input {...field} placeholder="my-gcp-project, ..." />
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl> </FormControl>
); )}
}} />
)}
{watchedType === "gce" && (
<Controller
control={control}
name="allowedZones"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Zones"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="us-west2-a, us-central1-a, ..." />
</FormControl>
)}
/>
)}
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/> />
<IconButton <Controller
onClick={() => { control={control}
if (subscription?.ipAllowlisting) { defaultValue="2592000"
removeAccessTokenTrustedIp(index); name="accessTokenMaxTTL"
return; render={({ field, fieldState: { error } }) => (
} <FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
}} }}
size="lg" placeholder="123.456.789.0"
colorSchema="danger" />
variant="plain" </FormControl>
ariaLabel="update" );
className="p-3" }}
> />
<FontAwesomeIcon icon={faXmark} /> <IconButton
</IconButton> onClick={() => {
</div> if (subscription?.ipAllowlisting) {
))} removeAccessTokenTrustedIp(index);
<div className="my-4 ml-1"> return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => { size="sm"
if (subscription?.ipAllowlisting) { type="submit"
appendAccessTokenTrustedIp({ isLoading={isSubmitting}
ipAddress: "0.0.0.0/0" isDisabled={isSubmitting}
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address {isUpdate ? "Update" : "Add"}
</Button> </Button>
</div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{!isUpdate ? "Create" : "Edit"}
</Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
variant="plain" variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)} onClick={() => handlePopUpToggle("identityAuthMethod", false)}
> >
Cancel Cancel
</Button> </Button>
</div>
</div> </div>
</form> </form>
); );
@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons"; import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
@@ -14,17 +14,22 @@ import {
Input, Input,
Select, Select,
SelectItem, SelectItem,
Tab,
TabList,
TabPanel,
Tabs,
TextArea, TextArea,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { useAddIdentityJwtAuth, useUpdateIdentityJwtAuth } from "@app/hooks/api"; import { useAddIdentityJwtAuth, useUpdateIdentityJwtAuth } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums"; import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums";
import { useGetIdentityJwtAuth } from "@app/hooks/api/identities/queries"; import { useGetIdentityJwtAuth } from "@app/hooks/api/identities/queries";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const commonSchema = z.object({ const commonSchema = z.object({
accessTokenTrustedIps: z accessTokenTrustedIps: z
.array( .array(
@@ -88,18 +93,15 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityJwtAuthForm = ({ export const IdentityJwtAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@@ -107,11 +109,9 @@ export const IdentityJwtAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityJwtAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityJwtAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityJwtAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityJwtAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityJwtAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityJwtAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -218,13 +218,13 @@ export const IdentityJwtAuthForm = ({
boundSubject boundSubject
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) { if (!identityId) {
return; return;
} }
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
identityId: identityAuthMethodData.identityId, identityId,
organizationId: orgId, organizationId: orgId,
configurationType, configurationType,
jwksUrl, jwksUrl,
@@ -241,7 +241,7 @@ export const IdentityJwtAuthForm = ({
}); });
} else { } else {
await addMutateAsync({ await addMutateAsync({
identityId: identityAuthMethodData.identityId, identityId,
configurationType, configurationType,
jwksUrl, jwksUrl,
jwksCaCert, jwksCaCert,
@@ -275,56 +275,230 @@ export const IdentityJwtAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
name="configurationType" [
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( "accessTokenTrustedIps",
<FormControl "boundIssuer",
label="Configuration Type" "boundAudiences",
isError={Boolean(error)} "boundSubject",
errorText={error?.message} "boundClaims"
> ].includes(Object.keys(fields)[0])
<Select ? IdentityFormTab.Advanced
defaultValue={field.value} : IdentityFormTab.Configuration
{...field} );
onValueChange={(e) => { })}
if (e === IdentityJwtConfigurationType.JWKS) { >
setValue("publicKeys", []); <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
} else { <TabList>
setValue("publicKeys", [ <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
{ <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
value: "" </TabList>
} <TabPanel value={IdentityFormTab.Configuration}>
]);
setValue("jwksUrl", "");
setValue("jwksCaCert", "");
}
onChange(e);
}}
className="w-full"
>
<SelectItem value={IdentityJwtConfigurationType.JWKS} key="jwks">
JWKS
</SelectItem>
<SelectItem value={IdentityJwtConfigurationType.STATIC} key="static">
Static
</SelectItem>
</Select>
</FormControl>
)}
/>
{selectedConfigurationType === IdentityJwtConfigurationType.JWKS && (
<>
<Controller <Controller
control={control} control={control}
name="jwksUrl" name="configurationType"
render={({ field, fieldState: { error } }) => ( render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl <FormControl
isRequired label="Configuration Type"
label="JWKS URL"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => {
if (e === IdentityJwtConfigurationType.JWKS) {
setValue("publicKeys", []);
} else {
setValue("publicKeys", [
{
value: ""
}
]);
setValue("jwksUrl", "");
setValue("jwksCaCert", "");
}
onChange(e);
}}
className="w-full"
>
<SelectItem value={IdentityJwtConfigurationType.JWKS} key="jwks">
JWKS
</SelectItem>
<SelectItem value={IdentityJwtConfigurationType.STATIC} key="static">
Static
</SelectItem>
</Select>
</FormControl>
)}
/>
{selectedConfigurationType === IdentityJwtConfigurationType.JWKS && (
<>
<Controller
control={control}
name="jwksUrl"
render={({ field, fieldState: { error } }) => (
<FormControl
isRequired
label="JWKS URL"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="jwksCaCert"
render={({ field, fieldState: { error } }) => (
<FormControl
label="JWKS CA Certificate"
errorText={error?.message}
isError={Boolean(error)}
>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>
</>
)}
{selectedConfigurationType === IdentityJwtConfigurationType.STATIC && (
<>
{publicKeyFields.map(({ id }, index) => (
<div key={id} className="flex gap-2">
<Controller
control={control}
name={`publicKeys.${index}.value`}
render={({ field, fieldState: { error } }) => (
<FormControl
className="flex-grow"
label={`Public Key ${index + 1}`}
errorText={error?.message}
isError={Boolean(error)}
icon={
<Tooltip
className="text-center"
content={<span>This field only accepts PEM-formatted public keys</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
>
<TextArea {...field} placeholder="-----BEGIN PUBLIC KEY----- ..." />
</FormControl>
)}
/>
<IconButton
onClick={() => {
if (publicKeyFields.length === 1) {
createNotification({
type: "error",
text: "A public key is required for static configurations"
});
return;
}
removePublicKeyFields(index);
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() =>
appendPublicKeyFields({
value: ""
})
}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add Public Key
</Button>
</div>
</>
)}
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
<Controller
control={control}
name="boundIssuer"
render={({ field, fieldState: { error } }) => (
<FormControl label="Issuer" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="boundSubject"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Subject"
isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
> >
<Input {...field} type="text" /> <Input {...field} type="text" />
</FormControl> </FormControl>
@@ -332,58 +506,79 @@ export const IdentityJwtAuthForm = ({
/> />
<Controller <Controller
control={control} control={control}
name="jwksCaCert" name="boundAudiences"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="JWKS CA Certificate" label="Audiences"
errorText={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
> >
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." /> <Input {...field} type="text" placeholder="service1, service2" />
</FormControl> </FormControl>
)} )}
/> />
</> {boundClaimsFields.map(({ id }, index) => (
)} <div className="mb-3 flex items-end space-x-2" key={id}>
{selectedConfigurationType === IdentityJwtConfigurationType.STATIC && (
<>
{publicKeyFields.map(({ id }, index) => (
<div key={id} className="flex gap-2">
<Controller <Controller
control={control} control={control}
name={`publicKeys.${index}.value`} name={`boundClaims.${index}.key`}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => {
<FormControl return (
className="flex-grow" <FormControl
label={`Public Key ${index + 1}`} className="mb-0 flex-grow"
errorText={error?.message} label={index === 0 ? "Claims" : undefined}
isError={Boolean(error)} icon={
icon={ index === 0 ? (
<Tooltip <Tooltip
className="text-center" className="text-center"
content={<span>This field only accepts PEM-formatted public keys</span>} content={<span>This field supports glob patterns</span>}
> >
<FontAwesomeIcon icon={faQuestionCircle} size="sm" /> <FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip> </Tooltip>
} ) : undefined
> }
<TextArea {...field} placeholder="-----BEGIN PUBLIC KEY----- ..." /> isError={Boolean(error)}
</FormControl> errorText={error?.message}
)} >
/> <Input
<IconButton value={field.value}
onClick={() => { onChange={(e) => field.onChange(e)}
if (publicKeyFields.length === 1) { placeholder="property"
createNotification({ />
type: "error", </FormControl>
text: "A public key is required for static configurations" );
});
return;
}
removePublicKeyFields(index);
}} }}
/>
<Controller
control={control}
name={`boundClaims.${index}.value`}
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="value1, value2"
/>
</FormControl>
);
}}
/>
<IconButton
onClick={() => removeBoundClaimField(index)}
size="lg" size="lg"
colorSchema="danger" colorSchema="danger"
variant="plain" variant="plain"
@@ -398,280 +593,106 @@ export const IdentityJwtAuthForm = ({
<Button <Button
variant="outline_bg" variant="outline_bg"
onClick={() => onClick={() =>
appendPublicKeyFields({ appendBoundClaimField({
key: "",
value: "" value: ""
}) })
} }
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs" size="xs"
> >
Add Public Key Add Claims
</Button> </Button>
</div> </div>
</> {accessTokenTrustedIpsFields.map(({ id }, index) => (
)} <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
<Controller handlePopUpOpen("upgradePlan");
control={control} }}
name="boundIssuer" placeholder="123.456.789.0"
render={({ field, fieldState: { error } }) => ( />
<FormControl label="Issuer" isError={Boolean(error)} errorText={error?.message}> </FormControl>
<Input {...field} type="text" /> );
</FormControl> }}
)} />
/> <IconButton
<Controller onClick={() => {
control={control} if (subscription?.ipAllowlisting) {
name="boundSubject" removeAccessTokenTrustedIp(index);
render={({ field, fieldState: { error } }) => ( return;
<FormControl
label="Subject"
isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
>
<Input {...field} type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="boundAudiences"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Audiences"
isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
>
<Input {...field} type="text" placeholder="service1, service2" />
</FormControl>
)}
/>
{boundClaimsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`boundClaims.${index}.key`}
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Claims" : undefined}
icon={
index === 0 ? (
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
) : undefined
} }
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="property"
/>
</FormControl>
);
}}
/>
<Controller
control={control}
name={`boundClaims.${index}.value`}
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="value1, value2"
/>
</FormControl>
);
}}
/>
<IconButton handlePopUpOpen("upgradePlan");
onClick={() => removeBoundClaimField(index)} }}
size="lg" size="lg"
colorSchema="danger" colorSchema="danger"
variant="plain" variant="plain"
ariaLabel="update" ariaLabel="update"
className="p-3" className="p-3"
> >
<FontAwesomeIcon icon={faXmark} /> <FontAwesomeIcon icon={faXmark} />
</IconButton> </IconButton>
</div> </div>
))} ))}
<div className="my-4 ml-1"> <div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => size="sm"
appendBoundClaimField({ type="submit"
key: "", isLoading={isSubmitting}
value: "" isDisabled={isSubmitting}
})
}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add Claims {isUpdate ? "Update" : "Create"}
</Button> </Button>
</div>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/>
<IconButton
onClick={() => {
if (subscription?.ipAllowlisting) {
removeAccessTokenTrustedIp(index);
return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button <Button
variant="outline_bg" colorSchema="secondary"
onClick={() => { variant="plain"
if (subscription?.ipAllowlisting) { onClick={() => handlePopUpToggle("identityAuthMethod", false)}
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address Cancel
</Button> </Button>
</div> </div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{isUpdate ? "Update" : "Create"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
>
Cancel
</Button>
</div>
</div>
</form> </form>
); );
}; };
@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -6,17 +6,28 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input, TextArea } from "@app/components/v2"; import {
Button,
FormControl,
IconButton,
Input,
Tab,
TabList,
TabPanel,
Tabs,
TextArea
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import {
useAddIdentityKubernetesAuth, useAddIdentityKubernetesAuth,
useGetIdentityKubernetesAuth, useGetIdentityKubernetesAuth,
useUpdateIdentityKubernetesAuth useUpdateIdentityKubernetesAuth
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z const schema = z
.object({ .object({
kubernetesHost: z.string().min(1), kubernetesHost: z.string().min(1),
@@ -50,18 +61,15 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityKubernetesAuthForm = ({ export const IdentityKubernetesAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@@ -69,11 +77,9 @@ export const IdentityKubernetesAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityKubernetesAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityKubernetesAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityKubernetesAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityKubernetesAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityKubernetesAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityKubernetesAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -154,7 +160,7 @@ export const IdentityKubernetesAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) return; if (!identityId) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
@@ -165,7 +171,7 @@ export const IdentityKubernetesAuthForm = ({
allowedNamespaces, allowedNamespaces,
allowedAudience, allowedAudience,
caCert, caCert,
identityId: identityAuthMethodData.identityId, identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -174,7 +180,7 @@ export const IdentityKubernetesAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
kubernetesHost: kubernetesHost || "", kubernetesHost: kubernetesHost || "",
tokenReviewerJwt, tokenReviewerJwt,
allowedNames: allowedNames || "", allowedNames: allowedNames || "",
@@ -205,230 +211,252 @@ export const IdentityKubernetesAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
defaultValue="2592000" [
name="kubernetesHost" "kubernetesHost",
render={({ field, fieldState: { error } }) => ( "tokenReviewerJwt",
<FormControl "accessTokenTTL",
label="Kubernetes Host / Base Kubernetes API URL " "accessTokenMaxTTL",
isError={Boolean(error)} "accessTokenNumUsesLimit"
errorText={error?.message} ].includes(Object.keys(fields)[0])
tooltipText="The host string, host:port pair, or URL to the base of the Kubernetes API server. This can usually be obtained by running 'kubectl cluster-info'" ? IdentityFormTab.Configuration
isRequired : IdentityFormTab.Advanced
> );
<Input {...field} placeholder="https://my-example-k8s-api-host.com" type="text" /> })}
</FormControl> >
)} <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
/> <TabList>
<Controller <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
control={control} <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
name="tokenReviewerJwt" </TabList>
render={({ field, fieldState: { error } }) => ( <TabPanel value={IdentityFormTab.Configuration}>
<FormControl
label="Token Reviewer JWT"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="A long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods."
isRequired
>
<Input {...field} placeholder="" type="password" />
</FormControl>
)}
/>
<Controller
control={control}
name="allowedNames"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Service Account Names"
isError={Boolean(error)}
tooltipText="An optional comma-separated list of trusted service account names that are allowed to authenticate with Infisical. Leave empty to allow any service account."
errorText={error?.message}
>
<Input {...field} placeholder="service-account-1-name, service-account-1-name" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="allowedNamespaces"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Namespaces"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="A comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical."
>
<Input {...field} placeholder="namespaceA, namespaceB" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="allowedAudience"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Audience"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="An optional audience claim that the service account JWT token must have to authenticate with Infisical. Leave empty to allow any audience claim."
>
<Input {...field} placeholder="" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="caCert"
render={({ field, fieldState: { error } }) => (
<FormControl
label="CA Certificate"
errorText={error?.message}
isError={Boolean(error)}
tooltipText="An optional PEM-encoded CA cert for the Kubernetes API server. This is used by the TLS client for secure communication with the Kubernetes API server."
>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
tooltipText="The lifetime for an acccess token in seconds. This value will be referenced at renewal time."
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The maximum lifetime for an access token in seconds. This value will be referenced at renewal time."
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`} defaultValue="2592000"
defaultValue="0.0.0.0/0" name="kubernetesHost"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl label="Kubernetes Host / Base Kubernetes API URL "
className="mb-0 flex-grow" isError={Boolean(error)}
label={index === 0 ? "Access Token Trusted IPs" : undefined} errorText={error?.message}
isError={Boolean(error)} tooltipText="The host string, host:port pair, or URL to the base of the Kubernetes API server. This can usually be obtained by running 'kubectl cluster-info'"
errorText={error?.message} isRequired
tooltipText="The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0, allowing usage from any network address." >
> <Input {...field} placeholder="https://my-example-k8s-api-host.com" type="text" />
<Input </FormControl>
value={field.value} )}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/> />
<IconButton <Controller
onClick={() => { control={control}
if (subscription?.ipAllowlisting) { name="tokenReviewerJwt"
removeAccessTokenTrustedIp(index); render={({ field, fieldState: { error } }) => (
return; <FormControl
} label="Token Reviewer JWT"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="A long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods."
isRequired
>
<Input {...field} placeholder="" type="password" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
tooltipText="The lifetime for an acccess token in seconds. This value will be referenced at renewal time."
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The maximum lifetime for an access token in seconds. This value will be referenced at renewal time."
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
<Controller
control={control}
name="allowedNames"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Service Account Names"
isError={Boolean(error)}
tooltipText="An optional comma-separated list of trusted service account names that are allowed to authenticate with Infisical. Leave empty to allow any service account."
errorText={error?.message}
>
<Input {...field} placeholder="service-account-1-name, service-account-1-name" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="allowedNamespaces"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Namespaces"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="A comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical."
>
<Input {...field} placeholder="namespaceA, namespaceB" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="allowedAudience"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Audience"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="An optional audience claim that the service account JWT token must have to authenticate with Infisical. Leave empty to allow any audience claim."
>
<Input {...field} placeholder="" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="caCert"
render={({ field, fieldState: { error } }) => (
<FormControl
label="CA Certificate"
errorText={error?.message}
isError={Boolean(error)}
tooltipText="An optional PEM-encoded CA cert for the Kubernetes API server. This is used by the TLS client for secure communication with the Kubernetes API server."
>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0, allowing usage from any network address."
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
}} }}
size="lg" placeholder="123.456.789.0"
colorSchema="danger" />
variant="plain" </FormControl>
ariaLabel="update" );
className="p-3" }}
> />
<FontAwesomeIcon icon={faXmark} /> <IconButton
</IconButton> onClick={() => {
</div> if (subscription?.ipAllowlisting) {
))} removeAccessTokenTrustedIp(index);
<div className="my-4 ml-1"> return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => { size="sm"
if (subscription?.ipAllowlisting) { type="submit"
appendAccessTokenTrustedIp({ isLoading={isSubmitting}
ipAddress: "0.0.0.0/0" isDisabled={isSubmitting}
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address {isUpdate ? "Update" : "Add"}
</Button> </Button>
</div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{isUpdate ? "Update" : "Create"}
</Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
variant="plain" variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)} onClick={() => handlePopUpToggle("identityAuthMethod", false)}
> >
Cancel Cancel
</Button> </Button>
</div>
</div> </div>
</form> </form>
); );
@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons"; import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
@@ -7,14 +7,26 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input, TextArea, Tooltip } from "@app/components/v2"; import {
Button,
FormControl,
IconButton,
Input,
Tab,
TabList,
TabPanel,
Tabs,
TextArea,
Tooltip
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { useAddIdentityOidcAuth, useUpdateIdentityOidcAuth } from "@app/hooks/api"; import { useAddIdentityOidcAuth, useUpdateIdentityOidcAuth } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { useGetIdentityOidcAuth } from "@app/hooks/api/identities/queries"; import { useGetIdentityOidcAuth } from "@app/hooks/api/identities/queries";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z.object({ const schema = z.object({
accessTokenTrustedIps: z accessTokenTrustedIps: z
.array( .array(
@@ -51,18 +63,15 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityOidcAuthForm = ({ export const IdentityOidcAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@@ -70,11 +79,9 @@ export const IdentityOidcAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityOidcAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityOidcAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityOidcAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityOidcAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityOidcAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityOidcAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -160,13 +167,13 @@ export const IdentityOidcAuthForm = ({
boundSubject boundSubject
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) { if (!identityId) {
return; return;
} }
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
identityId: identityAuthMethodData.identityId, identityId,
organizationId: orgId, organizationId: orgId,
oidcDiscoveryUrl, oidcDiscoveryUrl,
caCert, caCert,
@@ -181,7 +188,7 @@ export const IdentityOidcAuthForm = ({
}); });
} else { } else {
await addMutateAsync({ await addMutateAsync({
identityId: identityAuthMethodData.identityId, identityId,
oidcDiscoveryUrl, oidcDiscoveryUrl,
caCert, caCert,
boundIssuer, boundIssuer,
@@ -213,303 +220,329 @@ export const IdentityOidcAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
name="oidcDiscoveryUrl" [
render={({ field, fieldState: { error } }) => ( "accessTokenTrustedIps",
<FormControl "caCert",
isRequired "boundAudiences",
label="OIDC Discovery URL" "boundSubject",
isError={Boolean(error)} "boundClaims"
errorText={error?.message} ].includes(Object.keys(fields)[0])
> ? IdentityFormTab.Advanced
<Input : IdentityFormTab.Configuration
{...field} );
placeholder="https://token.actions.githubusercontent.com" })}
type="text" >
/> <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
</FormControl> <TabList>
)} <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
/> <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
<Controller </TabList>
control={control} <TabPanel value={IdentityFormTab.Configuration}>
name="boundIssuer"
render={({ field, fieldState: { error } }) => (
<FormControl
isRequired
label="Issuer"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
type="text"
placeholder="https://token.actions.githubusercontent.com"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="caCert"
render={({ field, fieldState: { error } }) => (
<FormControl label="CA Certificate" errorText={error?.message} isError={Boolean(error)}>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>
<Controller
control={control}
name="boundSubject"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Subject"
isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
>
<Input {...field} type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="boundAudiences"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Audiences"
isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
>
<Input {...field} type="text" placeholder="service1, service2" />
</FormControl>
)}
/>
{boundClaimsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`boundClaims.${index}.key`} name="oidcDiscoveryUrl"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl isRequired
className="mb-0 flex-grow" label="OIDC Discovery URL"
label={index === 0 ? "Claims" : undefined} isError={Boolean(error)}
icon={ errorText={error?.message}
index === 0 ? ( >
<Tooltip <Input
className="text-center" {...field}
content={<span>This field supports glob patterns</span>} placeholder="https://token.actions.githubusercontent.com"
> type="text"
<FontAwesomeIcon icon={faQuestionCircle} size="sm" /> />
</Tooltip> </FormControl>
) : undefined )}
}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="property"
/>
</FormControl>
);
}}
/> />
<Controller <Controller
control={control} control={control}
name={`boundClaims.${index}.value`} name="boundIssuer"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl isRequired
className="mb-0 flex-grow" label="Issuer"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
<Input <Input
value={field.value} {...field}
onChange={(e) => field.onChange(e)} type="text"
placeholder="value1, value2" placeholder="https://token.actions.githubusercontent.com"
/> />
</FormControl> </FormControl>
); )}
}}
/> />
<IconButton
onClick={() => removeBoundClaimField(index)}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() =>
appendBoundClaimField({
key: "",
value: ""
})
}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add Claims
</Button>
</div>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`} defaultValue="2592000"
defaultValue="0.0.0.0/0" name="accessTokenTTL"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl label="Access Token TTL (seconds)"
className="mb-0 flex-grow" isError={Boolean(error)}
label={index === 0 ? "Access Token Trusted IPs" : undefined} errorText={error?.message}
isError={Boolean(error)} >
errorText={error?.message} <Input {...field} placeholder="2592000" type="number" min="1" step="1" />
> </FormControl>
<Input )}
value={field.value} />
onChange={(e) => { <Controller
if (subscription?.ipAllowlisting) { control={control}
field.onChange(e); defaultValue="2592000"
return; name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
<Controller
control={control}
name="caCert"
render={({ field, fieldState: { error } }) => (
<FormControl
label="CA Certificate"
errorText={error?.message}
isError={Boolean(error)}
>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>
<Controller
control={control}
name="boundSubject"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Subject"
isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
>
<Input {...field} type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="boundAudiences"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Audiences"
isError={Boolean(error)}
errorText={error?.message}
icon={
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
}
>
<Input {...field} type="text" placeholder="service1, service2" />
</FormControl>
)}
/>
{boundClaimsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`boundClaims.${index}.key`}
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Claims" : undefined}
icon={
index === 0 ? (
<Tooltip
className="text-center"
content={<span>This field supports glob patterns</span>}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
) : undefined
} }
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="property"
/>
</FormControl>
);
}}
/>
<Controller
control={control}
name={`boundClaims.${index}.value`}
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="value1, value2"
/>
</FormControl>
);
}}
/>
handlePopUpOpen("upgradePlan"); <IconButton
}} onClick={() => removeBoundClaimField(index)}
placeholder="123.456.789.0" size="lg"
/> colorSchema="danger"
</FormControl> variant="plain"
); ariaLabel="update"
}} className="p-3"
/> >
<IconButton <FontAwesomeIcon icon={faXmark} />
onClick={() => { </IconButton>
if (subscription?.ipAllowlisting) { </div>
removeAccessTokenTrustedIp(index); ))}
return; <div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() =>
appendBoundClaimField({
key: "",
value: ""
})
} }
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add Claims
</Button>
</div>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
}} }}
size="lg" placeholder="123.456.789.0"
colorSchema="danger" />
variant="plain" </FormControl>
ariaLabel="update" );
className="p-3" }}
> />
<FontAwesomeIcon icon={faXmark} /> <IconButton
</IconButton> onClick={() => {
</div> if (subscription?.ipAllowlisting) {
))} removeAccessTokenTrustedIp(index);
<div className="my-4 ml-1"> return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => { size="sm"
if (subscription?.ipAllowlisting) { type="submit"
appendAccessTokenTrustedIp({ isLoading={isSubmitting}
ipAddress: "0.0.0.0/0" isDisabled={isSubmitting}
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address {isUpdate ? "Update" : "Add"}
</Button> </Button>
</div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{isUpdate ? "Update" : "Create"}
</Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
variant="plain" variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)} onClick={() => handlePopUpToggle("identityAuthMethod", false)}
> >
Cancel Cancel
</Button> </Button>
</div>
</div> </div>
</form> </form>
); );
@@ -1,3 +1,4 @@
import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -5,16 +6,27 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2"; import {
Button,
FormControl,
IconButton,
Input,
Tab,
TabList,
TabPanel,
Tabs
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import {
useAddIdentityTokenAuth, useAddIdentityTokenAuth,
useGetIdentityTokenAuth, useGetIdentityTokenAuth,
useUpdateIdentityTokenAuth useUpdateIdentityTokenAuth
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z const schema = z
.object({ .object({
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, { accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
@@ -42,18 +54,15 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData?: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityTokenAuthForm = ({ export const IdentityTokenAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@@ -61,12 +70,9 @@ export const IdentityTokenAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityTokenAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityTokenAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityTokenAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityTokenAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityTokenAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityTokenAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -91,6 +97,30 @@ export const IdentityTokenAuthForm = ({
remove: removeAccessTokenTrustedIp remove: removeAccessTokenTrustedIp
} = useFieldArray({ control, name: "accessTokenTrustedIps" }); } = useFieldArray({ control, name: "accessTokenTrustedIps" });
useEffect(() => {
if (data) {
reset({
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
accessTokenTrustedIps: data.accessTokenTrustedIps.map(
({ ipAddress, prefix }: IdentityTrustedIp) => {
return {
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
};
}
)
});
} else {
reset({
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
});
}
}, [data]);
const onFormSubmit = async ({ const onFormSubmit = async ({
accessTokenTTL, accessTokenTTL,
accessTokenMaxTTL, accessTokenMaxTTL,
@@ -98,12 +128,12 @@ export const IdentityTokenAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) return; if (!identityId) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -112,7 +142,7 @@ export const IdentityTokenAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -137,137 +167,153 @@ export const IdentityTokenAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
defaultValue="2592000" ["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
name="accessTokenTTL" ? IdentityFormTab.Advanced
render={({ field, fieldState: { error } }) => ( : IdentityFormTab.Configuration
<FormControl );
label="Access Token TTL (seconds)" })}
isError={Boolean(error)} >
errorText={error?.message} <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
> <TabList>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" /> <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
</FormControl> <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
)} </TabList>
/> <TabPanel value={IdentityFormTab.Configuration}>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`} defaultValue="2592000"
defaultValue="0.0.0.0/0" name="accessTokenTTL"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl label="Access Token TTL (seconds)"
className="mb-0 flex-grow" isError={Boolean(error)}
label={index === 0 ? "Access Token Trusted IPs" : undefined} errorText={error?.message}
isError={Boolean(error)} >
errorText={error?.message} <Input {...field} placeholder="2592000" type="number" min="1" step="1" />
> </FormControl>
<Input )}
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/> />
<IconButton <Controller
onClick={() => { control={control}
if (subscription?.ipAllowlisting) { defaultValue="2592000"
removeAccessTokenTrustedIp(index); name="accessTokenMaxTTL"
return; render={({ field, fieldState: { error } }) => (
} <FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
}} }}
size="lg" placeholder="123.456.789.0"
colorSchema="danger" />
variant="plain" </FormControl>
ariaLabel="update" );
className="p-3" }}
> />
<FontAwesomeIcon icon={faXmark} /> <IconButton
</IconButton> onClick={() => {
</div> if (subscription?.ipAllowlisting) {
))} removeAccessTokenTrustedIp(index);
<div className="my-4 ml-1"> return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => { size="sm"
if (subscription?.ipAllowlisting) { type="submit"
appendAccessTokenTrustedIp({ isLoading={isSubmitting}
ipAddress: "0.0.0.0/0" isDisabled={isSubmitting}
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address {isUpdate ? "Update" : "Add"}
</Button> </Button>
</div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{isUpdate ? "Update" : "Create"}
</Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
variant="plain" variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)} onClick={() => handlePopUpToggle("identityAuthMethod", false)}
> >
Cancel Cancel
</Button> </Button>
</div>
</div> </div>
</form> </form>
); );
@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -6,17 +6,27 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2"; import {
Button,
FormControl,
IconButton,
Input,
Tab,
TabList,
TabPanel,
Tabs
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import {
useAddIdentityUniversalAuth, useAddIdentityUniversalAuth,
useGetIdentityUniversalAuth, useGetIdentityUniversalAuth,
useUpdateIdentityUniversalAuth useUpdateIdentityUniversalAuth
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z const schema = z
.object({ .object({
accessTokenTTL: z accessTokenTTL: z
@@ -55,30 +65,24 @@ type Props = {
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityAuthMethodData?: { identityId?: string;
identityId: string; isUpdate?: boolean;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityUniversalAuthForm = ({ export const IdentityUniversalAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityId,
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityUniversalAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityUniversalAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( const { data } = useGetIdentityUniversalAuth(identityId ?? "", {
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityUniversalAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@@ -149,13 +153,13 @@ export const IdentityUniversalAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityAuthMethodData) return; if (!identityId) return;
if (data) { if (data) {
// update universal auth configuration // update universal auth configuration
await updateMutateAsync({ await updateMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
clientSecretTrustedIps, clientSecretTrustedIps,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
@@ -167,7 +171,7 @@ export const IdentityUniversalAuthForm = ({
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId: identityAuthMethodData.identityId, identityId,
clientSecretTrustedIps, clientSecretTrustedIps,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
@@ -195,204 +199,220 @@ export const IdentityUniversalAuthForm = ({
}; };
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form
<Controller onSubmit={handleSubmit(onFormSubmit, (fields) => {
control={control} setTabValue(
defaultValue="2592000" ["accessTokenTrustedIps", "clientSecretTrustedIps"].includes(Object.keys(fields)[0])
name="accessTokenTTL" ? IdentityFormTab.Advanced
render={({ field, fieldState: { error } }) => ( : IdentityFormTab.Configuration
<FormControl );
label="Access Token TTL (seconds)" })}
isError={Boolean(error)} >
errorText={error?.message} <Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
> <TabList>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" /> <Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
</FormControl> <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
)} </TabList>
/> <TabPanel value={IdentityFormTab.Configuration}>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{clientSecretTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`clientSecretTrustedIps.${index}.ipAddress`} defaultValue="2592000"
defaultValue="0.0.0.0/0" name="accessTokenTTL"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl label="Access Token TTL (seconds)"
className="mb-0 flex-grow" isError={Boolean(error)}
label={index === 0 ? "Client Secret Trusted IPs" : undefined} errorText={error?.message}
isError={Boolean(error)} >
errorText={error?.message} <Input {...field} placeholder="2592000" type="number" min="1" step="1" />
> </FormControl>
<Input )}
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/> />
<IconButton
onClick={() => {
if (subscription?.ipAllowlisting) {
removeClientSecretTrustedIp(index);
return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendClientSecretTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
control={control} control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`} defaultValue="2592000"
defaultValue="0.0.0.0/0" name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => { render={({ field, fieldState: { error } }) => (
return ( <FormControl
<FormControl label="Access Token Max TTL (seconds)"
className="mb-0 flex-grow" isError={Boolean(error)}
label={index === 0 ? "Access Token Trusted IPs" : undefined} errorText={error?.message}
isError={Boolean(error)} >
errorText={error?.message} <Input {...field} placeholder="2592000" type="number" min="0" step="1" />
> </FormControl>
<Input )}
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/> />
<IconButton <Controller
onClick={() => { control={control}
if (subscription?.ipAllowlisting) { defaultValue="0"
removeAccessTokenTrustedIp(index); name="accessTokenNumUsesLimit"
return; render={({ field, fieldState: { error } }) => (
} <FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{clientSecretTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`clientSecretTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Client Secret Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
}} }}
size="lg" placeholder="123.456.789.0"
colorSchema="danger" />
variant="plain" </FormControl>
ariaLabel="update" );
className="p-3" }}
> />
<FontAwesomeIcon icon={faXmark} /> <IconButton
</IconButton> onClick={() => {
</div> if (subscription?.ipAllowlisting) {
))} removeClientSecretTrustedIp(index);
<div className="my-4 ml-1"> return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendClientSecretTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/>
<IconButton
onClick={() => {
if (subscription?.ipAllowlisting) {
removeAccessTokenTrustedIp(index);
return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button <Button
variant="outline_bg" className="mr-4"
onClick={() => { size="sm"
if (subscription?.ipAllowlisting) { type="submit"
appendAccessTokenTrustedIp({ isLoading={isSubmitting}
ipAddress: "0.0.0.0/0" isDisabled={isSubmitting}
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
> >
Add IP Address {isUpdate ? "Update" : "Add"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
>
Cancel
</Button> </Button>
</div>
<div className="flex justify-between">
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{isUpdate ? "Edit" : "Create"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
>
Cancel
</Button>
</div>
</div> </div>
</form> </form>
); );
@@ -0,0 +1,4 @@
export enum IdentityFormTab {
Advanced = "advanced",
Configuration = "configuration"
}
@@ -201,14 +201,6 @@ const Page = () => {
onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)}
authMethod={popUp.viewAuthMethod.data} authMethod={popUp.viewAuthMethod.data}
identityId={identityId} identityId={identityId}
onEditAuthMethod={(authMethod) =>
handlePopUpOpen("identityAuthMethod", {
identityId,
name: data?.identity.name,
allAuthMethods: data?.identity.authMethods,
authMethod
})
}
/> />
</div> </div>
); );
@@ -9,11 +9,11 @@ type Props = {
export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => { export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => {
return ( return (
<div className={className}> <div className={className}>
<span className="text-xs font-medium text-mineshaft-400">{label}</span> <span className="text-sm text-mineshaft-400">{label}</span>
{children ? ( {children ? (
<p className="break-words text-sm leading-4">{children}</p> <p className="break-words text-base leading-4">{children}</p>
) : ( ) : (
<p className="text-sm italic leading-4 text-bunker-400">Not set</p> <p className="text-base italic leading-4 text-bunker-400">Not set</p>
)} )}
</div> </div>
); );
@@ -75,8 +75,8 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
return ( return (
<div className="col-span-2 mt-3"> <div className="col-span-2 mt-3">
<div className="flex items-baseline justify-between border-b border-mineshaft-500 pb-1"> <div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-sm text-bunker-300">Access Tokens</span> <span className="text-bunker-300">Access Tokens</span>
<Button <Button
size="xs" size="xs"
onClick={() => { onClick={() => {
@@ -64,8 +64,8 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
return ( return (
<div className="col-span-2"> <div className="col-span-2">
<div className="flex items-baseline justify-between border-b border-mineshaft-500 pb-1"> <div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-sm text-bunker-300">Client Secrets</span> <span className="text-bunker-300">Client Secrets</span>
<Button <Button
size="xs" size="xs"
onClick={() => { onClick={() => {
@@ -1,3 +1,4 @@
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, Modal, ModalContent } from "@app/components/v2"; import { DeleteActionModal, Modal, ModalContent } from "@app/components/v2";
import { useOrganization } from "@app/context"; import { useOrganization } from "@app/context";
@@ -30,7 +31,6 @@ type Props = {
authMethod?: IdentityAuthMethod; authMethod?: IdentityAuthMethod;
isOpen: boolean; isOpen: boolean;
onOpenChange: (isOpen: boolean) => void; onOpenChange: (isOpen: boolean) => void;
onEditAuthMethod: (authMethod: IdentityAuthMethod) => void;
onDeleteAuthMethod: () => void; onDeleteAuthMethod: () => void;
}; };
@@ -42,13 +42,16 @@ type TRevokeOptions = {
export const Content = ({ export const Content = ({
identityId, identityId,
authMethod, authMethod,
onEditAuthMethod,
onDeleteAuthMethod onDeleteAuthMethod
}: Pick<Props, "authMethod" | "identityId" | "onEditAuthMethod" | "onDeleteAuthMethod">) => { }: Pick<Props, "authMethod" | "identityId" | "onDeleteAuthMethod">) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["revokeAuthMethod"] as const); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"revokeAuthMethod",
"upgradePlan",
"identityAuthMethod"
] as const);
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth(); const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth(); const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
@@ -128,7 +131,9 @@ export const Content = ({
<Component <Component
identityId={identityId} identityId={identityId}
onDelete={handleDelete} onDelete={handleDelete}
onEdit={() => onEditAuthMethod(authMethod)} popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/> />
<DeleteActionModal <DeleteActionModal
isOpen={popUp?.revokeAuthMethod?.isOpen} isOpen={popUp?.revokeAuthMethod?.isOpen}
@@ -138,6 +143,11 @@ export const Content = ({
buttonText="Remove" buttonText="Remove"
onDeleteApproved={handleDeleteAuthMethod} onDeleteApproved={handleDeleteAuthMethod}
/> />
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={(popUp.upgradePlan?.data as { description: string })?.description}
/>
</> </>
); );
}; };
@@ -146,8 +156,7 @@ export const ViewIdentityAuthModal = ({
isOpen, isOpen,
onOpenChange, onOpenChange,
authMethod, authMethod,
identityId, identityId
onEditAuthMethod
}: Omit<Props, "onDeleteAuthMethod">) => { }: Omit<Props, "onDeleteAuthMethod">) => {
if (!identityId || !authMethod) return null; if (!identityId || !authMethod) return null;
@@ -157,7 +166,6 @@ export const ViewIdentityAuthModal = ({
<Content <Content
identityId={identityId} identityId={identityId}
authMethod={authMethod} authMethod={authMethod}
onEditAuthMethod={onEditAuthMethod}
onDeleteAuthMethod={() => onOpenChange(false)} onDeleteAuthMethod={() => onOpenChange(false)}
/> />
</ModalContent> </ModalContent>
@@ -2,6 +2,7 @@ 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";
@@ -9,8 +10,10 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityAwsAuthContent = ({ export const ViewIdentityAwsAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityAwsAuth(identityId); const { data, isPending } = useGetIdentityAwsAuth(identityId);
@@ -28,8 +31,34 @@ export const ViewIdentityAwsAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityAwsAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Principal ARNs"> <IdentityAuthFieldDisplay className="col-span-2" label="Allowed Principal ARNs">
{data.allowedPrincipalArns {data.allowedPrincipalArns
?.split(",") ?.split(",")
@@ -45,18 +74,6 @@ export const ViewIdentityAwsAuthContent = ({
<IdentityAuthFieldDisplay className="col-span-2" label="STS Endpoint"> <IdentityAuthFieldDisplay className="col-span-2" label="STS Endpoint">
{data.stsEndpoint} {data.stsEndpoint}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper> </ViewIdentityContentWrapper>
); );
}; };
@@ -2,6 +2,7 @@ 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";
@@ -9,8 +10,10 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityAzureAuthContent = ({ export const ViewIdentityAzureAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityAzureAuth(identityId); const { data, isPending } = useGetIdentityAzureAuth(identityId);
@@ -28,20 +31,22 @@ export const ViewIdentityAzureAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityAzureAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
<IdentityAuthFieldDisplay className="col-span-2" label="Tenant ID"> onEdit={() => handlePopUpOpen("identityAuthMethod")}
{data.tenantId} onDelete={onDelete}
</IdentityAuthFieldDisplay> >
<IdentityAuthFieldDisplay className="col-span-2" label="Resource / Audience">
{data.resource}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Service Principal IDs">
{data.allowedServicePrincipalIds
?.split(",")
.map((id) => id.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -54,6 +59,18 @@ export const ViewIdentityAzureAuthContent = ({
<IdentityAuthFieldDisplay label="Access Token Trusted IPs"> <IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")} {data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Tenant ID">
{data.tenantId}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Resource / Audience">
{data.resource}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Service Principal IDs">
{data.allowedServicePrincipalIds
?.split(",")
.map((id) => id.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper> </ViewIdentityContentWrapper>
); );
}; };
@@ -1,8 +1,14 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { faEdit, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faChevronDown, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "@app/components/v2"; import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
type Props = { type Props = {
children: ReactNode; children: ReactNode;
@@ -14,25 +20,28 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div> <div>
<div className="flex items-baseline justify-between border-b border-mineshaft-500 pb-1"> <div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-sm text-bunker-300">Details</span> <span className="text-bunker-300">Details</span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <DropdownMenu>
size="xs" <DropdownMenuTrigger asChild>
onClick={onEdit} <Button
leftIcon={<FontAwesomeIcon icon={faEdit} />} size="xs"
colorSchema="secondary" rightIcon={<FontAwesomeIcon className="ml-1" icon={faChevronDown} />}
> colorSchema="secondary"
Edit >
</Button> Options
<Button </Button>
size="xs" </DropdownMenuTrigger>
onClick={onDelete} <DropdownMenuContent className="min-w-[120px]" align="end">
leftIcon={<FontAwesomeIcon icon={faTrash} />} <DropdownMenuItem onClick={onEdit} icon={<FontAwesomeIcon icon={faEdit} />}>
colorSchema="danger" Edit
> </DropdownMenuItem>
Delete <DropdownMenuItem onClick={onDelete} icon={<FontAwesomeIcon icon={faTrash} />}>
</Button> Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
</div> </div>
<div className="mt-3 grid grid-cols-2 gap-3">{children}</div> <div className="mt-3 grid grid-cols-2 gap-3">{children}</div>
@@ -2,6 +2,7 @@ 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";
@@ -9,8 +10,10 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityGcpAuthContent = ({ export const ViewIdentityGcpAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityGcpAuth(identityId); const { data, isPending } = useGetIdentityGcpAuth(identityId);
@@ -28,8 +31,34 @@ export const ViewIdentityGcpAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityGcpAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Type"> <IdentityAuthFieldDisplay label="Type">
{data.type === "gce" ? "GCP ID Token Auth" : "GCP IAM Auth"} {data.type === "gce" ? "GCP ID Token Auth" : "GCP IAM Auth"}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -55,18 +84,6 @@ export const ViewIdentityGcpAuthContent = ({
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
</> </>
)} )}
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper> </ViewIdentityContentWrapper>
); );
}; };
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2"; import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
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/ViewIdentityAuthModal/ViewIdentityContentWrapper"; import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
@@ -11,8 +12,10 @@ import { ViewAuthMethodProps } from "./types";
export const ViewIdentityJwtAuthContent = ({ export const ViewIdentityJwtAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityJwtAuth(identityId); const { data, isPending } = useGetIdentityJwtAuth(identityId);
@@ -30,38 +33,66 @@ export const ViewIdentityJwtAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityJwtAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Configuration Type"> <IdentityAuthFieldDisplay label="Configuration Type">
{data.configurationType === IdentityJwtConfigurationType.JWKS ? "JWKS" : "Static"} {data.configurationType === IdentityJwtConfigurationType.JWKS ? "JWKS" : "Static"}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
{data.configurationType === IdentityJwtConfigurationType.JWKS ? ( {data.configurationType === IdentityJwtConfigurationType.JWKS ? (
<> <>
<IdentityAuthFieldDisplay className="col-span-2" label="JWKS CA Certificate">
<Tooltip
side="right"
className="max-w-xl p-2"
content={
<p className="break-words rounded bg-mineshaft-600 p-2">{data.jwksCaCert}</p>
}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<FontAwesomeIcon icon={faEye} />
<span>Reveal</span>
</Badge>
</div>
</Tooltip>
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="JWKS URL"> <IdentityAuthFieldDisplay className="col-span-2" label="JWKS URL">
{data.jwksUrl} {data.jwksUrl}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="JWKS CA Certificate">
{data.jwksCaCert && (
<Tooltip
side="right"
className="max-w-xl p-2"
content={
<p className="break-words rounded bg-mineshaft-600 p-2">{data.jwksCaCert}</p>
}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<FontAwesomeIcon icon={faEye} />
<span>Reveal</span>
</Badge>
</div>
</Tooltip>
)}
</IdentityAuthFieldDisplay>
</> </>
) : ( ) : (
<IdentityAuthFieldDisplay className="col-span-2" label="Public Keys"> <IdentityAuthFieldDisplay className="col-span-2" label="Public Keys">
<div className="flex flex-wrap gap-1"> {data.publicKeys.length && (
{data.publicKeys.length && <div className="flex flex-wrap gap-1">
data.publicKeys.map((key, index) => ( {data.publicKeys.map((key, index) => (
<Tooltip <Tooltip
side="right" side="right"
className="max-w-xl p-2" className="max-w-xl p-2"
@@ -80,7 +111,8 @@ export const ViewIdentityJwtAuthContent = ({
</div> </div>
</Tooltip> </Tooltip>
))} ))}
</div> </div>
)}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
)} )}
<IdentityAuthFieldDisplay className="col-span-2" label="Issuer"> <IdentityAuthFieldDisplay className="col-span-2" label="Issuer">
@@ -115,18 +147,6 @@ export const ViewIdentityJwtAuthContent = ({
</Tooltip> </Tooltip>
)} )}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper> </ViewIdentityContentWrapper>
); );
}; };
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2"; import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { useGetIdentityKubernetesAuth } from "@app/hooks/api"; import { 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";
@@ -10,8 +11,10 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityKubernetesAuthContent = ({ export const ViewIdentityKubernetesAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityKubernetesAuth(identityId); const { data, isPending } = useGetIdentityKubernetesAuth(identityId);
@@ -32,8 +35,34 @@ export const ViewIdentityKubernetesAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityKubernetesAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay <IdentityAuthFieldDisplay
className="col-span-2" className="col-span-2"
label="Kubernetes Host / Base Kubernetes API URL" label="Kubernetes Host / Base Kubernetes API URL"
@@ -72,30 +101,20 @@ export const ViewIdentityKubernetesAuthContent = ({
{data.allowedAudience} {data.allowedAudience}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="CA Certificate"> <IdentityAuthFieldDisplay className="col-span-2" label="CA Certificate">
<Tooltip {data.caCert && (
side="right" <Tooltip
className="max-w-xl p-2" side="right"
content={<p className="break-words rounded bg-mineshaft-600 p-2">{data.caCert}</p>} className="max-w-xl p-2"
> content={<p className="break-words rounded bg-mineshaft-600 p-2">{data.caCert}</p>}
<div className="w-min"> >
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300"> <div className="w-min">
<FontAwesomeIcon icon={faEye} /> <Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<span>Reveal</span> <FontAwesomeIcon icon={faEye} />
</Badge> <span>Reveal</span>
</div> </Badge>
</Tooltip> </div>
</IdentityAuthFieldDisplay> </Tooltip>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)"> )}
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper> </ViewIdentityContentWrapper>
); );
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2"; import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
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/ViewIdentityAuthModal/ViewIdentityContentWrapper"; import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay"; import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
@@ -10,8 +11,10 @@ import { ViewAuthMethodProps } from "./types";
export const ViewIdentityOidcAuthContent = ({ export const ViewIdentityOidcAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityOidcAuth(identityId); const { data, isPending } = useGetIdentityOidcAuth(identityId);
@@ -29,8 +32,34 @@ export const ViewIdentityOidcAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityOidcAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="OIDC Discovery URL"> <IdentityAuthFieldDisplay className="col-span-2" label="OIDC Discovery URL">
{data.oidcDiscoveryUrl} {data.oidcDiscoveryUrl}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -38,18 +67,20 @@ export const ViewIdentityOidcAuthContent = ({
{data.boundIssuer} {data.boundIssuer}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="CA Certificate"> <IdentityAuthFieldDisplay className="col-span-2" label="CA Certificate">
<Tooltip {data.caCert && (
side="right" <Tooltip
className="max-w-xl p-2" side="right"
content={<p className="break-words rounded bg-mineshaft-600 p-2">{data.caCert}</p>} className="max-w-xl p-2"
> content={<p className="break-words rounded bg-mineshaft-600 p-2">{data.caCert}</p>}
<div className="w-min"> >
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300"> <div className="w-min">
<FontAwesomeIcon icon={faEye} /> <Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<span>Reveal</span> <FontAwesomeIcon icon={faEye} />
</Badge> <span>Reveal</span>
</div> </Badge>
</Tooltip> </div>
</Tooltip>
)}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Subject"> <IdentityAuthFieldDisplay className="col-span-2" label="Subject">
{data.boundSubject} {data.boundSubject}
@@ -80,18 +111,6 @@ export const ViewIdentityOidcAuthContent = ({
</Tooltip> </Tooltip>
)} )}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper> </ViewIdentityContentWrapper>
); );
}; };
@@ -2,6 +2,7 @@ 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";
@@ -10,8 +11,10 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityTokenAuthContent = ({ export const ViewIdentityTokenAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityTokenAuth(identityId); const { data, isPending } = useGetIdentityTokenAuth(identityId);
const { data: tokens = [], isPending: clientSecretsPending } = const { data: tokens = [], isPending: clientSecretsPending } =
@@ -31,8 +34,22 @@ export const ViewIdentityTokenAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityTokenAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -7,6 +7,7 @@ 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 { IdentityUniversalAuthClientSecretsTable } from "./IdentityUniversalAuthClientSecretsTable"; import { IdentityUniversalAuthClientSecretsTable } from "./IdentityUniversalAuthClientSecretsTable";
@@ -15,8 +16,10 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityUniversalAuthContent = ({ export const ViewIdentityUniversalAuthContent = ({
identityId, identityId,
onEdit, handlePopUpToggle,
onDelete handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => { }: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityUniversalAuth(identityId); const { data, isPending } = useGetIdentityUniversalAuth(identityId);
const { data: clientSecrets = [], isPending: clientSecretsPending } = const { data: clientSecrets = [], isPending: clientSecretsPending } =
@@ -43,8 +46,22 @@ export const ViewIdentityUniversalAuthContent = ({
); );
} }
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityUniversalAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return ( return (
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}> <ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)"> <IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
{data.accessTokenTTL} {data.accessTokenTTL}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
@@ -61,8 +78,8 @@ export const ViewIdentityUniversalAuthContent = ({
{data.clientSecretTrustedIps.map((ip) => ip.ipAddress).join(", ")} {data.clientSecretTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<div className="col-span-2 my-3"> <div className="col-span-2 my-3">
<div className="mb-2 border-b border-mineshaft-500"> <div className="mb-3 border-b border-mineshaft-500 pb-2">
<span className="text-sm text-bunker-300">Client ID</span> <span className="text-bunker-300">Client ID</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm">{data.clientId}</span> <span className="text-sm">{data.clientId}</span>
@@ -1,5 +1,12 @@
import { UsePopUpState } from "@app/hooks/usePopUp";
export type ViewAuthMethodProps = { export type ViewAuthMethodProps = {
identityId: string; identityId: string;
onEdit: () => void;
onDelete: () => void; onDelete: () => void;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan", "identityAuthMethod"]>) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean
) => void;
popUp: UsePopUpState<["revokeAuthMethod", "upgradePlan", "identityAuthMethod"]>;
}; };