- Swap database column names to include "seconds" - Use MS for frontend

time display
This commit is contained in:
x032205
2025-08-26 20:55:02 -04:00
parent 6c214e6e99
commit 598a026482
12 changed files with 107 additions and 66 deletions
@@ -6,8 +6,11 @@ export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.IdentityLdapAuth)) { if (await knex.schema.hasTable(TableName.IdentityLdapAuth)) {
const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutEnabled"); const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutEnabled");
const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutThreshold"); const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutThreshold");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutDuration"); const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutDurationSeconds");
const hasLockoutCounterReset = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutCounterReset"); const hasLockoutCounterReset = await knex.schema.hasColumn(
TableName.IdentityLdapAuth,
"lockoutCounterResetSeconds"
);
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => { await knex.schema.alterTable(TableName.IdentityLdapAuth, (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.IdentityLdapAuth)) { if (await knex.schema.hasTable(TableName.IdentityLdapAuth)) {
const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutEnabled"); const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutEnabled");
const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutThreshold"); const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutThreshold");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutDuration"); const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutDurationSeconds");
const hasLockoutCounterReset = await knex.schema.hasColumn(TableName.IdentityLdapAuth, "lockoutCounterReset"); const hasLockoutCounterReset = await knex.schema.hasColumn(
TableName.IdentityLdapAuth,
"lockoutCounterResetSeconds"
);
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => { await knex.schema.alterTable(TableName.IdentityLdapAuth, (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");
} }
}); });
} }
@@ -29,8 +29,8 @@ export const IdentityLdapAuthsSchema = z.object({
templateId: z.string().uuid().nullable().optional(), templateId: z.string().uuid().nullable().optional(),
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 TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>; export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
@@ -1372,8 +1372,8 @@ interface AddIdentityLdapAuthEvent {
templateId?: string | null; templateId?: string | null;
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
}; };
} }
@@ -1390,8 +1390,8 @@ interface UpdateIdentityLdapAuthEvent {
templateId?: string | null; templateId?: string | null;
lockoutEnabled?: boolean; lockoutEnabled?: boolean;
lockoutThreshold?: number; lockoutThreshold?: number;
lockoutDuration?: number; lockoutDurationSeconds?: number;
lockoutCounterReset?: number; lockoutCounterResetSeconds?: number;
}; };
} }
+6 -4
View File
@@ -245,8 +245,9 @@ export const LDAP_AUTH = {
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
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."
}, },
UPDATE: { UPDATE: {
identityId: "The ID of the identity to update the configuration for.", identityId: "The ID of the identity to update the configuration for.",
@@ -264,8 +265,9 @@ export const LDAP_AUTH = {
templateId: "The ID of the identity auth template to update the configuration to.", templateId: "The ID of the identity auth template to update the configuration to.",
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 configuration for." identityId: "The ID of the identity to retrieve the configuration for."
@@ -266,8 +266,18 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit), .describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit),
lockoutEnabled: z.boolean().default(true).describe(LDAP_AUTH.ATTACH.lockoutEnabled), lockoutEnabled: z.boolean().default(true).describe(LDAP_AUTH.ATTACH.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).default(3).describe(LDAP_AUTH.ATTACH.lockoutThreshold), lockoutThreshold: z.number().min(1).max(30).default(3).describe(LDAP_AUTH.ATTACH.lockoutThreshold),
lockoutDuration: z.number().min(30).max(86400).default(300).describe(LDAP_AUTH.ATTACH.lockoutDuration), lockoutDurationSeconds: z
lockoutCounterReset: z.number().min(5).max(3600).default(30).describe(LDAP_AUTH.ATTACH.lockoutCounterReset) .number()
.min(30)
.max(86400)
.default(300)
.describe(LDAP_AUTH.ATTACH.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.default(30)
.describe(LDAP_AUTH.ATTACH.lockoutCounterResetSeconds)
}) })
.refine( .refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL, (val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -320,8 +330,18 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit), .describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit),
lockoutEnabled: z.boolean().default(true).describe(LDAP_AUTH.ATTACH.lockoutEnabled), lockoutEnabled: z.boolean().default(true).describe(LDAP_AUTH.ATTACH.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).default(3).describe(LDAP_AUTH.ATTACH.lockoutThreshold), lockoutThreshold: z.number().min(1).max(30).default(3).describe(LDAP_AUTH.ATTACH.lockoutThreshold),
lockoutDuration: z.number().min(30).max(86400).default(300).describe(LDAP_AUTH.ATTACH.lockoutDuration), lockoutDurationSeconds: z
lockoutCounterReset: z.number().min(5).max(3600).default(30).describe(LDAP_AUTH.ATTACH.lockoutCounterReset) .number()
.min(30)
.max(86400)
.default(300)
.describe(LDAP_AUTH.ATTACH.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.default(30)
.describe(LDAP_AUTH.ATTACH.lockoutCounterResetSeconds)
}) })
.refine( .refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL, (val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -364,8 +384,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
templateId: identityLdapAuth.templateId, templateId: identityLdapAuth.templateId,
lockoutEnabled: identityLdapAuth.lockoutEnabled, lockoutEnabled: identityLdapAuth.lockoutEnabled,
lockoutThreshold: identityLdapAuth.lockoutThreshold, lockoutThreshold: identityLdapAuth.lockoutThreshold,
lockoutDuration: identityLdapAuth.lockoutDuration, lockoutDurationSeconds: identityLdapAuth.lockoutDurationSeconds,
lockoutCounterReset: identityLdapAuth.lockoutCounterReset lockoutCounterResetSeconds: identityLdapAuth.lockoutCounterResetSeconds
} }
} }
}); });
@@ -432,8 +452,18 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
.describe(LDAP_AUTH.UPDATE.accessTokenMaxTTL), .describe(LDAP_AUTH.UPDATE.accessTokenMaxTTL),
lockoutEnabled: z.boolean().optional().describe(LDAP_AUTH.UPDATE.lockoutEnabled), lockoutEnabled: z.boolean().optional().describe(LDAP_AUTH.UPDATE.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).optional().describe(LDAP_AUTH.UPDATE.lockoutThreshold), lockoutThreshold: z.number().min(1).max(30).optional().describe(LDAP_AUTH.UPDATE.lockoutThreshold),
lockoutDuration: z.number().min(30).max(86400).optional().describe(LDAP_AUTH.UPDATE.lockoutDuration), lockoutDurationSeconds: z
lockoutCounterReset: z.number().min(5).max(3600).optional().describe(LDAP_AUTH.UPDATE.lockoutCounterReset) .number()
.min(30)
.max(86400)
.optional()
.describe(LDAP_AUTH.UPDATE.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.optional()
.describe(LDAP_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),
@@ -475,8 +505,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
templateId: identityLdapAuth.templateId, templateId: identityLdapAuth.templateId,
lockoutEnabled: identityLdapAuth.lockoutEnabled, lockoutEnabled: identityLdapAuth.lockoutEnabled,
lockoutThreshold: identityLdapAuth.lockoutThreshold, lockoutThreshold: identityLdapAuth.lockoutThreshold,
lockoutDuration: identityLdapAuth.lockoutDuration, lockoutDurationSeconds: identityLdapAuth.lockoutDurationSeconds,
lockoutCounterReset: identityLdapAuth.lockoutCounterReset lockoutCounterResetSeconds: identityLdapAuth.lockoutCounterResetSeconds
} }
} }
}); });
@@ -223,8 +223,8 @@ export const identityLdapAuthServiceFactory = ({
allowedFields, allowedFields,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}: TAttachLdapAuthDTO) => { }: TAttachLdapAuthDTO) => {
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin); await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
@@ -360,8 +360,8 @@ export const identityLdapAuthServiceFactory = ({
templateId, templateId,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}, },
tx tx
); );
@@ -390,8 +390,8 @@ export const identityLdapAuthServiceFactory = ({
actorOrgId, actorOrgId,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}: TUpdateLdapAuthDTO) => { }: TUpdateLdapAuthDTO) => {
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}` });
@@ -542,8 +542,8 @@ export const identityLdapAuthServiceFactory = ({
: undefined, : undefined,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}); });
return { ...updatedLdapAuth, orgId: identityMembershipOrg.orgId }; return { ...updatedLdapAuth, orgId: identityMembershipOrg.orgId };
@@ -687,7 +687,7 @@ export const identityLdapAuthServiceFactory = ({
await keyStore.setItemWithExpiry( await keyStore.setItemWithExpiry(
LOCKOUT_KEY, LOCKOUT_KEY,
lockout.lockedOut ? identityLdapAuth.lockoutDuration : identityLdapAuth.lockoutCounterReset, lockout.lockedOut ? identityLdapAuth.lockoutDurationSeconds : identityLdapAuth.lockoutCounterResetSeconds,
JSON.stringify(lockout) JSON.stringify(lockout)
); );
} }
@@ -29,8 +29,8 @@ export type TAttachLdapAuthDTO = {
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 TUpdateLdapAuthDTO = { export type TUpdateLdapAuthDTO = {
@@ -49,8 +49,8 @@ export type TUpdateLdapAuthDTO = {
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 TGetLdapAuthDTO = { export type TGetLdapAuthDTO = {
@@ -1436,8 +1436,8 @@ export const useAddIdentityLdapAuth = () => {
accessTokenTrustedIps, accessTokenTrustedIps,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}) => { }) => {
const { data } = await apiRequest.post<{ identityLdapAuth: IdentityLdapAuth }>( const { data } = await apiRequest.post<{ identityLdapAuth: IdentityLdapAuth }>(
`/api/v1/auth/ldap-auth/identities/${identityId}`, `/api/v1/auth/ldap-auth/identities/${identityId}`,
@@ -1456,8 +1456,8 @@ export const useAddIdentityLdapAuth = () => {
accessTokenTrustedIps, accessTokenTrustedIps,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
} }
); );
return data.identityLdapAuth; return data.identityLdapAuth;
@@ -1493,8 +1493,8 @@ export const useUpdateIdentityLdapAuth = () => {
accessTokenTrustedIps, accessTokenTrustedIps,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}) => { }) => {
const { data } = await apiRequest.patch<{ identityLdapAuth: IdentityLdapAuth }>( const { data } = await apiRequest.patch<{ identityLdapAuth: IdentityLdapAuth }>(
`/api/v1/auth/ldap-auth/identities/${identityId}`, `/api/v1/auth/ldap-auth/identities/${identityId}`,
@@ -1513,8 +1513,8 @@ export const useUpdateIdentityLdapAuth = () => {
accessTokenTrustedIps, accessTokenTrustedIps,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
} }
); );
return data.identityLdapAuth; return data.identityLdapAuth;
+6 -6
View File
@@ -606,8 +606,8 @@ export type AddIdentityLdapAuthDTO = {
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
}; };
export type UpdateIdentityLdapAuthDTO = { export type UpdateIdentityLdapAuthDTO = {
@@ -633,8 +633,8 @@ export type UpdateIdentityLdapAuthDTO = {
lockoutEnabled?: boolean; lockoutEnabled?: boolean;
lockoutThreshold?: number; lockoutThreshold?: number;
lockoutDuration?: number; lockoutDurationSeconds?: number;
lockoutCounterReset?: number; lockoutCounterResetSeconds?: number;
}; };
export type DeleteIdentityLdapAuthDTO = { export type DeleteIdentityLdapAuthDTO = {
@@ -663,8 +663,8 @@ export type IdentityLdapAuth = {
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
}; };
export type ClearIdentityLdapAuthLockoutsDTO = { export type ClearIdentityLdapAuthLockoutsDTO = {
@@ -245,8 +245,8 @@ export const IdentityLdapAuthForm = ({
if (data) { if (data) {
const detectedScope = determineScope(data); const detectedScope = determineScope(data);
const lockoutDurationObj = getObjectFromSeconds(data.lockoutDuration); const lockoutDurationObj = getObjectFromSeconds(data.lockoutDurationSeconds);
const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterReset); const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterResetSeconds);
reset({ reset({
scope: detectedScope, scope: detectedScope,
@@ -334,8 +334,11 @@ export const IdentityLdapAuthForm = ({
lockoutCounterResetUnit lockoutCounterResetUnit
} = formData; } = formData;
const lockoutDuration = durationToSeconds(Number(lockoutDurationValue), lockoutDurationUnit); const lockoutDurationSeconds = durationToSeconds(
const lockoutCounterReset = durationToSeconds( Number(lockoutDurationValue),
lockoutDurationUnit
);
const lockoutCounterResetSeconds = durationToSeconds(
Number(lockoutCounterResetValue), Number(lockoutCounterResetValue),
lockoutCounterResetUnit lockoutCounterResetUnit
); );
@@ -352,8 +355,8 @@ export const IdentityLdapAuthForm = ({
accessTokenTrustedIps, accessTokenTrustedIps,
lockoutEnabled, lockoutEnabled,
lockoutThreshold: Number(lockoutThreshold), lockoutThreshold: Number(lockoutThreshold),
lockoutDuration, lockoutDurationSeconds,
lockoutCounterReset lockoutCounterResetSeconds
}; };
// Add scope-specific fields // Add scope-specific fields
@@ -1,5 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { UseMutationResult } from "@tanstack/react-query"; import { UseMutationResult } from "@tanstack/react-query";
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";
@@ -20,8 +21,8 @@ export const LockoutFields = ({
data: { data: {
lockoutEnabled: boolean; lockoutEnabled: boolean;
lockoutThreshold: number; lockoutThreshold: number;
lockoutDuration: number; lockoutDurationSeconds: number;
lockoutCounterReset: number; lockoutCounterResetSeconds: number;
}; };
}) => { }) => {
const { mutateAsync, isPending } = clearLockoutsResult; const { mutateAsync, isPending } = clearLockoutsResult;
@@ -70,10 +71,10 @@ export const LockoutFields = ({
{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>
</> </>
); );
@@ -1,6 +1,5 @@
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 { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2"; import { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
import { useTimedReset } from "@app/hooks"; import { useTimedReset } from "@app/hooks";