mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 01:27:41 +00:00
improvement: overhaul identity auth ui section
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
+2
-2
@@ -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", "revokeAuthMethod"]>;
|
popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>;
|
||||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
||||||
handlePopUpToggle: (
|
handlePopUpToggle: (
|
||||||
popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-88
@@ -4,30 +4,8 @@ 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 { createNotification } from "@app/components/notifications";
|
import { Badge, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2";
|
||||||
import {
|
import { IdentityAuthMethod } from "@app/hooks/api/identities";
|
||||||
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";
|
||||||
@@ -40,10 +18,10 @@ import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm";
|
|||||||
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
|
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>;
|
popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>;
|
||||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
||||||
handlePopUpToggle: (
|
handlePopUpToggle: (
|
||||||
popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
@@ -56,13 +34,7 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,18 +68,6 @@ 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 () => {
|
||||||
@@ -149,7 +109,6 @@ 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
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -160,7 +119,6 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[IdentityAuthMethod.OIDC_AUTH]: {
|
[IdentityAuthMethod.OIDC_AUTH]: {
|
||||||
revokeMethod: revokeOidcAuth,
|
|
||||||
render: () => (
|
render: () => (
|
||||||
<IdentityOidcAuthForm
|
<IdentityOidcAuthForm
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -171,7 +129,6 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[IdentityAuthMethod.TOKEN_AUTH]: {
|
[IdentityAuthMethod.TOKEN_AUTH]: {
|
||||||
revokeMethod: revokeTokenAuth,
|
|
||||||
render: () => (
|
render: () => (
|
||||||
<IdentityTokenAuthForm
|
<IdentityTokenAuthForm
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -182,7 +139,6 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[IdentityAuthMethod.AZURE_AUTH]: {
|
[IdentityAuthMethod.AZURE_AUTH]: {
|
||||||
revokeMethod: revokeAzureAuth,
|
|
||||||
render: () => (
|
render: () => (
|
||||||
<IdentityAzureAuthForm
|
<IdentityAzureAuthForm
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -193,7 +149,6 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[IdentityAuthMethod.GCP_AUTH]: {
|
[IdentityAuthMethod.GCP_AUTH]: {
|
||||||
revokeMethod: revokeGcpAuth,
|
|
||||||
render: () => (
|
render: () => (
|
||||||
<IdentityGcpAuthForm
|
<IdentityGcpAuthForm
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -204,7 +159,6 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[IdentityAuthMethod.KUBERNETES_AUTH]: {
|
[IdentityAuthMethod.KUBERNETES_AUTH]: {
|
||||||
revokeMethod: revokeKubernetesAuth,
|
|
||||||
render: () => (
|
render: () => (
|
||||||
<IdentityKubernetesAuthForm
|
<IdentityKubernetesAuthForm
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -215,7 +169,6 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[IdentityAuthMethod.AWS_AUTH]: {
|
[IdentityAuthMethod.AWS_AUTH]: {
|
||||||
revokeMethod: revokeAwsAuth,
|
|
||||||
render: () => (
|
render: () => (
|
||||||
<IdentityAwsAuthForm
|
<IdentityAwsAuthForm
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -226,7 +179,6 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[IdentityAuthMethod.JWT_AUTH]: {
|
[IdentityAuthMethod.JWT_AUTH]: {
|
||||||
revokeMethod: revokeJwtAuth,
|
|
||||||
render: () => (
|
render: () => (
|
||||||
<IdentityJwtAuthForm
|
<IdentityJwtAuthForm
|
||||||
identityAuthMethodData={identityAuthMethodData}
|
identityAuthMethodData={identityAuthMethodData}
|
||||||
@@ -294,42 +246,6 @@ 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"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-12
@@ -49,7 +49,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData: {
|
identityAuthMethodData: {
|
||||||
@@ -360,17 +360,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-12
@@ -44,7 +44,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData: {
|
identityAuthMethodData: {
|
||||||
@@ -356,17 +356,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-12
@@ -45,7 +45,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData: {
|
identityAuthMethodData: {
|
||||||
@@ -389,17 +389,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-12
@@ -85,7 +85,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData: {
|
identityAuthMethodData: {
|
||||||
@@ -671,17 +671,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-12
@@ -47,7 +47,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData: {
|
identityAuthMethodData: {
|
||||||
@@ -429,17 +429,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-12
@@ -48,7 +48,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData: {
|
identityAuthMethodData: {
|
||||||
@@ -510,17 +510,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-12
@@ -39,7 +39,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData?: {
|
identityAuthMethodData?: {
|
||||||
@@ -268,17 +268,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
-344
@@ -1,344 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Controller, useForm } from "react-hook-form";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { faCheck, faCopy, faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { format } from "date-fns";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
// DeleteActionModal,
|
|
||||||
EmptyState,
|
|
||||||
FormControl,
|
|
||||||
IconButton,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalContent,
|
|
||||||
Table,
|
|
||||||
TableContainer,
|
|
||||||
TableSkeleton,
|
|
||||||
TBody,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
THead,
|
|
||||||
Tr
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
import {
|
|
||||||
useCreateIdentityUniversalAuthClientSecret,
|
|
||||||
useGetIdentityUniversalAuth,
|
|
||||||
useGetIdentityUniversalAuthClientSecrets
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
description: z.string(),
|
|
||||||
ttl: z
|
|
||||||
.string()
|
|
||||||
.refine((value) => Number(value) <= 315360000, "TTL cannot be greater than 315360000"),
|
|
||||||
numUsesLimit: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type FormData = z.infer<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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+1
-12
@@ -52,7 +52,7 @@ 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", "revokeAuthMethod"]>,
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
identityAuthMethodData?: {
|
identityAuthMethodData?: {
|
||||||
@@ -393,17 +393,6 @@ 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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
+18
-134
@@ -1,4 +1,3 @@
|
|||||||
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";
|
||||||
@@ -19,27 +18,17 @@ import {
|
|||||||
} from "@app/components/v2";
|
} 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 {
|
import { useDeleteIdentity, useGetIdentityById } from "@app/hooks/api";
|
||||||
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 = () => {
|
||||||
@@ -52,25 +41,13 @@ 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",
|
||||||
"revokeAuthMethod",
|
"upgradePlan",
|
||||||
"token",
|
"viewAuthMethod"
|
||||||
"tokenList",
|
|
||||||
"revokeToken",
|
|
||||||
"clientSecret",
|
|
||||||
"revokeClientSecret",
|
|
||||||
"universalAuthClientSecret", // list of client secrets
|
|
||||||
"upgradePlan"
|
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const onDeleteIdentitySubmit = async (id: string) => {
|
const onDeleteIdentitySubmit = async (id: string) => {
|
||||||
@@ -104,63 +81,6 @@ const Page = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRevokeTokenSubmit = async ({
|
|
||||||
identityId: parentIdentityId,
|
|
||||||
tokenId,
|
|
||||||
name
|
|
||||||
}: {
|
|
||||||
identityId: string;
|
|
||||||
tokenId: string;
|
|
||||||
name: string;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
await revokeToken({
|
|
||||||
identityId: parentIdentityId,
|
|
||||||
tokenId
|
|
||||||
});
|
|
||||||
|
|
||||||
handlePopUpClose("revokeToken");
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Successfully revoked token ${name ?? ""}`,
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
const error = err as any;
|
|
||||||
const text = error?.response?.data?.message ?? "Failed to delete identity";
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDeleteClientSecretSubmit = async ({ clientSecretId }: { clientSecretId: string }) => {
|
|
||||||
try {
|
|
||||||
if (!data?.identity.id || selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) return;
|
|
||||||
|
|
||||||
await revokeClientSecret({
|
|
||||||
identityId: data?.identity.id,
|
|
||||||
clientSecretId
|
|
||||||
});
|
|
||||||
|
|
||||||
handlePopUpToggle("revokeClientSecret", false);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully deleted client secret",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to delete client secret",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
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 && (
|
||||||
@@ -244,8 +164,6 @@ const Page = () => {
|
|||||||
<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}
|
||||||
/>
|
/>
|
||||||
@@ -260,18 +178,6 @@ 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)}
|
||||||
@@ -290,41 +196,19 @@ const Page = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<DeleteActionModal
|
<ViewIdentityAuthModal
|
||||||
isOpen={popUp.revokeToken.isOpen}
|
isOpen={popUp.viewAuthMethod.isOpen}
|
||||||
title={`Are you sure want to revoke ${
|
onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)}
|
||||||
(popUp?.revokeToken?.data as { name: string })?.name || ""
|
authMethod={popUp.viewAuthMethod.data}
|
||||||
}?`}
|
identityId={identityId}
|
||||||
onChange={(isOpen) => handlePopUpToggle("revokeToken", isOpen)}
|
onEditAuthMethod={(authMethod) =>
|
||||||
deleteKey="confirm"
|
handlePopUpOpen("identityAuthMethod", {
|
||||||
onDeleteApproved={() => {
|
identityId,
|
||||||
const revokeTokenData = popUp?.revokeToken?.data as {
|
name: data?.identity.name,
|
||||||
identityId: string;
|
allAuthMethods: data?.identity.authMethods,
|
||||||
tokenId: string;
|
authMethod
|
||||||
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>
|
||||||
);
|
);
|
||||||
|
|||||||
+38
-127
@@ -1,155 +1,66 @@
|
|||||||
import { useEffect } from "react";
|
import { faCog, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
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 { Button } from "@app/components/v2";
|
||||||
import { Button, IconButton, Select, SelectItem, Tooltip } from "@app/components/v2";
|
import { IdentityAuthMethod, identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
|
||||||
import { useGetIdentityById } from "@app/hooks/api";
|
|
||||||
import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities";
|
|
||||||
import { Identity } from "@app/hooks/api/identities/types";
|
|
||||||
import { 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<
|
popUpName: keyof UsePopUpState<["identityAuthMethod", "viewAuthMethod"]>,
|
||||||
[
|
data?: object | IdentityAuthMethod
|
||||||
"clientSecret",
|
|
||||||
"identityAuthMethod",
|
|
||||||
"revokeClientSecret",
|
|
||||||
"token",
|
|
||||||
"revokeToken",
|
|
||||||
"universalAuthClientSecret",
|
|
||||||
"tokenList"
|
|
||||||
]
|
|
||||||
>,
|
|
||||||
data?: object
|
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IdentityAuthenticationSection = ({
|
export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: Props) => {
|
||||||
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">
|
||||||
<div className="py-4">
|
{data.identity.authMethods.map((authMethod) => (
|
||||||
<div className="flex justify-between">
|
<button
|
||||||
<p className="mb-0.5 ml-px text-sm font-semibold text-mineshaft-300">Auth Method</p>
|
key={authMethod}
|
||||||
</div>
|
onClick={() => handlePopUpOpen("viewAuthMethod", authMethod)}
|
||||||
<div className="flex items-center gap-2">
|
type="button"
|
||||||
<div className="w-full">
|
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"
|
||||||
<Select
|
>
|
||||||
className="w-full"
|
<span>{identityAuthToNameMap[authMethod]}</span>
|
||||||
value={selectedAuthMethod as string}
|
<FontAwesomeIcon icon={faCog} size="xs" className="text-mineshaft-400" />
|
||||||
onValueChange={(value) => setSelectedAuthMethod(value as IdentityAuthMethod)}
|
</button>
|
||||||
>
|
))}
|
||||||
{(data.identity?.authMethods || []).map((authMethod) => (
|
</div>
|
||||||
<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>
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
handlePopUpOpen("identityAuthMethod", {
|
|
||||||
identityId,
|
|
||||||
name: data.identity.name,
|
|
||||||
allAuthMethods: data.identity.authMethods
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
variant="outline_bg"
|
|
||||||
className="w-full"
|
|
||||||
size="xs"
|
|
||||||
>
|
|
||||||
Create Auth Method
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!Object.values(IdentityAuthMethod).every((method) =>
|
||||||
|
data.identity.authMethods.includes(method)
|
||||||
|
) && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("identityAuthMethod", {
|
||||||
|
identityId,
|
||||||
|
name: data.identity.name,
|
||||||
|
allAuthMethods: data.identity.authMethods
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
variant="outline_bg"
|
||||||
|
className="mt-3 w-full"
|
||||||
|
size="xs"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
>
|
||||||
|
{data.identity.authMethods.length ? "Add" : "Create"} Auth Method
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
|
|||||||
+27
-17
@@ -1,4 +1,4 @@
|
|||||||
import { faEllipsis, faKey } from "@fortawesome/free-solid-svg-icons";
|
import { faEllipsisVertical, faKey } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ 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";
|
||||||
@@ -27,10 +28,13 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{tokens?.length ? (
|
{tokens?.length ? (
|
||||||
<div className="flex justify-between">
|
<div className="flex items-center justify-between border-b border-bunker-400 pb-1">
|
||||||
<p className="text-sm font-semibold text-mineshaft-300">{`Access Tokens (${tokens.length})`}</p>
|
<p className="text-sm font-medium text-bunker-300">{`Access Tokens (${tokens.length})`}</p>
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
size="xs"
|
||||||
|
className="underline"
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlePopUpOpen("tokenList", {
|
handlePopUpOpen("tokenList", {
|
||||||
identityId,
|
identityId,
|
||||||
@@ -50,16 +54,16 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="group flex items-center justify-between py-2 last:pb-0"
|
className="group flex items-center justify-between border-b border-mineshaft-500 px-2 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="1x" icon={faKey} />
|
<FontAwesomeIcon size="xs" className="text-mineshaft-400" icon={faKey} />
|
||||||
<div className="ml-4">
|
<div className="ml-3">
|
||||||
<p className="text-sm font-semibold text-mineshaft-300">
|
<p className="text-sm font-medium text-mineshaft-300">
|
||||||
{token.name ? token.name : "-"}
|
{token.name ? token.name : "-"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-mineshaft-300">
|
<p className="text-xs text-mineshaft-400">
|
||||||
{token.isAccessTokenRevoked
|
{token.isAccessTokenRevoked
|
||||||
? "Revoked"
|
? "Revoked"
|
||||||
: `Expires on ${format(expiresAt, "yyyy-MM-dd")}`}
|
: `Expires on ${format(expiresAt, "yyyy-MM-dd")}`}
|
||||||
@@ -67,14 +71,19 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
<DropdownMenuTrigger asChild>
|
||||||
<div className="opacity-0 transition-opacity duration-300 hover:text-primary-400 group-hover:opacity-100 data-[state=open]:text-primary-400">
|
<Tooltip side="right" content="More options">
|
||||||
<Tooltip content="More options">
|
<IconButton
|
||||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
colorSchema="secondary"
|
||||||
</Tooltip>
|
variant="plain"
|
||||||
</div>
|
size="xs"
|
||||||
|
ariaLabel="More options"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faEllipsisVertical} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="z-[101] p-1">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen("token", {
|
handlePopUpOpen("token", {
|
||||||
@@ -106,8 +115,9 @@ export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
|
|||||||
})}
|
})}
|
||||||
<Button
|
<Button
|
||||||
className="mr-4 mt-4 w-full"
|
className="mr-4 mt-4 w-full"
|
||||||
colorSchema="primary"
|
colorSchema="secondary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
size="xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlePopUpOpen("token", {
|
handlePopUpOpen("token", {
|
||||||
identityId
|
identityId
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
|||||||
type Props = {
|
type Props = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
popUpName: keyof UsePopUpState<["identity", "identityAuthMethod", "token", "clientSecret"]>,
|
popUpName: keyof UsePopUpState<["identity", "identityAuthMethod"]>,
|
||||||
data?: object
|
data?: object
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
-270
@@ -1,270 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Controller, useForm } from "react-hook-form";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { faCheck, faCopy, faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { format } from "date-fns";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
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-xs font-medium text-mineshaft-400">{label}</span>
|
||||||
|
{children ? (
|
||||||
|
<p className="break-words text-sm leading-4">{children}</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm italic leading-4 text-bunker-400">Not set</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { faBan, faEdit, faKey, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmptyState,
|
||||||
|
IconButton,
|
||||||
|
Pagination,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useRevokeIdentityTokenAuthToken } from "@app/hooks/api";
|
||||||
|
import { IdentityAccessToken } from "@app/hooks/api/identities/types";
|
||||||
|
import { IdentityTokenModal } from "@app/pages/organization/IdentityDetailsByIDPage/components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tokens: IdentityAccessToken[];
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
|
"token",
|
||||||
|
"revokeToken"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [perPage, setPerPage] = useState(5);
|
||||||
|
|
||||||
|
const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken();
|
||||||
|
|
||||||
|
const onRevokeTokenSubmit = async ({
|
||||||
|
identityId: parentIdentityId,
|
||||||
|
tokenId,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
identityId: string;
|
||||||
|
tokenId: string;
|
||||||
|
name: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
await revokeToken({
|
||||||
|
identityId: parentIdentityId,
|
||||||
|
tokenId
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("revokeToken");
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully revoked token ${name ?? ""}`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
const error = err as any;
|
||||||
|
const text = error?.response?.data?.message ?? "Failed to revoke token";
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-2 mt-3">
|
||||||
|
<div className="flex items-baseline justify-between border-b border-mineshaft-500 pb-1">
|
||||||
|
<span className="text-sm text-bunker-300">Access Tokens</span>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("token", {
|
||||||
|
identityId
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Add Token
|
||||||
|
</Button>
|
||||||
|
</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">
|
||||||
|
<Tooltip content="Edit Token">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("token", {
|
||||||
|
identityId,
|
||||||
|
tokenId: id,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="xs"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="Edit token"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faEdit} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
{!isAccessTokenRevoked && (
|
||||||
|
<Tooltip content="Revoke Token">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("revokeToken", {
|
||||||
|
identityId,
|
||||||
|
tokenId: id,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="xs"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="Revoke token"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faBan} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { faKey, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmptyState,
|
||||||
|
IconButton,
|
||||||
|
Pagination,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useRevokeIdentityUniversalAuthClientSecret } from "@app/hooks/api";
|
||||||
|
import { ClientSecretData } from "@app/hooks/api/identities/types";
|
||||||
|
import { IdentityClientSecretModal } from "@app/pages/organization/IdentityDetailsByIDPage/components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
clientSecrets: ClientSecretData[];
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identityId }: Props) => {
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||||
|
"revokeClientSecret",
|
||||||
|
"clientSecret"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [perPage, setPerPage] = useState(5);
|
||||||
|
|
||||||
|
const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret();
|
||||||
|
|
||||||
|
const onDeleteClientSecretSubmit = async (clientSecretId: string) => {
|
||||||
|
try {
|
||||||
|
await revokeClientSecret({
|
||||||
|
identityId,
|
||||||
|
clientSecretId
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpToggle("revokeClientSecret", false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted client secret",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete client secret",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-2">
|
||||||
|
<div className="flex items-baseline justify-between border-b border-mineshaft-500 pb-1">
|
||||||
|
<span className="text-sm text-bunker-300">Client Secrets</span>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("clientSecret", {
|
||||||
|
identityId
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Add Client Secret
|
||||||
|
</Button>
|
||||||
|
</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>
|
||||||
|
<Tooltip content="Delete Secret">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("revokeClientSecret", {
|
||||||
|
clientSecretPrefix,
|
||||||
|
clientSecretId: id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="xs"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="Delete secret"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { DeleteActionModal, Modal, ModalContent } from "@app/components/v2";
|
||||||
|
import { useOrganization } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import {
|
||||||
|
IdentityAuthMethod,
|
||||||
|
identityAuthToNameMap,
|
||||||
|
useDeleteIdentityAwsAuth,
|
||||||
|
useDeleteIdentityAzureAuth,
|
||||||
|
useDeleteIdentityGcpAuth,
|
||||||
|
useDeleteIdentityJwtAuth,
|
||||||
|
useDeleteIdentityKubernetesAuth,
|
||||||
|
useDeleteIdentityOidcAuth,
|
||||||
|
useDeleteIdentityTokenAuth,
|
||||||
|
useDeleteIdentityUniversalAuth
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { ViewIdentityJwtAuthContent } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityJwtAuthContent";
|
||||||
|
import { ViewIdentityOidcAuthContent } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent";
|
||||||
|
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent";
|
||||||
|
import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
|
||||||
|
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
|
||||||
|
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
|
||||||
|
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
|
||||||
|
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
identityId: string;
|
||||||
|
authMethod?: IdentityAuthMethod;
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
onEditAuthMethod: (authMethod: IdentityAuthMethod) => void;
|
||||||
|
onDeleteAuthMethod: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TRevokeOptions = {
|
||||||
|
identityId: string;
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Content = ({
|
||||||
|
identityId,
|
||||||
|
authMethod,
|
||||||
|
onEditAuthMethod,
|
||||||
|
onDeleteAuthMethod
|
||||||
|
}: Pick<Props, "authMethod" | "identityId" | "onEditAuthMethod" | "onDeleteAuthMethod">) => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const orgId = currentOrg?.id || "";
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["revokeAuthMethod"] as const);
|
||||||
|
|
||||||
|
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
|
||||||
|
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
|
||||||
|
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
|
||||||
|
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
|
||||||
|
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
|
||||||
|
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
||||||
|
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
||||||
|
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
||||||
|
|
||||||
|
let Component: (props: ViewAuthMethodProps) => JSX.Element;
|
||||||
|
let revokeMethod: (revokeOptions: TRevokeOptions) => Promise<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}
|
||||||
|
onEdit={() => onEditAuthMethod(authMethod)}
|
||||||
|
/>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewIdentityAuthModal = ({
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
authMethod,
|
||||||
|
identityId,
|
||||||
|
onEditAuthMethod
|
||||||
|
}: 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}
|
||||||
|
onEditAuthMethod={onEditAuthMethod}
|
||||||
|
onDeleteAuthMethod={() => onOpenChange(false)}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import { EmptyState, Spinner } from "@app/components/v2";
|
||||||
|
import { useGetIdentityAwsAuth } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
export const ViewIdentityAwsAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityAwsAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<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." />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}>
|
||||||
|
<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>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
||||||
|
{data.accessTokenTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
||||||
|
{data.accessTokenMaxTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
{data.accessTokenNumUsesLimit}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||||
|
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</ViewIdentityContentWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import { EmptyState, Spinner } from "@app/components/v2";
|
||||||
|
import { useGetIdentityAzureAuth } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
export const ViewIdentityAzureAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityAzureAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<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." />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}>
|
||||||
|
<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>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
||||||
|
{data.accessTokenTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
||||||
|
{data.accessTokenMaxTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
{data.accessTokenNumUsesLimit}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||||
|
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</ViewIdentityContentWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
onEdit: VoidFunction;
|
||||||
|
onDelete: VoidFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-baseline justify-between border-b border-mineshaft-500 pb-1">
|
||||||
|
<span className="text-sm text-bunker-300">Details</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
onClick={onEdit}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faEdit} />}
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
onClick={onDelete}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faTrash} />}
|
||||||
|
colorSchema="danger"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 grid grid-cols-2 gap-3">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import { EmptyState, Spinner } from "@app/components/v2";
|
||||||
|
import { useGetIdentityGcpAuth } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
export const ViewIdentityGcpAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityGcpAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<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." />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
||||||
|
{data.accessTokenTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
||||||
|
{data.accessTokenMaxTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
{data.accessTokenNumUsesLimit}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||||
|
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</ViewIdentityContentWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||||
|
import { useGetIdentityJwtAuth } from "@app/hooks/api";
|
||||||
|
import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums";
|
||||||
|
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
|
||||||
|
export const ViewIdentityJwtAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityJwtAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<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." />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}>
|
||||||
|
<IdentityAuthFieldDisplay label="Configuration Type">
|
||||||
|
{data.configurationType === IdentityJwtConfigurationType.JWKS ? "JWKS" : "Static"}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
{data.configurationType === IdentityJwtConfigurationType.JWKS ? (
|
||||||
|
<>
|
||||||
|
<IdentityAuthFieldDisplay className="col-span-2" label="JWKS CA Certificate">
|
||||||
|
<Tooltip
|
||||||
|
side="right"
|
||||||
|
className="max-w-xl p-2"
|
||||||
|
content={
|
||||||
|
<p className="break-words rounded bg-mineshaft-600 p-2">{data.jwksCaCert}</p>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="w-min">
|
||||||
|
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||||
|
<FontAwesomeIcon icon={faEye} />
|
||||||
|
<span>Reveal</span>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay className="col-span-2" label="JWKS URL">
|
||||||
|
{data.jwksUrl}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<IdentityAuthFieldDisplay className="col-span-2" label="Public Keys">
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{data.publicKeys.length &&
|
||||||
|
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>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
||||||
|
{data.accessTokenTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
||||||
|
{data.accessTokenMaxTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
{data.accessTokenNumUsesLimit}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||||
|
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</ViewIdentityContentWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||||
|
import { useGetIdentityKubernetesAuth } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
export const ViewIdentityKubernetesAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityKubernetesAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<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."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}>
|
||||||
|
<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">
|
||||||
|
<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 label="Access Token TLL (seconds)">
|
||||||
|
{data.accessTokenTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
||||||
|
{data.accessTokenMaxTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
{data.accessTokenNumUsesLimit}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||||
|
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</ViewIdentityContentWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||||
|
import { useGetIdentityOidcAuth } from "@app/hooks/api";
|
||||||
|
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
|
||||||
|
export const ViewIdentityOidcAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityOidcAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<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." />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete}>
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
||||||
|
{data.accessTokenTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
||||||
|
{data.accessTokenMaxTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
{data.accessTokenNumUsesLimit}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||||
|
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</ViewIdentityContentWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import { EmptyState, Spinner } from "@app/components/v2";
|
||||||
|
import { useGetIdentityTokenAuth, useGetIdentityTokensTokenAuth } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { IdentityTokenAuthTokensTable } from "./IdentityTokenAuthTokensTable";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
export const ViewIdentityTokenAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityTokenAuth(identityId);
|
||||||
|
const { data: tokens = [], isPending: clientSecretsPending } =
|
||||||
|
useGetIdentityTokensTokenAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending || clientSecretsPending) {
|
||||||
|
return (
|
||||||
|
<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." />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
|
||||||
|
import { useTimedReset } from "@app/hooks";
|
||||||
|
import {
|
||||||
|
useGetIdentityUniversalAuth,
|
||||||
|
useGetIdentityUniversalAuthClientSecrets
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { IdentityUniversalAuthClientSecretsTable } from "./IdentityUniversalAuthClientSecretsTable";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
export const ViewIdentityUniversalAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityUniversalAuth(identityId);
|
||||||
|
const { data: clientSecrets = [], isPending: clientSecretsPending } =
|
||||||
|
useGetIdentityUniversalAuthClientSecrets(identityId);
|
||||||
|
|
||||||
|
const [copyTextClientId, isCopyingClientId, setCopyTextClientId] = useTimedReset<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."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper onEdit={onEdit} 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-2 border-b border-mineshaft-500">
|
||||||
|
<span className="text-sm 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ViewIdentityAuthModal";
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
export type ViewAuthMethodProps = {
|
||||||
|
identityId: string;
|
||||||
|
onEdit: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
};
|
||||||
@@ -2,5 +2,4 @@ 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";
|
||||||
|
|||||||
Reference in New Issue
Block a user