mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 15:28:12 +00:00
feat(identities): Universal Auth Login Lockout
This commit is contained in:
+13
-3
@@ -1,4 +1,4 @@
|
||||
import { faCog, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCog, faLock, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
@@ -28,12 +28,22 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P
|
||||
{data.identity.authMethods.map((authMethod) => (
|
||||
<button
|
||||
key={authMethod}
|
||||
onClick={() => handlePopUpOpen("viewAuthMethod", authMethod)}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("viewAuthMethod", {
|
||||
authMethod,
|
||||
lockedOut: data.identity.activeLockoutAuthMethods.includes(authMethod)
|
||||
})
|
||||
}
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between bg-mineshaft-900 px-4 py-2 text-sm hover:bg-mineshaft-700 data-[state=open]:bg-mineshaft-600"
|
||||
>
|
||||
<span>{identityAuthToNameMap[authMethod]}</span>
|
||||
<FontAwesomeIcon icon={faCog} size="xs" className="text-mineshaft-400" />
|
||||
<div className="flex gap-2">
|
||||
{data.identity.activeLockoutAuthMethods.includes(authMethod) && (
|
||||
<FontAwesomeIcon icon={faLock} size="xs" className="text-red-400/50" />
|
||||
)}
|
||||
<FontAwesomeIcon icon={faCog} size="xs" className="text-mineshaft-400" />
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
+7
-2
@@ -37,6 +37,7 @@ import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthCon
|
||||
type Props = {
|
||||
identityId: string;
|
||||
authMethod?: IdentityAuthMethod;
|
||||
lockedOut: boolean;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
onDeleteAuthMethod: () => void;
|
||||
@@ -50,8 +51,9 @@ type TRevokeOptions = {
|
||||
export const Content = ({
|
||||
identityId,
|
||||
authMethod,
|
||||
lockedOut,
|
||||
onDeleteAuthMethod
|
||||
}: Pick<Props, "authMethod" | "identityId" | "onDeleteAuthMethod">) => {
|
||||
}: Pick<Props, "authMethod" | "lockedOut" | "identityId" | "onDeleteAuthMethod">) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
|
||||
@@ -162,6 +164,7 @@ export const Content = ({
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
lockedOut={lockedOut}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp?.revokeAuthMethod?.isOpen}
|
||||
@@ -184,7 +187,8 @@ export const ViewIdentityAuthModal = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
authMethod,
|
||||
identityId
|
||||
identityId,
|
||||
lockedOut
|
||||
}: Omit<Props, "onDeleteAuthMethod">) => {
|
||||
if (!identityId || !authMethod) return null;
|
||||
|
||||
@@ -194,6 +198,7 @@ export const ViewIdentityAuthModal = ({
|
||||
<Content
|
||||
identityId={identityId}
|
||||
authMethod={authMethod}
|
||||
lockedOut={lockedOut}
|
||||
onDeleteAuthMethod={() => onOpenChange(false)}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
||||
+58
-3
@@ -1,9 +1,10 @@
|
||||
import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faArrowsRotate, faBan, faCheck, faCopy, faFire } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { Button, EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import {
|
||||
useClearIdentityUniversalAuthLockouts,
|
||||
useGetIdentityUniversalAuth,
|
||||
useGetIdentityUniversalAuthClientSecrets
|
||||
} from "@app/hooks/api";
|
||||
@@ -13,22 +14,42 @@ import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { IdentityUniversalAuthClientSecretsTable } from "./IdentityUniversalAuthClientSecretsTable";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { useState } from "react";
|
||||
|
||||
export const ViewIdentityUniversalAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
popUp,
|
||||
lockedOut
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityUniversalAuth(identityId);
|
||||
const { data: clientSecrets = [], isPending: clientSecretsPending } =
|
||||
useGetIdentityUniversalAuthClientSecrets(identityId);
|
||||
const { mutateAsync: clearLockoutsFn, isPending: isClearLockoutsPending } =
|
||||
useClearIdentityUniversalAuthLockouts();
|
||||
|
||||
const [lockedOutState, setLockedOutState] = useState(lockedOut);
|
||||
|
||||
const [copyTextClientId, isCopyingClientId, setCopyTextClientId] = useTimedReset<string>({
|
||||
initialState: "Copy Client ID to clipboard"
|
||||
});
|
||||
|
||||
async function clearLockouts() {
|
||||
const deleted = await clearLockoutsFn({ identityId });
|
||||
|
||||
createNotification({
|
||||
text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
setLockedOutState(false);
|
||||
}
|
||||
|
||||
if (isPending || clientSecretsPending) {
|
||||
return (
|
||||
<div className="flex w-full items-center justify-center">
|
||||
@@ -85,6 +106,40 @@ 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}
|
||||
leftIcon={
|
||||
isClearLockoutsPending ? (
|
||||
<FontAwesomeIcon icon={faArrowsRotate} className="animate-spin" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faFire} />
|
||||
)
|
||||
}
|
||||
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>
|
||||
<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>
|
||||
|
||||
+1
@@ -9,4 +9,5 @@ export type ViewAuthMethodProps = {
|
||||
state?: boolean
|
||||
) => void;
|
||||
popUp: UsePopUpState<["revokeAuthMethod", "upgradePlan", "identityAuthMethod"]>;
|
||||
lockedOut: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user