Compare commits

..

2 Commits

Author SHA1 Message Date
b949708f45 docs(sso): fixed azure attributes typo 2025-01-23 05:20:44 +01:00
2a6b6b03b9 docs(guides): updated python guide 2025-01-23 05:20:26 +01:00
37 changed files with 3024 additions and 3743 deletions

View File

@ -5,7 +5,7 @@ title: "Python"
This guide demonstrates how to use Infisical to manage secrets for your Python stack from local development to production. It uses: This guide demonstrates how to use Infisical to manage secrets for your Python stack from local development to production. It uses:
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets. - Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets.
- The [infisical-python](https://pypi.org/project/infisical-python/) Python client SDK to fetch secrets back to your Python application on demand. - The [infisicalsdk](https://pypi.org/project/infisicalsdk/) Python client SDK to fetch secrets back to your Python application on demand.
## Project Setup ## Project Setup
@ -36,40 +36,38 @@ python3 -m venv env
source env/bin/activate source env/bin/activate
``` ```
Install Flask and [infisical-python](https://pypi.org/project/infisical-python/), the client Python SDK for Infisical. Install Flask and [infisicalsdk](https://pypi.org/project/infisicalsdk/), the client Python SDK for Infisical.
```console ```console
pip install flask infisical-python pip install flask infisicalsdk
``` ```
Finally, create an `app.py` file containing the application code. Finally, create an `app.py` file containing the application code.
```py ```py
from flask import Flask from flask import Flask
from infisical_client import ClientSettings, InfisicalClient, GetSecretOptions, AuthenticationOptions, UniversalAuthMethod from infisical_sdk import InfisicalSDKClient
app = Flask(__name__) app = Flask(__name__)
client = InfisicalClient(ClientSettings( client = InfisicalSDKClient(host="https://app.infisical.com") # host is optional, defaults to https://app.infisical.com
auth=AuthenticationOptions(
universal_auth=UniversalAuthMethod( client.auth.universal_auth.login(
client_id="CLIENT_ID", "<machine-identity-client-id>",
client_secret="CLIENT_SECRET", "<machine-identity-client-secret>"
) )
)
))
@app.route("/") @app.route("/")
def hello_world(): def hello_world():
# access value # access value
name = client.secrets.get_secret_by_name(
secret_name="NAME",
project_id="<project-id>",
environment_slug="dev",
secret_path="/"
)
name = client.getSecret(options=GetSecretOptions( return f"Hello! My name is: {name.secretValue}"
environment="dev",
project_id="PROJECT_ID",
secret_name="NAME"
))
return f"Hello! My name is: {name.secret_value}"
``` ```
Here, we initialized a `client` instance of the Infisical Python SDK with the Infisical Token Here, we initialized a `client` instance of the Infisical Python SDK with the Infisical Token
@ -89,15 +87,6 @@ At this stage, you know how to fetch secrets from Infisical back to your Python
## FAQ ## FAQ
<AccordionGroup> <AccordionGroup>
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
The client SDK caches every secret and implements a 5-minute waiting period before
re-requesting it. The waiting period can be controlled by setting the `cacheTTL` parameter at
the time of initializing the client.
</Accordion>
<Accordion title="What if a request for a secret fails?">
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
value ever-existed, the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="What's the point if I still have to manage a token for the SDK?"> <Accordion title="What's the point if I still have to manage a token for the SDK?">
The token enables the SDK to authenticate with Infisical to fetch back your secrets. The token enables the SDK to authenticate with Infisical to fetch back your secrets.
Although the SDK requires you to pass in a token, it enables greater efficiency and security Although the SDK requires you to pass in a token, it enables greater efficiency and security
@ -114,6 +103,6 @@ At this stage, you know how to fetch secrets from Infisical back to your Python
See also: See also:
- Explore the [Python SDK](https://github.com/Infisical/sdk/tree/main/crates/infisical-py) - Explore the [Python SDK](https://github.com/Infisical/python-sdk-official)

View File

@ -48,7 +48,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **Attributes & Claims** section and configure the following map: Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **Attributes & Claims** section and configure the following map:
- `email -> user.userprinciplename` - `email -> user.userprincipalname`
- `firstName -> user.givenname` - `firstName -> user.givenname`
- `lastName -> user.surname` - `lastName -> user.surname`

View File

@ -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.getIdentityTokenAuth(identityId) queryKey: identitiesKeys.getIdentityUniversalAuth(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.getIdentityTokenAuth(identityId) queryKey: identitiesKeys.getIdentityUniversalAuth(identityId)
}); });
} }
}); });

View File

@ -182,7 +182,7 @@ export const queryClient = new QueryClient({
createNotification({ createNotification({
title: "Bad Request", title: "Bad Request",
type: "error", type: "error",
text: `${serverResponse.message}${serverResponse.message?.endsWith(".") ? "" : "."}`, text: `${serverResponse.message}${serverResponse.message.endsWith(".") ? "" : "."}`,
copyActions: [ copyActions: [
{ {
value: serverResponse.reqId, value: serverResponse.reqId,

View File

@ -7,10 +7,10 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityAuthMethodModalContent } from "./IdentityAuthMethodModalContent"; import { IdentityAuthMethodModalContent } from "./IdentityAuthMethodModalContent";
type Props = { type Props = {
popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>; popUp: UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
}; };
@ -34,7 +34,7 @@ export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpTog
title={ title={
isSelectedAuthAlreadyConfigured isSelectedAuthAlreadyConfigured
? `Edit ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}` ? `Edit ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}`
: `Add ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}` : `Create new ${identityAuthToNameMap[selectedAuthMethod!] ?? ""}`
} }
> >
<IdentityAuthMethodModalContent <IdentityAuthMethodModalContent

View File

@ -4,8 +4,30 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { Badge, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; import { createNotification } from "@app/components/notifications";
import { IdentityAuthMethod } from "@app/hooks/api/identities"; import {
Badge,
DeleteActionModal,
FormControl,
Select,
SelectItem,
Tooltip
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useDeleteIdentityAwsAuth,
useDeleteIdentityAzureAuth,
useDeleteIdentityGcpAuth,
useDeleteIdentityKubernetesAuth,
useDeleteIdentityOidcAuth,
useDeleteIdentityTokenAuth,
useDeleteIdentityUniversalAuth
} from "@app/hooks/api";
import {
IdentityAuthMethod,
identityAuthToNameMap,
useDeleteIdentityJwtAuth
} from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm"; import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm";
@ -18,10 +40,10 @@ import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm";
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm"; import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
type Props = { type Props = {
popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>; popUp: UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
@ -34,7 +56,13 @@ type Props = {
setSelectedAuthMethod: (authMethod: IdentityAuthMethod) => void; setSelectedAuthMethod: (authMethod: IdentityAuthMethod) => void;
}; };
type TRevokeOptions = {
identityId: string;
organizationId: string;
};
type TRevokeMethods = { type TRevokeMethods = {
revokeMethod: (revokeOptions: TRevokeOptions) => Promise<any>;
render: () => JSX.Element; render: () => JSX.Element;
}; };
@ -68,6 +96,18 @@ export const IdentityAuthMethodModalContent = ({
initialAuthMethod, initialAuthMethod,
setSelectedAuthMethod setSelectedAuthMethod
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
const { control, watch } = useForm<FormData>({ const { control, watch } = useForm<FormData>({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: async () => { defaultValues: async () => {
@ -109,9 +149,10 @@ export const IdentityAuthMethodModalContent = ({
const methodMap: Record<IdentityAuthMethod, TRevokeMethods | undefined> = { const methodMap: Record<IdentityAuthMethod, TRevokeMethods | undefined> = {
[IdentityAuthMethod.UNIVERSAL_AUTH]: { [IdentityAuthMethod.UNIVERSAL_AUTH]: {
revokeMethod: revokeUniversalAuth,
render: () => ( render: () => (
<IdentityUniversalAuthForm <IdentityUniversalAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -119,9 +160,10 @@ export const IdentityAuthMethodModalContent = ({
}, },
[IdentityAuthMethod.OIDC_AUTH]: { [IdentityAuthMethod.OIDC_AUTH]: {
revokeMethod: revokeOidcAuth,
render: () => ( render: () => (
<IdentityOidcAuthForm <IdentityOidcAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -129,9 +171,10 @@ export const IdentityAuthMethodModalContent = ({
}, },
[IdentityAuthMethod.TOKEN_AUTH]: { [IdentityAuthMethod.TOKEN_AUTH]: {
revokeMethod: revokeTokenAuth,
render: () => ( render: () => (
<IdentityTokenAuthForm <IdentityTokenAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -139,9 +182,10 @@ export const IdentityAuthMethodModalContent = ({
}, },
[IdentityAuthMethod.AZURE_AUTH]: { [IdentityAuthMethod.AZURE_AUTH]: {
revokeMethod: revokeAzureAuth,
render: () => ( render: () => (
<IdentityAzureAuthForm <IdentityAzureAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -149,9 +193,10 @@ export const IdentityAuthMethodModalContent = ({
}, },
[IdentityAuthMethod.GCP_AUTH]: { [IdentityAuthMethod.GCP_AUTH]: {
revokeMethod: revokeGcpAuth,
render: () => ( render: () => (
<IdentityGcpAuthForm <IdentityGcpAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -159,9 +204,10 @@ export const IdentityAuthMethodModalContent = ({
}, },
[IdentityAuthMethod.KUBERNETES_AUTH]: { [IdentityAuthMethod.KUBERNETES_AUTH]: {
revokeMethod: revokeKubernetesAuth,
render: () => ( render: () => (
<IdentityKubernetesAuthForm <IdentityKubernetesAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -169,9 +215,10 @@ export const IdentityAuthMethodModalContent = ({
}, },
[IdentityAuthMethod.AWS_AUTH]: { [IdentityAuthMethod.AWS_AUTH]: {
revokeMethod: revokeAwsAuth,
render: () => ( render: () => (
<IdentityAwsAuthForm <IdentityAwsAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -179,9 +226,10 @@ export const IdentityAuthMethodModalContent = ({
}, },
[IdentityAuthMethod.JWT_AUTH]: { [IdentityAuthMethod.JWT_AUTH]: {
revokeMethod: revokeJwtAuth,
render: () => ( render: () => (
<IdentityJwtAuthForm <IdentityJwtAuthForm
identityId={identityAuthMethodData.identityId} identityAuthMethodData={identityAuthMethodData}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
@ -246,6 +294,42 @@ export const IdentityAuthMethodModalContent = ({
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use IP allowlisting if you switch to Infisical's Pro plan." text="You can use IP allowlisting if you switch to Infisical's Pro plan."
/> />
<DeleteActionModal
isOpen={popUp?.revokeAuthMethod?.isOpen}
title={`Are you sure want to remove ${
identityAuthMethodData?.authMethod
? identityAuthToNameMap[identityAuthMethodData.authMethod]
: "the auth method"
} on ${identityAuthMethodData?.name ?? ""}?`}
onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
deleteKey="confirm"
buttonText="Remove"
onDeleteApproved={async () => {
if (!identityAuthMethodData.authMethod || !orgId || !selectedMethodItem) {
return;
}
try {
await selectedMethodItem.revokeMethod({
identityId: identityAuthMethodData.identityId,
organizationId: orgId
});
createNotification({
text: "Successfully removed auth method",
type: "success"
});
handlePopUpToggle("revokeAuthMethod", false);
handlePopUpToggle("identityAuthMethod", false);
} catch {
createNotification({
text: "Failed to remove auth method",
type: "error"
});
}
}}
/>
</> </>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect } 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,27 +6,17 @@ 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 { import { Button, FormControl, IconButton, Input } from "@app/components/v2";
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(),
@ -59,18 +49,21 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityAwsAuthForm = ({ export const IdentityAwsAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@ -78,9 +71,11 @@ 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 { data } = useGetIdentityAwsAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityAwsAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@ -148,7 +143,7 @@ export const IdentityAwsAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityAuthMethodData) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
@ -156,7 +151,7 @@ export const IdentityAwsAuthForm = ({
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
allowedAccountIds, allowedAccountIds,
identityId, identityId: identityAuthMethodData.identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@ -165,7 +160,7 @@ export const IdentityAwsAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
stsEndpoint: stsEndpoint || "", stsEndpoint: stsEndpoint || "",
allowedPrincipalArns: allowedPrincipalArns || "", allowedPrincipalArns: allowedPrincipalArns || "",
allowedAccountIds: allowedAccountIds || "", allowedAccountIds: allowedAccountIds || "",
@ -193,21 +188,7 @@ export const IdentityAwsAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
? IdentityFormTab.Advanced
: IdentityFormTab.Configuration
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
defaultValue="2592000" defaultValue="2592000"
@ -291,8 +272,6 @@ export const IdentityAwsAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => ( {accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -361,8 +340,7 @@ export const IdentityAwsAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -371,7 +349,7 @@ export const IdentityAwsAuthForm = ({
isLoading={isSubmitting} isLoading={isSubmitting}
isDisabled={isSubmitting} isDisabled={isSubmitting}
> >
{isUpdate ? "Update" : "Add"} {!isUpdate ? "Create" : "Edit"}
</Button> </Button>
<Button <Button
@ -382,6 +360,18 @@ export const IdentityAwsAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Remove Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect } 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,27 +6,17 @@ 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 { import { Button, FormControl, IconButton, Input } from "@app/components/v2";
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),
@ -54,18 +44,21 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityAzureAuthForm = ({ export const IdentityAzureAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@ -73,9 +66,11 @@ 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 { data } = useGetIdentityAzureAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityAzureAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@ -144,12 +139,12 @@ export const IdentityAzureAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityAuthMethodData) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
tenantId, tenantId,
resource, resource,
allowedServicePrincipalIds, allowedServicePrincipalIds,
@ -161,7 +156,7 @@ export const IdentityAzureAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
tenantId: tenantId || "", tenantId: tenantId || "",
resource: resource || "", resource: resource || "",
allowedServicePrincipalIds: allowedServicePrincipalIds || "", allowedServicePrincipalIds: allowedServicePrincipalIds || "",
@ -189,21 +184,7 @@ export const IdentityAzureAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
? IdentityFormTab.Advanced
: IdentityFormTab.Configuration
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
defaultValue="2592000" defaultValue="2592000"
@ -287,8 +268,6 @@ export const IdentityAzureAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => ( {accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -357,8 +336,7 @@ export const IdentityAzureAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -367,7 +345,7 @@ export const IdentityAzureAuthForm = ({
isLoading={isSubmitting} isLoading={isSubmitting}
isDisabled={isSubmitting} isDisabled={isSubmitting}
> >
{isUpdate ? "Update" : "Add"} {!isUpdate ? "Create" : "Edit"}
</Button> </Button>
<Button <Button
@ -378,6 +356,18 @@ export const IdentityAzureAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Remove Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect } 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,29 +6,17 @@ 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 { import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2";
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"]),
@ -57,18 +45,21 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityGcpAuthForm = ({ export const IdentityGcpAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@ -76,9 +67,11 @@ 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 { data } = useGetIdentityGcpAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityGcpAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@ -153,11 +146,11 @@ export const IdentityGcpAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityAuthMethodData) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
identityId, identityId: identityAuthMethodData.identityId,
organizationId: orgId, organizationId: orgId,
type, type,
allowedServiceAccounts, allowedServiceAccounts,
@ -170,7 +163,7 @@ export const IdentityGcpAuthForm = ({
}); });
} else { } else {
await addMutateAsync({ await addMutateAsync({
identityId, identityId: identityAuthMethodData.identityId,
organizationId: orgId, organizationId: orgId,
type, type,
allowedServiceAccounts: allowedServiceAccounts || "", allowedServiceAccounts: allowedServiceAccounts || "",
@ -200,21 +193,7 @@ export const IdentityGcpAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
? IdentityFormTab.Advanced
: IdentityFormTab.Configuration
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
name="type" name="type"
@ -274,11 +253,7 @@ export const IdentityGcpAuthForm = ({
control={control} control={control}
name="allowedZones" name="allowedZones"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl label="Allowed Zones" isError={Boolean(error)} errorText={error?.message}>
label="Allowed Zones"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="us-west2-a, us-central1-a, ..." /> <Input {...field} placeholder="us-west2-a, us-central1-a, ..." />
</FormControl> </FormControl>
)} )}
@ -326,8 +301,6 @@ export const IdentityGcpAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => ( {accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -396,8 +369,7 @@ export const IdentityGcpAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -406,7 +378,7 @@ export const IdentityGcpAuthForm = ({
isLoading={isSubmitting} isLoading={isSubmitting}
isDisabled={isSubmitting} isDisabled={isSubmitting}
> >
{isUpdate ? "Update" : "Add"} {!isUpdate ? "Create" : "Edit"}
</Button> </Button>
<Button <Button
@ -417,6 +389,18 @@ export const IdentityGcpAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Remove Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect } 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,22 +14,17 @@ 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(
@ -90,18 +85,21 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityJwtAuthForm = ({ export const IdentityJwtAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@ -109,9 +107,11 @@ 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 { data } = useGetIdentityJwtAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
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 (!identityId) { if (!identityAuthMethodData) {
return; return;
} }
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
identityId, identityId: identityAuthMethodData.identityId,
organizationId: orgId, organizationId: orgId,
configurationType, configurationType,
jwksUrl, jwksUrl,
@ -241,7 +241,7 @@ export const IdentityJwtAuthForm = ({
}); });
} else { } else {
await addMutateAsync({ await addMutateAsync({
identityId, identityId: identityAuthMethodData.identityId,
configurationType, configurationType,
jwksUrl, jwksUrl,
jwksCaCert, jwksCaCert,
@ -275,21 +275,7 @@ export const IdentityJwtAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
? IdentityFormTab.Advanced
: IdentityFormTab.Configuration
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
name="configurationType" name="configurationType"
@ -359,6 +345,7 @@ export const IdentityJwtAuthForm = ({
/> />
</> </>
)} )}
{selectedConfigurationType === IdentityJwtConfigurationType.STATIC && ( {selectedConfigurationType === IdentityJwtConfigurationType.STATIC && (
<> <>
{publicKeyFields.map(({ id }, index) => ( {publicKeyFields.map(({ id }, index) => (
@ -423,6 +410,7 @@ export const IdentityJwtAuthForm = ({
</div> </div>
</> </>
)} )}
<Controller <Controller
control={control} control={control}
name="boundIssuer" name="boundIssuer"
@ -595,8 +583,6 @@ export const IdentityJwtAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => ( {accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -665,8 +651,7 @@ export const IdentityJwtAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -686,6 +671,18 @@ export const IdentityJwtAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Remove Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect } 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,28 +6,17 @@ 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 { import { Button, FormControl, IconButton, Input, TextArea } from "@app/components/v2";
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),
@ -58,18 +47,21 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityKubernetesAuthForm = ({ export const IdentityKubernetesAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@ -77,9 +69,11 @@ 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 { data } = useGetIdentityKubernetesAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityKubernetesAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@ -160,7 +154,7 @@ export const IdentityKubernetesAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityAuthMethodData) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
@ -171,7 +165,7 @@ export const IdentityKubernetesAuthForm = ({
allowedNamespaces, allowedNamespaces,
allowedAudience, allowedAudience,
caCert, caCert,
identityId, identityId: identityAuthMethodData.identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@ -180,7 +174,7 @@ export const IdentityKubernetesAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
kubernetesHost: kubernetesHost || "", kubernetesHost: kubernetesHost || "",
tokenReviewerJwt, tokenReviewerJwt,
allowedNames: allowedNames || "", allowedNames: allowedNames || "",
@ -211,29 +205,7 @@ export const IdentityKubernetesAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
[
"kubernetesHost",
"tokenReviewerJwt",
"accessTokenTTL",
"accessTokenMaxTTL",
"accessTokenNumUsesLimit",
"allowedNames",
"allowedNamespaces"
].includes(Object.keys(fields)[0])
? IdentityFormTab.Configuration
: IdentityFormTab.Advanced
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
defaultValue="2592000" defaultValue="2592000"
@ -265,6 +237,20 @@ export const IdentityKubernetesAuthForm = ({
</FormControl> </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 <Controller
control={control} control={control}
defaultValue="" defaultValue=""
@ -282,15 +268,30 @@ export const IdentityKubernetesAuthForm = ({
/> />
<Controller <Controller
control={control} control={control}
name="allowedNames" defaultValue=""
name="allowedAudience"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Allowed Service Account Names" label="Allowed Audience"
isError={Boolean(error)} 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} 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="service-account-1-name, service-account-1-name" /> <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> </FormControl>
)} )}
/> />
@ -339,37 +340,6 @@ export const IdentityKubernetesAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
<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) => ( {accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -439,8 +409,7 @@ export const IdentityKubernetesAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -449,7 +418,7 @@ export const IdentityKubernetesAuthForm = ({
isLoading={isSubmitting} isLoading={isSubmitting}
isDisabled={isSubmitting} isDisabled={isSubmitting}
> >
{isUpdate ? "Update" : "Add"} {isUpdate ? "Update" : "Create"}
</Button> </Button>
<Button <Button
@ -460,6 +429,18 @@ export const IdentityKubernetesAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Remove Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect } 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,26 +7,14 @@ 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 { import { Button, FormControl, IconButton, Input, TextArea, Tooltip } from "@app/components/v2";
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(
@ -60,18 +48,21 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityOidcAuthForm = ({ export const IdentityOidcAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@ -79,9 +70,11 @@ 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 { data } = useGetIdentityOidcAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityOidcAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@ -167,13 +160,13 @@ export const IdentityOidcAuthForm = ({
boundSubject boundSubject
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) { if (!identityAuthMethodData) {
return; return;
} }
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
identityId, identityId: identityAuthMethodData.identityId,
organizationId: orgId, organizationId: orgId,
oidcDiscoveryUrl, oidcDiscoveryUrl,
caCert, caCert,
@ -188,7 +181,7 @@ export const IdentityOidcAuthForm = ({
}); });
} else { } else {
await addMutateAsync({ await addMutateAsync({
identityId, identityId: identityAuthMethodData.identityId,
oidcDiscoveryUrl, oidcDiscoveryUrl,
caCert, caCert,
boundIssuer, boundIssuer,
@ -220,21 +213,7 @@ export const IdentityOidcAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
["accessTokenTrustedIps", "caCert", "boundClaims"].includes(Object.keys(fields)[0])
? IdentityFormTab.Advanced
: IdentityFormTab.Configuration
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
name="oidcDiscoveryUrl" name="oidcDiscoveryUrl"
@ -271,6 +250,15 @@ export const IdentityOidcAuthForm = ({
</FormControl> </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 <Controller
control={control} control={control}
name="boundSubject" name="boundSubject"
@ -313,63 +301,6 @@ export const IdentityOidcAuthForm = ({
</FormControl> </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}>
<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>
)}
/>
{boundClaimsFields.map(({ id }, index) => ( {boundClaimsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -449,6 +380,48 @@ export const IdentityOidcAuthForm = ({
Add Claims Add Claims
</Button> </Button>
</div> </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) => ( {accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -517,8 +490,7 @@ export const IdentityOidcAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -527,7 +499,7 @@ export const IdentityOidcAuthForm = ({
isLoading={isSubmitting} isLoading={isSubmitting}
isDisabled={isSubmitting} isDisabled={isSubmitting}
> >
{isUpdate ? "Update" : "Add"} {isUpdate ? "Update" : "Create"}
</Button> </Button>
<Button <Button
@ -538,6 +510,18 @@ export const IdentityOidcAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Remove Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -1,4 +1,3 @@
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,27 +5,16 @@ 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 { import { Button, FormControl, IconButton, Input } from "@app/components/v2";
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 { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityAuthMethod } from "@app/hooks/api/identities";
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, {
@ -51,18 +39,21 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData?: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityTokenAuthForm = ({ export const IdentityTokenAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
isUpdate
}: Props) => { }: Props) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
@ -70,9 +61,12 @@ 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 { data } = useGetIdentityTokenAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityTokenAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@ -97,30 +91,6 @@ 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,
@ -128,12 +98,12 @@ export const IdentityTokenAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityAuthMethodData) return;
if (data) { if (data) {
await updateMutateAsync({ await updateMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@ -142,7 +112,7 @@ export const IdentityTokenAuthForm = ({
} else { } else {
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@ -167,21 +137,7 @@ export const IdentityTokenAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
? IdentityFormTab.Advanced
: IdentityFormTab.Configuration
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
defaultValue="2592000" defaultValue="2592000"
@ -224,8 +180,6 @@ export const IdentityTokenAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{accessTokenTrustedIpsFields.map(({ id }, index) => ( {accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -294,8 +248,7 @@ export const IdentityTokenAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -304,7 +257,7 @@ export const IdentityTokenAuthForm = ({
isLoading={isSubmitting} isLoading={isSubmitting}
isDisabled={isSubmitting} isDisabled={isSubmitting}
> >
{isUpdate ? "Update" : "Add"} {isUpdate ? "Update" : "Create"}
</Button> </Button>
<Button <Button
@ -315,6 +268,18 @@ export const IdentityTokenAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Remove Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -0,0 +1,344 @@
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { faCheck, faCopy, faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
// DeleteActionModal,
EmptyState,
FormControl,
IconButton,
Input,
Modal,
ModalContent,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { useToggle } from "@app/hooks";
import {
useCreateIdentityUniversalAuthClientSecret,
useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = z.object({
description: z.string(),
ttl: z
.string()
.refine((value) => Number(value) <= 315360000, "TTL cannot be greater than 315360000"),
numUsesLimit: z.string()
});
export type FormData = z.infer<typeof schema>;
type Props = {
popUp: UsePopUpState<["universalAuthClientSecret", "revokeClientSecret"]>;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["revokeClientSecret"]>,
data?: {
clientSecretPrefix: string;
clientSecretId: string;
}
) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["universalAuthClientSecret", "revokeClientSecret"]>,
state?: boolean
) => void;
};
export const IdentityUniversalAuthClientSecretModal = ({
popUp,
handlePopUpOpen,
handlePopUpToggle
}: Props) => {
const { t } = useTranslation();
const [token, setToken] = useState("");
const [isClientSecretCopied, setIsClientSecretCopied] = useToggle(false);
const [isClientIdCopied, setIsClientIdCopied] = useToggle(false);
const popUpData = popUp?.universalAuthClientSecret?.data as {
identityId?: string;
name?: string;
};
const { data, isPending } = useGetIdentityUniversalAuthClientSecrets(popUpData?.identityId ?? "");
const { data: identityUniversalAuth } = useGetIdentityUniversalAuth(popUpData?.identityId ?? "");
const { mutateAsync: createClientSecretMutateAsync } =
useCreateIdentityUniversalAuthClientSecret();
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
description: "",
ttl: "",
numUsesLimit: ""
}
});
useEffect(() => {
let timer: NodeJS.Timeout;
if (isClientSecretCopied) {
timer = setTimeout(() => setIsClientSecretCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isClientSecretCopied]);
useEffect(() => {
let timer: NodeJS.Timeout;
if (isClientIdCopied) {
timer = setTimeout(() => setIsClientIdCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isClientIdCopied]);
const onFormSubmit = async ({ description, ttl, numUsesLimit }: FormData) => {
try {
if (!popUpData?.identityId) return;
const { clientSecret } = await createClientSecretMutateAsync({
identityId: popUpData.identityId,
description,
ttl: Number(ttl),
numUsesLimit: Number(numUsesLimit)
});
setToken(clientSecret);
createNotification({
text: "Successfully created client secret",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to create client secret",
type: "error"
});
}
};
const hasToken = Boolean(token);
return (
<Modal
isOpen={popUp?.universalAuthClientSecret?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("universalAuthClientSecret", isOpen);
reset();
setToken("");
}}
>
<ModalContent title={`Manage Client ID/Secrets for ${popUpData?.name ?? ""}`}>
<h2 className="mb-4">Client ID</h2>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{identityUniversalAuth?.clientId ?? ""}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(identityUniversalAuth?.clientId ?? "");
setIsClientIdCopied.on();
}}
>
<FontAwesomeIcon icon={isClientIdCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
{t("common.click-to-copy")}
</span>
</IconButton>
</div>
<h2 className="mb-4">New Client Secret</h2>
{hasToken ? (
<div>
<div className="mb-4 flex items-center justify-between">
<p>We will only show this secret once</p>
<Button
colorSchema="secondary"
type="submit"
onClick={() => {
reset();
setToken("");
}}
>
Got it
</Button>
</div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{token}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(token);
setIsClientSecretCopied.on();
}}
>
<FontAwesomeIcon icon={isClientSecretCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
{t("common.click-to-copy")}
</span>
</IconButton>
</div>
</div>
) : (
<form onSubmit={handleSubmit(onFormSubmit)} className="mb-8">
<Controller
control={control}
defaultValue=""
name="description"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Description (optional)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="Description" />
</FormControl>
)}
/>
<div className="flex">
<Controller
control={control}
defaultValue=""
name="ttl"
render={({ field, fieldState: { error } }) => (
<FormControl
label="TTL (seconds - optional)"
isError={Boolean(error)}
errorText={error?.message}
>
<div className="flex">
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</div>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="numUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
className="ml-4"
>
<div className="flex">
<Input {...field} placeholder="0" type="number" min="0" step="1" />
<Button
className="ml-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Create
</Button>
</div>
</FormControl>
)}
/>
</div>
</form>
)}
<h2 className="mb-4">Client Secrets</h2>
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Description</Th>
<Th>Num Uses</Th>
<Th>Expires At</Th>
<Th>Client Secret</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={5} innerKey="org-identities-client-secrets" />}
{!isPending &&
data &&
data.length > 0 &&
data.map(
({
id,
description,
clientSecretTTL,
clientSecretPrefix,
clientSecretNumUses,
clientSecretNumUsesLimit,
createdAt
}) => {
let expiresAt;
if (clientSecretTTL > 0) {
expiresAt = new Date(new Date(createdAt).getTime() + clientSecretTTL * 1000);
}
return (
<Tr className="h-10 items-center" key={`mi-client-secret-${id}`}>
<Td>{description === "" ? "-" : description}</Td>
<Td>{`${clientSecretNumUses}${
clientSecretNumUsesLimit ? `/${clientSecretNumUsesLimit}` : ""
}`}</Td>
<Td>{expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"}</Td>
<Td>{`${clientSecretPrefix}****`}</Td>
<Td>
<IconButton
onClick={() => {
handlePopUpOpen("revokeClientSecret", {
clientSecretPrefix,
clientSecretId: id
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</Td>
</Tr>
);
}
)}
{!isPending && data && data?.length === 0 && (
<Tr>
<Td colSpan={5}>
<EmptyState
title="No client secrets have been created for this identity yet"
icon={faKey}
/>
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
</ModalContent>
</Modal>
);
};

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect } 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,27 +6,17 @@ 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 { import { Button, FormControl, IconButton, Input } from "@app/components/v2";
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
@ -62,27 +52,33 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: ( handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>, popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
state?: boolean state?: boolean
) => void; ) => void;
identityId?: string; identityAuthMethodData?: {
isUpdate?: boolean; identityId: string;
name: string;
configuredAuthMethods?: IdentityAuthMethod[];
authMethod?: IdentityAuthMethod;
};
}; };
export const IdentityUniversalAuthForm = ({ export const IdentityUniversalAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityId, identityAuthMethodData
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 { data } = useGetIdentityUniversalAuth(identityId ?? "", { const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
identityAuthMethodData.authMethod! || ""
);
const { data } = useGetIdentityUniversalAuth(identityAuthMethodData?.identityId ?? "", {
enabled: isUpdate enabled: isUpdate
}); });
@ -153,13 +149,13 @@ export const IdentityUniversalAuthForm = ({
accessTokenTrustedIps accessTokenTrustedIps
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityAuthMethodData) return;
if (data) { if (data) {
// update universal auth configuration // update universal auth configuration
await updateMutateAsync({ await updateMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
clientSecretTrustedIps, clientSecretTrustedIps,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
@ -171,7 +167,7 @@ export const IdentityUniversalAuthForm = ({
await addMutateAsync({ await addMutateAsync({
organizationId: orgId, organizationId: orgId,
identityId, identityId: identityAuthMethodData.identityId,
clientSecretTrustedIps, clientSecretTrustedIps,
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
@ -199,21 +195,7 @@ export const IdentityUniversalAuthForm = ({
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)}>
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
["accessTokenTrustedIps", "clientSecretTrustedIps"].includes(Object.keys(fields)[0])
? IdentityFormTab.Advanced
: IdentityFormTab.Configuration
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller <Controller
control={control} control={control}
defaultValue="2592000" defaultValue="2592000"
@ -256,8 +238,6 @@ export const IdentityUniversalAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
{clientSecretTrustedIpsFields.map(({ id }, index) => ( {clientSecretTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}> <div className="mb-3 flex items-end space-x-2" key={id}>
<Controller <Controller
@ -394,8 +374,7 @@ export const IdentityUniversalAuthForm = ({
Add IP Address Add IP Address
</Button> </Button>
</div> </div>
</TabPanel> <div className="flex justify-between">
</Tabs>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@ -404,7 +383,7 @@ export const IdentityUniversalAuthForm = ({
isLoading={isSubmitting} isLoading={isSubmitting}
isDisabled={isSubmitting} isDisabled={isSubmitting}
> >
{isUpdate ? "Update" : "Add"} {isUpdate ? "Edit" : "Create"}
</Button> </Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
@ -414,6 +393,18 @@ export const IdentityUniversalAuthForm = ({
Cancel Cancel
</Button> </Button>
</div> </div>
{isUpdate && (
<Button
size="sm"
colorSchema="danger"
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
>
Delete Auth Method
</Button>
)}
</div>
</form> </form>
); );
}; };

View File

@ -1,4 +0,0 @@
export enum IdentityFormTab {
Advanced = "advanced",
Configuration = "configuration"
}

View File

@ -1,24 +1,45 @@
import { useState } from "react";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router"; import { useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { DeleteActionModal, PageHeader } from "@app/components/v2"; import {
Button,
DeleteActionModal,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
PageHeader,
Tooltip
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useDeleteIdentity, useGetIdentityById } from "@app/hooks/api"; import {
IdentityAuthMethod,
useDeleteIdentity,
useGetIdentityById,
useRevokeIdentityTokenAuthToken,
useRevokeIdentityUniversalAuthClientSecret
} from "@app/hooks/api";
import { Identity } from "@app/hooks/api/identities/types";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal";
import { OrgAccessControlTabSections } from "@app/types/org"; import { OrgAccessControlTabSections } from "@app/types/org";
import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal"; import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
import { IdentityModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal"; import { IdentityModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal";
import { IdentityUniversalAuthClientSecretModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal";
import { import {
IdentityAuthenticationSection, IdentityAuthenticationSection,
IdentityClientSecretModal,
IdentityDetailsSection, IdentityDetailsSection,
IdentityProjectsSection IdentityProjectsSection,
IdentityTokenListModal,
IdentityTokenModal
} from "./components"; } from "./components";
const Page = () => { const Page = () => {
@ -31,13 +52,25 @@ const Page = () => {
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { data } = useGetIdentityById(identityId); const { data } = useGetIdentityById(identityId);
const { mutateAsync: deleteIdentity } = useDeleteIdentity(); const { mutateAsync: deleteIdentity } = useDeleteIdentity();
const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken();
const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret();
const [selectedAuthMethod, setSelectedAuthMethod] = useState<
Identity["authMethods"][number] | null
>(null);
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"identity", "identity",
"deleteIdentity", "deleteIdentity",
"identityAuthMethod", "identityAuthMethod",
"upgradePlan", "revokeAuthMethod",
"viewAuthMethod" "token",
"tokenList",
"revokeToken",
"clientSecret",
"revokeClientSecret",
"universalAuthClientSecret", // list of client secrets
"upgradePlan"
] as const); ] as const);
const onDeleteIdentitySubmit = async (id: string) => { const onDeleteIdentitySubmit = async (id: string) => {
@ -71,15 +104,148 @@ const Page = () => {
} }
}; };
const onRevokeTokenSubmit = async ({
identityId: parentIdentityId,
tokenId,
name
}: {
identityId: string;
tokenId: string;
name: string;
}) => {
try {
await revokeToken({
identityId: parentIdentityId,
tokenId
});
handlePopUpClose("revokeToken");
createNotification({
text: `Successfully revoked token ${name ?? ""}`,
type: "success"
});
} catch (err) {
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to delete identity";
createNotification({
text,
type: "error"
});
}
};
const onDeleteClientSecretSubmit = async ({ clientSecretId }: { clientSecretId: string }) => {
try {
if (!data?.identity.id || selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) return;
await revokeClientSecret({
identityId: data?.identity.id,
clientSecretId
});
handlePopUpToggle("revokeClientSecret", false);
createNotification({
text: "Successfully deleted client secret",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to delete client secret",
type: "error"
});
}
};
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title={data.identity.name} /> <PageHeader title={data.identity.name}>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<Button variant="outline_bg">More</Button>
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen("identity", {
identityId,
name: data.identity.name,
role: data.role,
customRole: data.customRole
});
}}
disabled={!isAllowed}
>
Edit Identity
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen("identityAuthMethod", {
identityId,
name: data.identity.name,
allAuthMethods: data.identity.authMethods
});
}}
disabled={!isAllowed}
>
Add new auth method
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Delete}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
isAllowed
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen("deleteIdentity", {
identityId,
name: data.identity.name
});
}}
disabled={!isAllowed}
>
Delete Identity
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</PageHeader>
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} /> <IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
<IdentityAuthenticationSection <IdentityAuthenticationSection
selectedAuthMethod={selectedAuthMethod}
setSelectedAuthMethod={setSelectedAuthMethod}
identityId={identityId} identityId={identityId}
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
/> />
@ -94,6 +260,18 @@ const Page = () => {
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
<IdentityTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<IdentityTokenListModal
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
<IdentityClientSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<IdentityUniversalAuthClientSecretModal
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
<UpgradePlanModal <UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen} isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
@ -112,11 +290,41 @@ const Page = () => {
) )
} }
/> />
<ViewIdentityAuthModal <DeleteActionModal
isOpen={popUp.viewAuthMethod.isOpen} isOpen={popUp.revokeToken.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)} title={`Are you sure want to revoke ${
authMethod={popUp.viewAuthMethod.data} (popUp?.revokeToken?.data as { name: string })?.name || ""
identityId={identityId} }?`}
onChange={(isOpen) => handlePopUpToggle("revokeToken", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => {
const revokeTokenData = popUp?.revokeToken?.data as {
identityId: string;
tokenId: string;
name: string;
};
return onRevokeTokenSubmit(revokeTokenData);
}}
/>
<DeleteActionModal
isOpen={popUp.revokeClientSecret.isOpen}
title={`Are you sure want to delete the client secret ${
(popUp?.revokeClientSecret?.data as { clientSecretPrefix: string })?.clientSecretPrefix ||
""
}************?`}
onChange={(isOpen) => handlePopUpToggle("revokeClientSecret", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => {
const deleteClientSecretData = popUp?.revokeClientSecret?.data as {
clientSecretId: string;
clientSecretPrefix: string;
};
return onDeleteClientSecretSubmit({
clientSecretId: deleteClientSecretData.clientSecretId
});
}}
/> />
</div> </div>
); );

View File

@ -1,56 +1,140 @@
import { faCog, faPlus } from "@fortawesome/free-solid-svg-icons"; import { useEffect } from "react";
import { faPencil, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2"; import { Button, IconButton, Select, SelectItem, Tooltip } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { IdentityAuthMethod, identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api"; import { useGetIdentityById } from "@app/hooks/api";
import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities";
import { Identity } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityClientSecrets } from "./IdentityClientSecrets";
import { IdentityTokens } from "./IdentityTokens";
type Props = { type Props = {
identityId: string; identityId: string;
setSelectedAuthMethod: (authMethod: Identity["authMethods"][number] | null) => void;
selectedAuthMethod: Identity["authMethods"][number] | null;
handlePopUpOpen: ( handlePopUpOpen: (
popUpName: keyof UsePopUpState<["identityAuthMethod", "viewAuthMethod"]>, popUpName: keyof UsePopUpState<
data?: object | IdentityAuthMethod [
"clientSecret",
"identityAuthMethod",
"revokeClientSecret",
"token",
"revokeToken",
"universalAuthClientSecret",
"tokenList"
]
>,
data?: object
) => void; ) => void;
}; };
export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: Props) => { export const IdentityAuthenticationSection = ({
identityId,
setSelectedAuthMethod,
selectedAuthMethod,
handlePopUpOpen
}: Props) => {
const { data } = useGetIdentityById(identityId); const { data } = useGetIdentityById(identityId);
useEffect(() => {
if (!data?.identity) return;
if (data.identity.authMethods?.length) {
setSelectedAuthMethod(data.identity.authMethods[0]);
}
// eslint-disable-next-line consistent-return
return () => setSelectedAuthMethod(null);
}, [data?.identity]);
return data ? ( return data ? (
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4"> <div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-semibold text-mineshaft-100">Authentication</h3> <h3 className="text-lg font-semibold text-mineshaft-100">Authentication</h3>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => {
return (
<Tooltip content="Add new auth method">
<IconButton
isDisabled={!isAllowed}
ariaLabel="copy icon"
variant="plain"
className="group relative"
onClick={() =>
handlePopUpOpen("identityAuthMethod", {
identityId,
name: data.identity.name,
allAuthMethods: data.identity.authMethods
})
}
>
<FontAwesomeIcon icon={faPlus} />
</IconButton>
</Tooltip>
);
}}
</OrgPermissionCan>
</div> </div>
{data.identity.authMethods.length > 0 ? ( {data.identity.authMethods.length > 0 ? (
<div className="flex flex-col divide-y divide-mineshaft-400/50"> <>
{data.identity.authMethods.map((authMethod) => ( <div className="py-4">
<button <div className="flex justify-between">
key={authMethod} <p className="mb-0.5 ml-px text-sm font-semibold text-mineshaft-300">Auth Method</p>
onClick={() => handlePopUpOpen("viewAuthMethod", authMethod)}
type="button"
className="flex w-full items-center justify-between bg-mineshaft-900 px-4 py-2 text-sm hover:bg-mineshaft-700 data-[state=open]:bg-mineshaft-600"
>
<span>{identityAuthToNameMap[authMethod]}</span>
<FontAwesomeIcon icon={faCog} size="xs" className="text-mineshaft-400" />
</button>
))}
</div> </div>
<div className="flex items-center gap-2">
<div className="w-full">
<Select
className="w-full"
value={selectedAuthMethod as string}
onValueChange={(value) => setSelectedAuthMethod(value as IdentityAuthMethod)}
>
{(data.identity?.authMethods || []).map((authMethod) => (
<SelectItem key={authMethod || authMethod} value={authMethod}>
{identityAuthToNameMap[authMethod]}
</SelectItem>
))}
</Select>
</div>
<div>
<Tooltip content="Edit auth method">
<IconButton
onClick={() => {
handlePopUpOpen("identityAuthMethod", {
identityId,
name: data.identity.name,
authMethod: selectedAuthMethod,
allAuthMethods: data.identity.authMethods
});
}}
ariaLabel="copy icon"
variant="plain"
className="group relative"
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
</Tooltip>{" "}
</div>
</div>
</div>
{selectedAuthMethod === IdentityAuthMethod.UNIVERSAL_AUTH && (
<IdentityClientSecrets identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
)}
{selectedAuthMethod === IdentityAuthMethod.TOKEN_AUTH && (
<IdentityTokens identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
)}
</>
) : ( ) : (
<div className="w-full space-y-2 pt-2"> <div className="w-full space-y-2 pt-2">
<p className="text-sm text-mineshaft-300"> <p className="text-sm text-mineshaft-300">
No authentication methods configured. Get started by creating a new auth method. No authentication methods configured. Get started by creating a new auth method.
</p> </p>
</div>
)}
{!Object.values(IdentityAuthMethod).every((method) =>
data.identity.authMethods.includes(method)
) && (
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button <Button
isDisabled={!isAllowed}
onClick={() => { onClick={() => {
handlePopUpOpen("identityAuthMethod", { handlePopUpOpen("identityAuthMethod", {
identityId, identityId,
@ -59,14 +143,12 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P
}); });
}} }}
variant="outline_bg" variant="outline_bg"
className="mt-3 w-full" className="w-full"
size="xs" size="xs"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
> >
{data.identity.authMethods.length ? "Add" : "Create"} Auth Method Create Auth Method
</Button> </Button>
)} </div>
</OrgPermissionCan>
)} )}
</div> </div>
) : ( ) : (

View File

@ -1,4 +1,4 @@
import { faEllipsisVertical, faKey } from "@fortawesome/free-solid-svg-icons"; import { faEllipsis, faKey } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns"; import { format } from "date-fns";
@ -8,7 +8,6 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
IconButton,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { useGetIdentityById, useGetIdentityTokensTokenAuth } from "@app/hooks/api"; import { useGetIdentityById, useGetIdentityTokensTokenAuth } from "@app/hooks/api";
@ -28,13 +27,10 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
return ( return (
<div> <div>
{tokens?.length ? ( {tokens?.length ? (
<div className="flex items-center justify-between border-b border-bunker-400 pb-1"> <div className="flex justify-between">
<p className="text-sm font-medium text-bunker-300">{`Access Tokens (${tokens.length})`}</p> <p className="text-sm font-semibold text-mineshaft-300">{`Access Tokens (${tokens.length})`}</p>
<Button <Button
size="xs" variant="link"
className="underline"
variant="plain"
colorSchema="secondary"
onClick={() => { onClick={() => {
handlePopUpOpen("tokenList", { handlePopUpOpen("tokenList", {
identityId, identityId,
@ -54,16 +50,16 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
); );
return ( return (
<div <div
className="group flex items-center justify-between border-b border-mineshaft-500 px-2 py-2 last:pb-0" className="group flex items-center justify-between py-2 last:pb-0"
key={`identity-token-${token.id}`} key={`identity-token-${token.id}`}
> >
<div className="flex items-center"> <div className="flex items-center">
<FontAwesomeIcon size="xs" className="text-mineshaft-400" icon={faKey} /> <FontAwesomeIcon size="1x" icon={faKey} />
<div className="ml-3"> <div className="ml-4">
<p className="text-sm font-medium text-mineshaft-300"> <p className="text-sm font-semibold text-mineshaft-300">
{token.name ? token.name : "-"} {token.name ? token.name : "-"}
</p> </p>
<p className="text-xs text-mineshaft-400"> <p className="text-sm text-mineshaft-300">
{token.isAccessTokenRevoked {token.isAccessTokenRevoked
? "Revoked" ? "Revoked"
: `Expires on ${format(expiresAt, "yyyy-MM-dd")}`} : `Expires on ${format(expiresAt, "yyyy-MM-dd")}`}
@ -71,19 +67,14 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
</div> </div>
</div> </div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild className="rounded-lg">
<Tooltip side="right" content="More options"> <div className="opacity-0 transition-opacity duration-300 hover:text-primary-400 group-hover:opacity-100 data-[state=open]:text-primary-400">
<IconButton <Tooltip content="More options">
colorSchema="secondary" <FontAwesomeIcon size="sm" icon={faEllipsis} />
variant="plain"
size="xs"
ariaLabel="More options"
>
<FontAwesomeIcon icon={faEllipsisVertical} />
</IconButton>
</Tooltip> </Tooltip>
</div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="z-[101] p-1"> <DropdownMenuContent align="start" className="p-1">
<DropdownMenuItem <DropdownMenuItem
onClick={async () => { onClick={async () => {
handlePopUpOpen("token", { handlePopUpOpen("token", {
@ -115,9 +106,8 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
})} })}
<Button <Button
className="mr-4 mt-4 w-full" className="mr-4 mt-4 w-full"
colorSchema="secondary" colorSchema="primary"
type="submit" type="submit"
size="xs"
onClick={() => { onClick={() => {
handlePopUpOpen("token", { handlePopUpOpen("token", {
identityId identityId

View File

@ -1,25 +1,8 @@
import { import { faCheck, faCopy, faKey, faPencil } from "@fortawesome/free-solid-svg-icons";
faCheck,
faChevronDown,
faCopy,
faEdit,
faKey,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { import { IconButton, Tag, Tooltip } from "@app/components/v2";
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
IconButton,
Tag,
Tooltip
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { useTimedReset } from "@app/hooks"; import { useTimedReset } from "@app/hooks";
import { useGetIdentityById } from "@app/hooks/api"; import { useGetIdentityById } from "@app/hooks/api";
@ -28,7 +11,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = { type Props = {
identityId: string; identityId: string;
handlePopUpOpen: ( handlePopUpOpen: (
popUpName: keyof UsePopUpState<["identity", "identityAuthMethod", "deleteIdentity"]>, popUpName: keyof UsePopUpState<["identity", "identityAuthMethod", "token", "clientSecret"]>,
data?: object data?: object
) => void; ) => void;
}; };
@ -43,61 +26,31 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4"> <div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-semibold text-mineshaft-100">Identity Details</h3> <h3 className="text-lg font-semibold text-mineshaft-100">Identity Details</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="xs"
rightIcon={<FontAwesomeIcon className="ml-1" icon={faChevronDown} />}
colorSchema="secondary"
>
Options
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-[120px]" align="end">
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}> <OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => ( {(isAllowed) => {
<DropdownMenuItem return (
className={twMerge( <Tooltip content="Edit Identity">
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50" <IconButton
)} isDisabled={!isAllowed}
icon={<FontAwesomeIcon icon={faEdit} />} ariaLabel="copy icon"
onClick={async () => { variant="plain"
className="group relative"
onClick={() => {
handlePopUpOpen("identity", { handlePopUpOpen("identity", {
identityId, identityId,
name: data.identity.name, name: data.identity.name,
role: data.role, role: data.role,
customRole: data.customRole customRole: data.customRole,
metadata: data.metadata
}); });
}} }}
disabled={!isAllowed}
> >
Edit Identity <FontAwesomeIcon icon={faPencil} />
</DropdownMenuItem> </IconButton>
)} </Tooltip>
</OrgPermissionCan> );
<OrgPermissionCan I={OrgPermissionActions.Delete} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
isAllowed
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen("deleteIdentity", {
identityId,
name: data.identity.name
});
}} }}
icon={<FontAwesomeIcon icon={faTrash} />}
disabled={!isAllowed}
>
Delete Identity
</DropdownMenuItem>
)}
</OrgPermissionCan> </OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
<div className="pt-4"> <div className="pt-4">
<div className="mb-4"> <div className="mb-4">

View File

@ -0,0 +1,270 @@
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { faCheck, faCopy, faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
EmptyState,
FormControl,
IconButton,
Input,
Modal,
ModalContent,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { useToggle } from "@app/hooks";
import {
useCreateTokenIdentityTokenAuth,
useGetIdentityTokensTokenAuth,
useGetIdentityUniversalAuthClientSecrets
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = z.object({
name: z.string()
});
export type FormData = z.infer<typeof schema>;
type Props = {
popUp: UsePopUpState<["tokenList", "revokeToken"]>;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["revokeToken"]>, data?: object) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["tokenList", "revokeToken"]>,
state?: boolean
) => void;
};
export const IdentityTokenListModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => {
const { t } = useTranslation();
const [token, setToken] = useState("");
const [isClientSecretCopied, setIsClientSecretCopied] = useToggle(false);
const [isClientIdCopied, setIsClientIdCopied] = useToggle(false);
const popUpData = popUp?.tokenList?.data as {
identityId: string;
name: string;
};
const { data: tokens } = useGetIdentityTokensTokenAuth(popUpData?.identityId ?? "");
const { data, isPending } = useGetIdentityUniversalAuthClientSecrets(popUpData?.identityId ?? "");
const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth();
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
name: ""
}
});
useEffect(() => {
let timer: NodeJS.Timeout;
if (isClientSecretCopied) {
timer = setTimeout(() => setIsClientSecretCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isClientSecretCopied]);
useEffect(() => {
let timer: NodeJS.Timeout;
if (isClientIdCopied) {
timer = setTimeout(() => setIsClientIdCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isClientIdCopied]);
const onFormSubmit = async ({ name }: FormData) => {
try {
if (!popUpData?.identityId) return;
const newTokenData = await createToken({
identityId: popUpData.identityId,
name
});
setToken(newTokenData.accessToken);
createNotification({
text: "Successfully created token",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to create token",
type: "error"
});
}
};
const hasToken = Boolean(token);
return (
<Modal
isOpen={popUp?.tokenList?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("tokenList", isOpen);
reset();
setToken("");
}}
>
<ModalContent title={`Manage Access Tokens for ${popUpData?.name ?? ""}`}>
<h2 className="mb-4">New Token</h2>
{hasToken ? (
<div>
<div className="mb-4 flex items-center justify-between">
<p>We will only show this token once</p>
<Button
colorSchema="secondary"
type="submit"
onClick={() => {
reset();
setToken("");
}}
>
Got it
</Button>
</div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{token}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(token);
setIsClientSecretCopied.on();
}}
>
<FontAwesomeIcon icon={isClientSecretCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
{t("common.click-to-copy")}
</span>
</IconButton>
</div>
</div>
) : (
<form onSubmit={handleSubmit(onFormSubmit)} className="mb-8">
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
<div className="flex">
<Input {...field} placeholder="My Token" />
<Button
className="ml-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Create
</Button>
</div>
</FormControl>
)}
/>
</form>
)}
<h2 className="mb-4">Tokens</h2>
<TableContainer>
<Table>
<THead>
<Tr>
<Th>name</Th>
<Th>Num Uses</Th>
<Th>Created At</Th>
<Th>Max Expires At</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={5} innerKey="identities-tokens" />}
{!isPending &&
tokens?.map(
({
id,
createdAt,
name,
accessTokenNumUses,
accessTokenNumUsesLimit,
accessTokenMaxTTL,
isAccessTokenRevoked
}) => {
const expiresAt = new Date(
new Date(createdAt).getTime() + accessTokenMaxTTL * 1000
);
return (
<Tr className="h-10 items-center" key={`mi-client-secret-${id}`}>
<Td>{name === "" ? "-" : name}</Td>
<Td>{`${accessTokenNumUses}${
accessTokenNumUsesLimit ? `/${accessTokenNumUsesLimit}` : ""
}`}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td>
{isAccessTokenRevoked ? "Revoked" : `${format(expiresAt, "yyyy-MM-dd")}`}
</Td>
<Td>
{!isAccessTokenRevoked && (
<IconButton
onClick={() => {
handlePopUpOpen("revokeToken", {
identityId: popUpData?.identityId,
tokenId: id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</Td>
</Tr>
);
}
)}
{!isPending && data && data?.length === 0 && (
<Tr>
<Td colSpan={5}>
<EmptyState
title="No tokens have been created for this identity yet"
icon={faKey}
/>
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
</ModalContent>
</Modal>
);
};

View File

@ -1,20 +0,0 @@
import { ReactNode } from "react";
type Props = {
label: string;
children: ReactNode;
className?: string;
};
export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => {
return (
<div className={className}>
<span className="text-sm text-mineshaft-400">{label}</span>
{children ? (
<p className="break-words text-base leading-4">{children}</p>
) : (
<p className="text-base italic leading-4 text-bunker-400">Not set</p>
)}
</div>
);
};

View File

@ -1,239 +0,0 @@
import { useState } from "react";
import { faBan, faEdit, faKey, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
Pagination,
Table,
TableContainer,
TBody,
Td,
Th,
THead,
Tooltip,
Tr
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useRevokeIdentityTokenAuthToken } from "@app/hooks/api";
import { IdentityAccessToken } from "@app/hooks/api/identities/types";
import { IdentityTokenModal } from "@app/pages/organization/IdentityDetailsByIDPage/components";
type Props = {
tokens: IdentityAccessToken[];
identityId: string;
};
export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"token",
"revokeToken"
] as const);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(5);
const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken();
const onRevokeTokenSubmit = async ({
identityId: parentIdentityId,
tokenId,
name
}: {
identityId: string;
tokenId: string;
name: string;
}) => {
try {
await revokeToken({
identityId: parentIdentityId,
tokenId
});
handlePopUpClose("revokeToken");
createNotification({
text: `Successfully revoked token ${name ?? ""}`,
type: "success"
});
} catch (err) {
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to revoke token";
createNotification({
text,
type: "error"
});
}
};
return (
<div className="col-span-2 mt-3">
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Access Tokens</span>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button
size="xs"
isDisabled={!isAllowed}
onClick={() => {
handlePopUpOpen("token", {
identityId
});
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
colorSchema="secondary"
>
Add Token
</Button>
)}
</OrgPermissionCan>
</div>
<TableContainer className="mt-4 rounded-none border-none">
<Table>
{Boolean(tokens?.length) && (
<THead>
<Tr className="text-xs font-medium">
<Th className="py-1 font-normal">Name</Th>
<Th className="whitespace-nowrap py-1 font-normal">Number of Uses</Th>
<Th className="py-1 font-normal">Expires</Th>
<Th className="w-5 py-1 font-normal" />
</Tr>
</THead>
)}
<TBody>
{tokens
.slice((page - 1) * perPage, perPage * page)
.map(
({
createdAt,
isAccessTokenRevoked,
name,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenNumUses,
id
}) => {
let expiresAt;
if (accessTokenTTL > 0) {
expiresAt = new Date(new Date(createdAt).getTime() + accessTokenTTL * 1000);
}
return (
<Tr className="text-xs hover:bg-mineshaft-700" key={id}>
<Td>{name || "-"}</Td>
<Td>
{`${accessTokenNumUses}${accessTokenNumUsesLimit ? `/${accessTokenNumUsesLimit}` : ""}`}
</Td>
<Td className="whitespace-nowrap">
{/* eslint-disable-next-line no-nested-ternary */}
{isAccessTokenRevoked
? "Revoked"
: expiresAt
? format(expiresAt, "yyyy-MM-dd")
: "-"}
</Td>
<Td>
<div className="flex items-center gap-2">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Edit Token" : "Access Restricted"}>
<IconButton
isDisabled={!isAllowed}
onClick={() => {
handlePopUpOpen("token", {
identityId,
tokenId: id,
name
});
}}
size="xs"
variant="plain"
ariaLabel="Edit token"
>
<FontAwesomeIcon icon={faEdit} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
{!isAccessTokenRevoked && (
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Revoke Token" : "Access Restricted"}>
<IconButton
isDisabled={!isAllowed}
onClick={() => {
handlePopUpOpen("revokeToken", {
identityId,
tokenId: id,
name
});
}}
size="xs"
colorSchema="danger"
variant="plain"
ariaLabel="Revoke token"
>
<FontAwesomeIcon icon={faBan} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
)}
</div>
</Td>
</Tr>
);
}
)}
</TBody>
</Table>
{!tokens?.length && (
<EmptyState iconSize="1x" title="No access tokens have been generated" icon={faKey} />
)}
{tokens.length > 0 && (
<Pagination
count={tokens.length}
page={page}
perPage={perPage}
perPageList={[5]}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
</TableContainer>
<DeleteActionModal
isOpen={popUp.revokeToken.isOpen}
title={`Are you sure want to revoke ${
(popUp?.revokeToken?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle("revokeToken", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => {
const revokeTokenData = popUp?.revokeToken?.data as {
identityId: string;
tokenId: string;
name: string;
};
return onRevokeTokenSubmit(revokeTokenData);
}}
/>
<IdentityTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</div>
);
};

View File

@ -1,196 +0,0 @@
import { useState } from "react";
import { faKey, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
Pagination,
Table,
TableContainer,
TBody,
Td,
Th,
THead,
Tooltip,
Tr
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useRevokeIdentityUniversalAuthClientSecret } from "@app/hooks/api";
import { ClientSecretData } from "@app/hooks/api/identities/types";
import { IdentityClientSecretModal } from "@app/pages/organization/IdentityDetailsByIDPage/components";
type Props = {
clientSecrets: ClientSecretData[];
identityId: string;
};
export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identityId }: Props) => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"revokeClientSecret",
"clientSecret"
] as const);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(5);
const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret();
const onDeleteClientSecretSubmit = async (clientSecretId: string) => {
try {
await revokeClientSecret({
identityId,
clientSecretId
});
handlePopUpToggle("revokeClientSecret", false);
createNotification({
text: "Successfully deleted client secret",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to delete client secret",
type: "error"
});
}
};
return (
<div className="col-span-2">
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Client Secrets</span>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
size="xs"
onClick={() => {
handlePopUpOpen("clientSecret", {
identityId
});
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
colorSchema="secondary"
>
Add Client Secret
</Button>
)}
</OrgPermissionCan>
</div>
<TableContainer className="mt-4 rounded-none border-none">
<Table>
{Boolean(clientSecrets?.length) && (
<THead>
<Tr className="text-xs font-medium">
<Th className="py-1 font-normal">Secret</Th>
<Th className="py-1 font-normal">Description</Th>
<Th className="whitespace-nowrap py-1 font-normal">Number of Uses</Th>
<Th className="py-1 font-normal">Expires</Th>
<Th className="w-5 py-1 font-normal" />
</Tr>
</THead>
)}
<TBody>
{clientSecrets
.slice((page - 1) * perPage, perPage * page)
.map(
({
createdAt,
clientSecretTTL,
description,
clientSecretNumUses,
clientSecretPrefix,
clientSecretNumUsesLimit,
id
}) => {
let expiresAt;
if (clientSecretTTL > 0) {
expiresAt = new Date(new Date(createdAt).getTime() + clientSecretTTL * 1000);
}
return (
<Tr className="text-xs hover:bg-mineshaft-700" key={id}>
<Td>{clientSecretPrefix}***</Td>
<Td>{description || "-"}</Td>
<Td>
{`${clientSecretNumUses}${clientSecretNumUsesLimit ? `/${clientSecretNumUsesLimit}` : ""}`}
</Td>
<Td className="whitespace-nowrap">
{expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"}
</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Delete Secret" : "Access Restricted"}>
<IconButton
isDisabled={!isAllowed}
onClick={() => {
handlePopUpOpen("revokeClientSecret", {
clientSecretPrefix,
clientSecretId: id
});
}}
size="xs"
colorSchema="danger"
variant="plain"
ariaLabel="Delete secret"
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</Td>
</Tr>
);
}
)}
</TBody>
</Table>
{!clientSecrets?.length && (
<EmptyState iconSize="1x" title="No client secrets have been generated" icon={faKey} />
)}
{clientSecrets.length > 0 && (
<Pagination
count={clientSecrets.length}
page={page}
perPage={perPage}
perPageList={[5]}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
</TableContainer>
<DeleteActionModal
isOpen={popUp.revokeClientSecret.isOpen}
title={`Are you sure want to delete the client secret ${
(popUp?.revokeClientSecret?.data as { clientSecretPrefix: string })?.clientSecretPrefix ||
""
}************?`}
onChange={(isOpen) => handlePopUpToggle("revokeClientSecret", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => {
const deleteClientSecretData = popUp?.revokeClientSecret?.data as {
clientSecretId: string;
clientSecretPrefix: string;
};
return onDeleteClientSecretSubmit(deleteClientSecretData.clientSecretId);
}}
/>
<IdentityClientSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</div>
);
};

View File

@ -1,174 +0,0 @@
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, Modal, ModalContent } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks";
import {
IdentityAuthMethod,
identityAuthToNameMap,
useDeleteIdentityAwsAuth,
useDeleteIdentityAzureAuth,
useDeleteIdentityGcpAuth,
useDeleteIdentityJwtAuth,
useDeleteIdentityKubernetesAuth,
useDeleteIdentityOidcAuth,
useDeleteIdentityTokenAuth,
useDeleteIdentityUniversalAuth
} from "@app/hooks/api";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent";
import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
type Props = {
identityId: string;
authMethod?: IdentityAuthMethod;
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
onDeleteAuthMethod: () => void;
};
type TRevokeOptions = {
identityId: string;
organizationId: string;
};
export const Content = ({
identityId,
authMethod,
onDeleteAuthMethod
}: Pick<Props, "authMethod" | "identityId" | "onDeleteAuthMethod">) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"revokeAuthMethod",
"upgradePlan",
"identityAuthMethod"
] as const);
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
let Component: (props: ViewAuthMethodProps) => JSX.Element;
let revokeMethod: (revokeOptions: TRevokeOptions) => Promise<any>;
const handleDelete = () => handlePopUpOpen("revokeAuthMethod");
switch (authMethod) {
case IdentityAuthMethod.UNIVERSAL_AUTH:
revokeMethod = revokeUniversalAuth;
Component = ViewIdentityUniversalAuthContent;
break;
case IdentityAuthMethod.TOKEN_AUTH:
revokeMethod = revokeTokenAuth;
Component = ViewIdentityTokenAuthContent;
break;
case IdentityAuthMethod.KUBERNETES_AUTH:
revokeMethod = revokeKubernetesAuth;
Component = ViewIdentityKubernetesAuthContent;
break;
case IdentityAuthMethod.GCP_AUTH:
revokeMethod = revokeGcpAuth;
Component = ViewIdentityGcpAuthContent;
break;
case IdentityAuthMethod.AWS_AUTH:
revokeMethod = revokeAwsAuth;
Component = ViewIdentityAwsAuthContent;
break;
case IdentityAuthMethod.AZURE_AUTH:
revokeMethod = revokeAzureAuth;
Component = ViewIdentityAzureAuthContent;
break;
case IdentityAuthMethod.OIDC_AUTH:
revokeMethod = revokeOidcAuth;
Component = ViewIdentityOidcAuthContent;
break;
case IdentityAuthMethod.JWT_AUTH:
revokeMethod = revokeJwtAuth;
Component = ViewIdentityJwtAuthContent;
break;
default:
throw new Error(`Unhandled Auth Method: ${authMethod}`);
}
const handleDeleteAuthMethod = async () => {
try {
await revokeMethod({
identityId,
organizationId: orgId
});
createNotification({
text: "Successfully removed auth method",
type: "success"
});
handlePopUpToggle("revokeAuthMethod", false);
onDeleteAuthMethod();
} catch {
createNotification({
text: "Failed to remove auth method",
type: "error"
});
}
};
return (
<>
<Component
identityId={identityId}
onDelete={handleDelete}
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
<DeleteActionModal
isOpen={popUp?.revokeAuthMethod?.isOpen}
title={`Are you sure want to remove ${identityAuthToNameMap[authMethod]} on this identity?`}
onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
deleteKey="confirm"
buttonText="Remove"
onDeleteApproved={handleDeleteAuthMethod}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={(popUp.upgradePlan?.data as { description: string })?.description}
/>
</>
);
};
export const ViewIdentityAuthModal = ({
isOpen,
onOpenChange,
authMethod,
identityId
}: Omit<Props, "onDeleteAuthMethod">) => {
if (!identityId || !authMethod) return null;
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent className="max-w-2xl" title={identityAuthToNameMap[authMethod]}>
<Content
identityId={identityId}
authMethod={authMethod}
onDeleteAuthMethod={() => onOpenChange(false)}
/>
</ModalContent>
</Modal>
);
};

View File

@ -1,79 +0,0 @@
import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityAwsAuth } from "@app/hooks/api";
import { IdentityAwsAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityAwsAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityAwsAuth(identityId);
if (isPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState icon={faBan} title="Could not find AWS Auth associated with this Identity." />
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityAwsAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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">
{data.allowedPrincipalArns
?.split(",")
.map((arn) => arn.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Account IDs">
{data.allowedAccountIds
?.split(",")
.map((id) => id.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="STS Endpoint">
{data.stsEndpoint}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper>
);
};

View File

@ -1,76 +0,0 @@
import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityAzureAuth } from "@app/hooks/api";
import { IdentityAzureAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityAzureAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityAzureAuth(identityId);
if (isPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState icon={faBan} title="Could not find Azure Auth associated with this Identity." />
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityAzureAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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="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>
);
};

View File

@ -1,69 +0,0 @@
import { ReactNode } from "react";
import { faChevronDown, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
type Props = {
children: ReactNode;
onEdit: VoidFunction;
onDelete: VoidFunction;
};
export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props) => {
return (
<div className="flex flex-col gap-4">
<div>
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Details</span>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="xs"
rightIcon={<FontAwesomeIcon className="ml-1" icon={faChevronDown} />}
colorSchema="secondary"
>
Options
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-[120px]" align="end">
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<DropdownMenuItem
isDisabled={!isAllowed}
onClick={onEdit}
icon={<FontAwesomeIcon icon={faEdit} />}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<DropdownMenuItem
isDisabled={!isAllowed}
onClick={onDelete}
icon={<FontAwesomeIcon icon={faTrash} />}
>
Delete
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3">{children}</div>
</div>
</div>
);
};

View File

@ -1,89 +0,0 @@
import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityGcpAuth } from "@app/hooks/api";
import { IdentityGcpAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityGcpAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityGcpAuth(identityId);
if (isPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState icon={faBan} title="Could not find GCP Auth associated with this Identity." />
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityGcpAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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">
{data.type === "gce" ? "GCP ID Token Auth" : "GCP IAM Auth"}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Service Account Emails">
{data.allowedServiceAccounts
?.split(",")
.map((account) => account.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
{data.type === "gce" && (
<>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Projects">
{data.allowedProjects
?.split(",")
.map((project) => project.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Zones">
{data.allowedZones
?.split(",")
.map((zone) => zone.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
</>
)}
</ViewIdentityContentWrapper>
);
};

View File

@ -1,152 +0,0 @@
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { useGetIdentityJwtAuth } from "@app/hooks/api";
import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums";
import { IdentityJwtAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm";
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types";
export const ViewIdentityJwtAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityJwtAuth(identityId);
if (isPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState icon={faBan} title="Could not find JWT Auth associated with this Identity." />
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityJwtAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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">
{data.configurationType === IdentityJwtConfigurationType.JWKS ? "JWKS" : "Static"}
</IdentityAuthFieldDisplay>
{data.configurationType === IdentityJwtConfigurationType.JWKS ? (
<>
<IdentityAuthFieldDisplay className="col-span-2" label="JWKS URL">
{data.jwksUrl}
</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">
{data.publicKeys.length && (
<div className="flex flex-wrap gap-1">
{data.publicKeys.map((key, index) => (
<Tooltip
side="right"
className="max-w-xl p-2"
key={key}
content={
<p className="whitespace-normal break-words rounded bg-mineshaft-600 p-2">
{key}
</p>
}
>
<div className="inline-block 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>Key {index + 1}</span>
</Badge>
</div>
</Tooltip>
))}
</div>
)}
</IdentityAuthFieldDisplay>
)}
<IdentityAuthFieldDisplay className="col-span-2" label="Issuer">
{data.boundIssuer}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Subject">
{data.boundSubject}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Audiences">
{data.boundAudiences
?.split(",")
.map((name) => name.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Claims">
{Object.keys(data.boundClaims).length && (
<Tooltip
side="right"
className="max-w-xl p-2"
content={
<pre className="whitespace-pre-wrap rounded bg-mineshaft-600 p-2">
{JSON.stringify(data.boundClaims, null, 2)}
</pre>
}
>
<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>
</ViewIdentityContentWrapper>
);
};

View File

@ -1,121 +0,0 @@
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { useGetIdentityKubernetesAuth } from "@app/hooks/api";
import { IdentityKubernetesAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityKubernetesAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityKubernetesAuth(identityId);
if (isPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState
icon={faBan}
title="Could not find Kubernetes Auth associated with this Identity."
/>
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityKubernetesAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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="Kubernetes Host / Base Kubernetes API URL"
>
{data.kubernetesHost}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Token Reviewer JWT">
<Tooltip
side="right"
className="max-w-xl p-2"
content={
<p className="break-words rounded bg-mineshaft-600 p-2">{data.tokenReviewerJwt}</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="Allowed Service Account Names">
{data.allowedNames
?.split(",")
.map((name) => name.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Namespaces">
{data.allowedNamespaces
?.split(",")
.map((namespace) => namespace.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Audience">
{data.allowedAudience}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="CA Certificate">
{data.caCert && (
<Tooltip
side="right"
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">
<FontAwesomeIcon icon={faEye} />
<span>Reveal</span>
</Badge>
</div>
</Tooltip>
)}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper>
);
};

View File

@ -1,116 +0,0 @@
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { useGetIdentityOidcAuth } from "@app/hooks/api";
import { IdentityOidcAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm";
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types";
export const ViewIdentityOidcAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityOidcAuth(identityId);
if (isPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState icon={faBan} title="Could not find OIDC Auth associated with this Identity." />
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityOidcAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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">
{data.oidcDiscoveryUrl}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Issuer">
{data.boundIssuer}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="CA Certificate">
{data.caCert && (
<Tooltip
side="right"
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">
<FontAwesomeIcon icon={faEye} />
<span>Reveal</span>
</Badge>
</div>
</Tooltip>
)}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Subject">
{data.boundSubject}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Audiences">
{data.boundAudiences
?.split(",")
.map((name) => name.trim())
.join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay className="col-span-2" label="Claims">
{Object.keys(data.boundClaims).length && (
<Tooltip
side="right"
className="max-w-xl p-2"
content={
<pre className="whitespace-pre-wrap rounded bg-mineshaft-600 p-2">
{JSON.stringify(data.boundClaims, null, 2)}
</pre>
}
>
<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>
</ViewIdentityContentWrapper>
);
};

View File

@ -1,68 +0,0 @@
import { faBan } from "@fortawesome/free-solid-svg-icons";
import { EmptyState, Spinner } from "@app/components/v2";
import { useGetIdentityTokenAuth, useGetIdentityTokensTokenAuth } from "@app/hooks/api";
import { IdentityTokenAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { IdentityTokenAuthTokensTable } from "./IdentityTokenAuthTokensTable";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityTokenAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityTokenAuth(identityId);
const { data: tokens = [], isPending: clientSecretsPending } =
useGetIdentityTokensTokenAuth(identityId);
if (isPending || clientSecretsPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState icon={faBan} title="Could not find Token Auth associated with this Identity." />
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityTokenAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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>
<IdentityTokenAuthTokensTable tokens={tokens} identityId={identityId} />
</ViewIdentityContentWrapper>
);
};

View File

@ -1,106 +0,0 @@
import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
import { useTimedReset } from "@app/hooks";
import {
useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets
} from "@app/hooks/api";
import { IdentityUniversalAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { IdentityUniversalAuthClientSecretsTable } from "./IdentityUniversalAuthClientSecretsTable";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
export const ViewIdentityUniversalAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityUniversalAuth(identityId);
const { data: clientSecrets = [], isPending: clientSecretsPending } =
useGetIdentityUniversalAuthClientSecrets(identityId);
const [copyTextClientId, isCopyingClientId, setCopyTextClientId] = useTimedReset<string>({
initialState: "Copy Client ID to clipboard"
});
if (isPending || clientSecretsPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState
icon={faBan}
title="Could not find Universal Auth associated with this Identity."
/>
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityUniversalAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<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="Client Secret Trusted IPs">
{data.clientSecretTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<div className="col-span-2 my-3">
<div className="mb-3 border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Client ID</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm">{data.clientId}</span>
<Tooltip content={copyTextClientId}>
<IconButton
ariaLabel="copy icon"
variant="plain"
onClick={() => {
navigator.clipboard.writeText(data.clientId);
setCopyTextClientId("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingClientId ? faCheck : faCopy} />
</IconButton>
</Tooltip>
</div>
</div>
<IdentityUniversalAuthClientSecretsTable
clientSecrets={clientSecrets}
identityId={identityId}
/>
</ViewIdentityContentWrapper>
);
};

View File

@ -1 +0,0 @@
export * from "./ViewIdentityAuthModal";

View File

@ -1,12 +0,0 @@
import { UsePopUpState } from "@app/hooks/usePopUp";
export type ViewAuthMethodProps = {
identityId: string;
onDelete: () => void;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan", "identityAuthMethod"]>) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean
) => void;
popUp: UsePopUpState<["revokeAuthMethod", "upgradePlan", "identityAuthMethod"]>;
};

View File

@ -2,4 +2,5 @@ export { IdentityAuthenticationSection } from "./IdentityAuthenticationSection/I
export { IdentityClientSecretModal } from "./IdentityClientSecretModal"; export { IdentityClientSecretModal } from "./IdentityClientSecretModal";
export { IdentityDetailsSection } from "./IdentityDetailsSection"; export { IdentityDetailsSection } from "./IdentityDetailsSection";
export { IdentityProjectsSection } from "./IdentityProjectsSection/IdentityProjectsSection"; export { IdentityProjectsSection } from "./IdentityProjectsSection/IdentityProjectsSection";
export { IdentityTokenListModal } from "./IdentityTokenListModal";
export { IdentityTokenModal } from "./IdentityTokenModal"; export { IdentityTokenModal } from "./IdentityTokenModal";