Review fixes

This commit is contained in:
x032205
2025-08-26 20:40:16 -04:00
parent 1b22438c46
commit 8945bc0dc1
12 changed files with 89 additions and 67 deletions
@@ -6,8 +6,11 @@ export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.IdentityUniversalAuth)) { if (await knex.schema.hasTable(TableName.IdentityUniversalAuth)) {
const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutEnabled"); const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutEnabled");
const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutThreshold"); const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutThreshold");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDuration"); const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDurationSeconds");
const hasLockoutCounterReset = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutCounterReset"); const hasLockoutCounterReset = await knex.schema.hasColumn(
TableName.IdentityUniversalAuth,
"lockoutCounterResetSeconds"
);
await knex.schema.alterTable(TableName.IdentityUniversalAuth, async (t) => { await knex.schema.alterTable(TableName.IdentityUniversalAuth, async (t) => {
if (!hasLockoutEnabled) { if (!hasLockoutEnabled) {
@@ -17,10 +20,10 @@ export async function up(knex: Knex): Promise<void> {
t.integer("lockoutThreshold").notNullable().defaultTo(3); t.integer("lockoutThreshold").notNullable().defaultTo(3);
} }
if (!hasLockoutDuration) { if (!hasLockoutDuration) {
t.integer("lockoutDuration").notNullable().defaultTo(300); // 5 minutes (in seconds) t.integer("lockoutDurationSeconds").notNullable().defaultTo(300); // 5 minutes
} }
if (!hasLockoutCounterReset) { if (!hasLockoutCounterReset) {
t.integer("lockoutCounterReset").notNullable().defaultTo(30); // 30 seconds t.integer("lockoutCounterResetSeconds").notNullable().defaultTo(30); // 30 seconds
} }
}); });
} }
@@ -30,8 +33,11 @@ export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.IdentityUniversalAuth)) { if (await knex.schema.hasTable(TableName.IdentityUniversalAuth)) {
const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutEnabled"); const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutEnabled");
const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutThreshold"); const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutThreshold");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDuration"); const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDurationSeconds");
const hasLockoutCounterReset = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutCounterReset"); const hasLockoutCounterReset = await knex.schema.hasColumn(
TableName.IdentityUniversalAuth,
"lockoutCounterResetSeconds"
);
await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => { await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => {
if (hasLockoutEnabled) { if (hasLockoutEnabled) {
@@ -41,10 +47,10 @@ export async function down(knex: Knex): Promise<void> {
t.dropColumn("lockoutThreshold"); t.dropColumn("lockoutThreshold");
} }
if (hasLockoutDuration) { if (hasLockoutDuration) {
t.dropColumn("lockoutDuration"); t.dropColumn("lockoutDurationSeconds");
} }
if (hasLockoutCounterReset) { if (hasLockoutCounterReset) {
t.dropColumn("lockoutCounterReset"); t.dropColumn("lockoutCounterResetSeconds");
} }
}); });
} }
@@ -21,8 +21,8 @@ export const IdentityUniversalAuthsSchema = z.object({
accessTokenPeriod: z.coerce.number().default(0), accessTokenPeriod: z.coerce.number().default(0),
lockoutEnabled: z.boolean().default(true), lockoutEnabled: z.boolean().default(true),
lockoutThreshold: z.number().default(3), lockoutThreshold: z.number().default(3),
lockoutDuration: z.number().default(300), lockoutDurationSeconds: z.number().default(300),
lockoutCounterReset: z.number().default(30) lockoutCounterResetSeconds: z.number().default(30)
}); });
export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>; export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>;
@@ -869,8 +869,8 @@ interface AddIdentityUniversalAuthEvent {
accessTokenTrustedIps: Array<TIdentityTrustedIp>; accessTokenTrustedIps: Array<TIdentityTrustedIp>;
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
}; };
} }
@@ -885,8 +885,8 @@ interface UpdateIdentityUniversalAuthEvent {
accessTokenTrustedIps?: Array<TIdentityTrustedIp>; accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
lockoutEnabled?: boolean; lockoutEnabled?: boolean;
lockoutThreshold?: number; lockoutThreshold?: number;
lockoutDuration?: number; lockoutDurationSeconds?: number;
lockoutCounterReset?: number; lockoutCounterResetSeconds?: number;
}; };
} }
+6 -4
View File
@@ -169,8 +169,9 @@ export const UNIVERSAL_AUTH = {
"The period for an access token in seconds. This value will be referenced at renewal time. Default value is 0.", "The period for an access token in seconds. This value will be referenced at renewal time. Default value is 0.",
lockoutEnabled: "Whether the lockout feature is enabled.", lockoutEnabled: "Whether the lockout feature is enabled.",
lockoutThreshold: "The amount of times login must fail before locking the identity auth method.", lockoutThreshold: "The amount of times login must fail before locking the identity auth method.",
lockoutDuration: "How long an identity auth method lockout lasts.", lockoutDurationSeconds: "How long an identity auth method lockout lasts.",
lockoutCounterReset: "How long to wait from the most recent failed login until resetting the lockout counter." lockoutCounterResetSeconds:
"How long to wait from the most recent failed login until resetting the lockout counter."
}, },
RETRIEVE: { RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for." identityId: "The ID of the identity to retrieve the auth method for."
@@ -188,8 +189,9 @@ export const UNIVERSAL_AUTH = {
accessTokenPeriod: "The new period for an access token in seconds.", accessTokenPeriod: "The new period for an access token in seconds.",
lockoutEnabled: "Whether the lockout feature is enabled.", lockoutEnabled: "Whether the lockout feature is enabled.",
lockoutThreshold: "The amount of times login must fail before locking the identity auth method.", lockoutThreshold: "The amount of times login must fail before locking the identity auth method.",
lockoutDuration: "How long an identity auth method lockout lasts.", lockoutDurationSeconds: "How long an identity auth method lockout lasts.",
lockoutCounterReset: "How long to wait from the most recent failed login until resetting the lockout counter." lockoutCounterResetSeconds:
"How long to wait from the most recent failed login until resetting the lockout counter."
}, },
CREATE_CLIENT_SECRET: { CREATE_CLIENT_SECRET: {
identityId: "The ID of the identity to create a client secret for.", identityId: "The ID of the identity to create a client secret for.",
@@ -140,13 +140,18 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenPeriod: z.number().int().min(0).default(0).describe(UNIVERSAL_AUTH.ATTACH.accessTokenPeriod), accessTokenPeriod: z.number().int().min(0).default(0).describe(UNIVERSAL_AUTH.ATTACH.accessTokenPeriod),
lockoutEnabled: z.boolean().default(true).describe(UNIVERSAL_AUTH.ATTACH.lockoutEnabled), lockoutEnabled: z.boolean().default(true).describe(UNIVERSAL_AUTH.ATTACH.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).default(3).describe(UNIVERSAL_AUTH.ATTACH.lockoutThreshold), lockoutThreshold: z.number().min(1).max(30).default(3).describe(UNIVERSAL_AUTH.ATTACH.lockoutThreshold),
lockoutDuration: z.number().min(30).max(86400).default(300).describe(UNIVERSAL_AUTH.ATTACH.lockoutDuration), lockoutDurationSeconds: z
lockoutCounterReset: z .number()
.min(30)
.max(86400)
.default(300)
.describe(UNIVERSAL_AUTH.ATTACH.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number() .number()
.min(5) .min(5)
.max(3600) .max(3600)
.default(30) .default(30)
.describe(UNIVERSAL_AUTH.ATTACH.lockoutCounterReset) .describe(UNIVERSAL_AUTH.ATTACH.lockoutCounterResetSeconds)
}) })
.refine( .refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL, (val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -183,8 +188,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit, accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit,
lockoutEnabled: identityUniversalAuth.lockoutEnabled, lockoutEnabled: identityUniversalAuth.lockoutEnabled,
lockoutThreshold: identityUniversalAuth.lockoutThreshold, lockoutThreshold: identityUniversalAuth.lockoutThreshold,
lockoutDuration: identityUniversalAuth.lockoutDuration, lockoutDurationSeconds: identityUniversalAuth.lockoutDurationSeconds,
lockoutCounterReset: identityUniversalAuth.lockoutCounterReset lockoutCounterResetSeconds: identityUniversalAuth.lockoutCounterResetSeconds
} }
} }
}); });
@@ -259,13 +264,18 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenPeriod), .describe(UNIVERSAL_AUTH.UPDATE.accessTokenPeriod),
lockoutEnabled: z.boolean().optional().describe(UNIVERSAL_AUTH.UPDATE.lockoutEnabled), lockoutEnabled: z.boolean().optional().describe(UNIVERSAL_AUTH.UPDATE.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).optional().describe(UNIVERSAL_AUTH.UPDATE.lockoutThreshold), lockoutThreshold: z.number().min(1).max(30).optional().describe(UNIVERSAL_AUTH.UPDATE.lockoutThreshold),
lockoutDuration: z.number().min(30).max(86400).optional().describe(UNIVERSAL_AUTH.UPDATE.lockoutDuration), lockoutDurationSeconds: z
lockoutCounterReset: z .number()
.min(30)
.max(86400)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number() .number()
.min(5) .min(5)
.max(3600) .max(3600)
.optional() .optional()
.describe(UNIVERSAL_AUTH.UPDATE.lockoutCounterReset) .describe(UNIVERSAL_AUTH.UPDATE.lockoutCounterResetSeconds)
}) })
.refine( .refine(
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true), (val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
@@ -301,8 +311,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit, accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit,
lockoutEnabled: identityUniversalAuth.lockoutEnabled, lockoutEnabled: identityUniversalAuth.lockoutEnabled,
lockoutThreshold: identityUniversalAuth.lockoutThreshold, lockoutThreshold: identityUniversalAuth.lockoutThreshold,
lockoutDuration: identityUniversalAuth.lockoutDuration, lockoutDurationSeconds: identityUniversalAuth.lockoutDurationSeconds,
lockoutCounterReset: identityUniversalAuth.lockoutCounterReset lockoutCounterResetSeconds: identityUniversalAuth.lockoutCounterResetSeconds
} }
} }
}); });
@@ -131,7 +131,7 @@ export const identityUaServiceFactory = ({
await keyStore.setItemWithExpiry( await keyStore.setItemWithExpiry(
LOCKOUT_KEY, LOCKOUT_KEY,
lockout.lockedOut ? identityUa.lockoutDuration : identityUa.lockoutCounterReset, lockout.lockedOut ? identityUa.lockoutDurationSeconds : identityUa.lockoutCounterResetSeconds,
JSON.stringify(lockout) JSON.stringify(lockout)
); );
} }
@@ -251,8 +251,8 @@ export const identityUaServiceFactory = ({
accessTokenPeriod, accessTokenPeriod,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}: TAttachUaDTO) => { }: TAttachUaDTO) => {
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin); await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
@@ -325,8 +325,8 @@ export const identityUaServiceFactory = ({
accessTokenPeriod, accessTokenPeriod,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}, },
tx tx
); );
@@ -349,8 +349,8 @@ export const identityUaServiceFactory = ({
actorOrgId, actorOrgId,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}: TUpdateUaDTO) => { }: TUpdateUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
@@ -429,8 +429,8 @@ export const identityUaServiceFactory = ({
: undefined, : undefined,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}); });
return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId }; return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId };
}; };
@@ -11,8 +11,8 @@ export type TAttachUaDTO = {
isActorSuperAdmin?: boolean; isActorSuperAdmin?: boolean;
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TUpdateUaDTO = { export type TUpdateUaDTO = {
@@ -25,8 +25,8 @@ export type TUpdateUaDTO = {
accessTokenTrustedIps?: { ipAddress: string }[]; accessTokenTrustedIps?: { ipAddress: string }[];
lockoutEnabled?: boolean; lockoutEnabled?: boolean;
lockoutThreshold?: number; lockoutThreshold?: number;
lockoutDuration?: number; lockoutDurationSeconds?: number;
lockoutCounterReset?: number; lockoutCounterResetSeconds?: number;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetUaDTO = { export type TGetUaDTO = {
@@ -152,8 +152,8 @@ export const useAddIdentityUniversalAuth = () => {
accessTokenTrustedIps, accessTokenTrustedIps,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}) => { }) => {
const { const {
data: { identityUniversalAuth } data: { identityUniversalAuth }
@@ -165,8 +165,8 @@ export const useAddIdentityUniversalAuth = () => {
accessTokenTrustedIps, accessTokenTrustedIps,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}); });
return identityUniversalAuth; return identityUniversalAuth;
}, },
@@ -195,8 +195,8 @@ export const useUpdateIdentityUniversalAuth = () => {
accessTokenPeriod, accessTokenPeriod,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}) => { }) => {
const { const {
data: { identityUniversalAuth } data: { identityUniversalAuth }
@@ -209,8 +209,8 @@ export const useUpdateIdentityUniversalAuth = () => {
accessTokenPeriod, accessTokenPeriod,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}); });
return identityUniversalAuth; return identityUniversalAuth;
}, },
+6 -6
View File
@@ -116,8 +116,8 @@ export type IdentityUniversalAuth = {
accessTokenPeriod: number; accessTokenPeriod: number;
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
}; };
export type AddIdentityUniversalAuthDTO = { export type AddIdentityUniversalAuthDTO = {
@@ -135,8 +135,8 @@ export type AddIdentityUniversalAuthDTO = {
}[]; }[];
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
}; };
export type UpdateIdentityUniversalAuthDTO = { export type UpdateIdentityUniversalAuthDTO = {
@@ -154,8 +154,8 @@ export type UpdateIdentityUniversalAuthDTO = {
}[]; }[];
lockoutEnabled?: boolean; lockoutEnabled?: boolean;
lockoutThreshold?: number; lockoutThreshold?: number;
lockoutDuration?: number; lockoutDurationSeconds?: number;
lockoutCounterReset?: number; lockoutCounterResetSeconds?: number;
}; };
export type DeleteIdentityUniversalAuthDTO = { export type DeleteIdentityUniversalAuthDTO = {
@@ -151,8 +151,8 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
accessTokenPeriod: 0, accessTokenPeriod: 0,
lockoutEnabled: true, lockoutEnabled: true,
lockoutThreshold: 3, lockoutThreshold: 3,
lockoutDuration: 300, lockoutDurationSeconds: 300,
lockoutCounterReset: 30 lockoutCounterResetSeconds: 30
}); });
handlePopUpToggle("identity", false); handlePopUpToggle("identity", false);
@@ -215,8 +215,8 @@ export const IdentityUniversalAuthForm = ({
useEffect(() => { useEffect(() => {
if (data) { if (data) {
const lockoutDurationObj = getObjectFromSeconds(data.lockoutDuration); const lockoutDurationObj = getObjectFromSeconds(data.lockoutDurationSeconds);
const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterReset); const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterResetSeconds);
reset({ reset({
accessTokenTTL: String(data.accessTokenTTL), accessTokenTTL: String(data.accessTokenTTL),
@@ -279,8 +279,11 @@ export const IdentityUniversalAuthForm = ({
try { try {
if (!identityId) return; if (!identityId) return;
const lockoutDuration = durationToSeconds(Number(lockoutDurationValue), lockoutDurationUnit); const lockoutDurationSeconds = durationToSeconds(
const lockoutCounterReset = durationToSeconds( Number(lockoutDurationValue),
lockoutDurationUnit
);
const lockoutCounterResetSeconds = durationToSeconds(
Number(lockoutCounterResetValue), Number(lockoutCounterResetValue),
lockoutCounterResetUnit lockoutCounterResetUnit
); );
@@ -298,8 +301,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenPeriod: Number(accessTokenPeriod), accessTokenPeriod: Number(accessTokenPeriod),
lockoutEnabled, lockoutEnabled,
lockoutThreshold: Number(lockoutThreshold), lockoutThreshold: Number(lockoutThreshold),
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}); });
} else { } else {
// create new universal auth configuration // create new universal auth configuration
@@ -315,8 +318,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenPeriod: Number(accessTokenPeriod), accessTokenPeriod: Number(accessTokenPeriod),
lockoutEnabled, lockoutEnabled,
lockoutThreshold: Number(lockoutThreshold), lockoutThreshold: Number(lockoutThreshold),
lockoutDuration: Number(lockoutDuration), lockoutDurationSeconds: Number(lockoutDurationSeconds),
lockoutCounterReset: Number(lockoutCounterReset) lockoutCounterResetSeconds: Number(lockoutCounterResetSeconds)
}); });
} }
@@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import ms from "ms";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
@@ -135,10 +136,10 @@ export const ViewIdentityUniversalAuthContent = ({
{data.lockoutThreshold} {data.lockoutThreshold}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Duration"> <IdentityAuthFieldDisplay label="Lockout Duration">
{data.lockoutDuration} seconds {ms(data.lockoutDurationSeconds * 1000, { long: true })}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Lockout Counter Reset"> <IdentityAuthFieldDisplay label="Lockout Counter Reset">
{data.lockoutCounterReset} seconds {ms(data.lockoutCounterResetSeconds * 1000, { long: true })}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>
<div className="col-span-2 my-3"> <div className="col-span-2 my-3">
<div className="mb-3 border-b border-mineshaft-500 pb-2"> <div className="mb-3 border-b border-mineshaft-500 pb-2">