mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 17:28:13 +00:00
improvement: overhaul identity auth ui section
This commit is contained in:
+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;
|
||||
};
|
||||
Reference in New Issue
Block a user