feat(machine-identities): LDAP Auth Lockout

This commit is contained in:
x032205
2025-08-26 03:10:38 -04:00
parent 57c667f0b1
commit 931abea2bb
21 changed files with 956 additions and 353 deletions
@@ -0,0 +1,80 @@
import { useState } from "react";
import { UseMutationResult } from "@tanstack/react-query";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
export const LockoutFields = ({
clearLockoutsResult,
lockedOut,
identityId,
data
}: {
clearLockoutsResult: UseMutationResult<number, object, { identityId: string }, unknown>;
lockedOut: boolean;
identityId: string;
data: {
lockoutEnabled: boolean;
lockoutThreshold: number;
lockoutDuration: number;
lockoutCounterReset: number;
};
}) => {
const { mutateAsync, isPending } = clearLockoutsResult;
const [lockedOutState, setLockedOutState] = useState(lockedOut);
async function clearLockouts() {
try {
const deleted = await mutateAsync({ identityId });
createNotification({
text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`,
type: "success"
});
setLockedOutState(false);
} catch (error) {
console.error(error);
createNotification({
text: "Failed to clear lockouts. Please try again.",
type: "error"
});
}
}
return (
<>
<div className="col-span-2 mt-3 flex justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Lockout Options</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button
isDisabled={!isAllowed || !lockedOutState || isPending}
size="xs"
onClick={() => clearLockouts()}
isLoading={isPending}
colorSchema="secondary"
>
Clear All Lockouts
</Button>
)}
</OrgPermissionCan>
</div>
<IdentityAuthFieldDisplay label="Lockout">
{data.lockoutEnabled ? "Enabled" : "Disabled"}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Threshold">
{data.lockoutThreshold}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Duration">
{data.lockoutDuration} seconds
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Counter Reset">
{data.lockoutCounterReset} seconds
</IdentityAuthFieldDisplay>
</>
);
};
@@ -2,11 +2,12 @@ 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 { useGetIdentityLdapAuth } from "@app/hooks/api";
import { useClearIdentityLdapAuthLockouts, useGetIdentityLdapAuth } from "@app/hooks/api";
import { IdentityLdapAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm";
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { LockoutFields } from "./IdentityAuthLockoutFields";
import { ViewAuthMethodProps } from "./types";
export const ViewIdentityLdapAuthContent = ({
@@ -14,9 +15,11 @@ export const ViewIdentityLdapAuthContent = ({
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
popUp,
lockedOut
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityLdapAuth(identityId);
const clearLockoutsResult = useClearIdentityLdapAuthLockouts();
if (isPending) {
return (
@@ -98,6 +101,12 @@ export const ViewIdentityLdapAuthContent = ({
</Tooltip>
)}
</IdentityAuthFieldDisplay>
<LockoutFields
identityId={identityId}
lockedOut={lockedOut}
clearLockoutsResult={clearLockoutsResult}
data={data}
/>
</ViewIdentityContentWrapper>
);
};
@@ -1,11 +1,7 @@
import { useState } from "react";
import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
import { useTimedReset } from "@app/hooks";
import {
useClearIdentityUniversalAuthLockouts,
@@ -15,6 +11,7 @@ import {
import { IdentityUniversalAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { LockoutFields } from "./IdentityAuthLockoutFields";
import { IdentityUniversalAuthClientSecretsTable } from "./IdentityUniversalAuthClientSecretsTable";
import { ViewAuthMethodProps } from "./types";
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
@@ -30,32 +27,12 @@ export const ViewIdentityUniversalAuthContent = ({
const { data, isPending } = useGetIdentityUniversalAuth(identityId);
const { data: clientSecrets = [], isPending: clientSecretsPending } =
useGetIdentityUniversalAuthClientSecrets(identityId);
const { mutateAsync: clearLockoutsFn, isPending: isClearLockoutsPending } =
useClearIdentityUniversalAuthLockouts();
const [lockedOutState, setLockedOutState] = useState(lockedOut);
const clearLockoutsResult = useClearIdentityUniversalAuthLockouts();
const [copyTextClientId, isCopyingClientId, setCopyTextClientId] = useTimedReset<string>({
initialState: "Copy Client ID to clipboard"
});
async function clearLockouts() {
try {
const deleted = await clearLockoutsFn({ identityId });
createNotification({
text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`,
type: "success"
});
setLockedOutState(false);
} catch (error) {
console.error(error);
createNotification({
text: "Failed to clear lockouts. Please try again.",
type: "error"
});
}
}
if (isPending || clientSecretsPending) {
return (
<div className="flex w-full items-center justify-center">
@@ -112,34 +89,12 @@ export const ViewIdentityUniversalAuthContent = ({
<IdentityAuthFieldDisplay label="Client Secret Trusted IPs">
{data.clientSecretTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<div className="col-span-2 mt-3 flex justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Lockout Options</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button
isDisabled={!isAllowed || !lockedOutState || isClearLockoutsPending}
size="xs"
onClick={() => clearLockouts()}
isLoading={isClearLockoutsPending}
colorSchema="secondary"
>
Clear All Lockouts
</Button>
)}
</OrgPermissionCan>
</div>
<IdentityAuthFieldDisplay label="Lockout">
{data.lockoutEnabled ? "Enabled" : "Disabled"}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Threshold">
{data.lockoutThreshold}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Duration">
{data.lockoutDuration} seconds
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Counter Reset">
{data.lockoutCounterReset} seconds
</IdentityAuthFieldDisplay>
<LockoutFields
identityId={identityId}
lockedOut={lockedOut}
clearLockoutsResult={clearLockoutsResult}
data={data}
/>
<div className="col-span-2 my-3">
<div className="mb-3 border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Client ID</span>