Merge branch 'ENG-3506' into ENG-3506-LDAP

This commit is contained in:
x032205
2025-08-26 20:45:28 -04:00
12 changed files with 87 additions and 65 deletions

View File

@@ -6,8 +6,11 @@ export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.IdentityUniversalAuth)) {
const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutEnabled");
const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutThreshold");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDuration");
const hasLockoutCounterReset = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutCounterReset");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDurationSeconds");
const hasLockoutCounterReset = await knex.schema.hasColumn(
TableName.IdentityUniversalAuth,
"lockoutCounterResetSeconds"
);
await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => {
if (!hasLockoutEnabled) {
@@ -17,10 +20,10 @@ export async function up(knex: Knex): Promise<void> {
t.integer("lockoutThreshold").notNullable().defaultTo(3);
}
if (!hasLockoutDuration) {
t.integer("lockoutDuration").notNullable().defaultTo(300); // 5 minutes (in seconds)
t.integer("lockoutDurationSeconds").notNullable().defaultTo(300); // 5 minutes
}
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)) {
const hasLockoutEnabled = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutEnabled");
const hasLockoutThreshold = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutThreshold");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDuration");
const hasLockoutCounterReset = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutCounterReset");
const hasLockoutDuration = await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "lockoutDurationSeconds");
const hasLockoutCounterReset = await knex.schema.hasColumn(
TableName.IdentityUniversalAuth,
"lockoutCounterResetSeconds"
);
await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => {
if (hasLockoutEnabled) {
@@ -41,10 +47,10 @@ export async function down(knex: Knex): Promise<void> {
t.dropColumn("lockoutThreshold");
}
if (hasLockoutDuration) {
t.dropColumn("lockoutDuration");
t.dropColumn("lockoutDurationSeconds");
}
if (hasLockoutCounterReset) {
t.dropColumn("lockoutCounterReset");
t.dropColumn("lockoutCounterResetSeconds");
}
});
}

View File

@@ -21,8 +21,8 @@ export const IdentityUniversalAuthsSchema = z.object({
accessTokenPeriod: z.coerce.number().default(0),
lockoutEnabled: z.boolean().default(true),
lockoutThreshold: z.number().default(3),
lockoutDuration: z.number().default(300),
lockoutCounterReset: z.number().default(30)
lockoutDurationSeconds: z.number().default(300),
lockoutCounterResetSeconds: z.number().default(30)
});
export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>;

View File

@@ -870,8 +870,8 @@ interface AddIdentityUniversalAuthEvent {
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
lockoutEnabled: boolean;
lockoutThreshold: number;
lockoutDuration: number;
lockoutCounterReset: number;
lockoutDurationSeconds: number;
lockoutCounterResetSeconds: number;
};
}
@@ -886,8 +886,8 @@ interface UpdateIdentityUniversalAuthEvent {
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
lockoutEnabled?: boolean;
lockoutThreshold?: number;
lockoutDuration?: number;
lockoutCounterReset?: number;
lockoutDurationSeconds?: number;
lockoutCounterResetSeconds?: number;
};
}

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.",
lockoutEnabled: "Whether the lockout feature is enabled.",
lockoutThreshold: "The amount of times login must fail before locking the identity auth method.",
lockoutDuration: "How long an identity auth method lockout lasts.",
lockoutCounterReset: "How long to wait from the most recent failed login until resetting the lockout counter."
lockoutDurationSeconds: "How long an identity auth method lockout lasts.",
lockoutCounterResetSeconds:
"How long to wait from the most recent failed login until resetting the lockout counter."
},
RETRIEVE: {
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.",
lockoutEnabled: "Whether the lockout feature is enabled.",
lockoutThreshold: "The amount of times login must fail before locking the identity auth method.",
lockoutDuration: "How long an identity auth method lockout lasts.",
lockoutCounterReset: "How long to wait from the most recent failed login until resetting the lockout counter."
lockoutDurationSeconds: "How long an identity auth method lockout lasts.",
lockoutCounterResetSeconds:
"How long to wait from the most recent failed login until resetting the lockout counter."
},
CREATE_CLIENT_SECRET: {
identityId: "The ID of the identity to create a client secret for.",

View File

@@ -140,13 +140,18 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenPeriod: z.number().int().min(0).default(0).describe(UNIVERSAL_AUTH.ATTACH.accessTokenPeriod),
lockoutEnabled: z.boolean().default(true).describe(UNIVERSAL_AUTH.ATTACH.lockoutEnabled),
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),
lockoutCounterReset: z
lockoutDurationSeconds: z
.number()
.min(30)
.max(86400)
.default(300)
.describe(UNIVERSAL_AUTH.ATTACH.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.default(30)
.describe(UNIVERSAL_AUTH.ATTACH.lockoutCounterReset)
.describe(UNIVERSAL_AUTH.ATTACH.lockoutCounterResetSeconds)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -183,8 +188,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit,
lockoutEnabled: identityUniversalAuth.lockoutEnabled,
lockoutThreshold: identityUniversalAuth.lockoutThreshold,
lockoutDuration: identityUniversalAuth.lockoutDuration,
lockoutCounterReset: identityUniversalAuth.lockoutCounterReset
lockoutDurationSeconds: identityUniversalAuth.lockoutDurationSeconds,
lockoutCounterResetSeconds: identityUniversalAuth.lockoutCounterResetSeconds
}
}
});
@@ -259,13 +264,18 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenPeriod),
lockoutEnabled: z.boolean().optional().describe(UNIVERSAL_AUTH.UPDATE.lockoutEnabled),
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),
lockoutCounterReset: z
lockoutDurationSeconds: z
.number()
.min(30)
.max(86400)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.lockoutCounterReset)
.describe(UNIVERSAL_AUTH.UPDATE.lockoutCounterResetSeconds)
})
.refine(
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
@@ -301,8 +311,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit,
lockoutEnabled: identityUniversalAuth.lockoutEnabled,
lockoutThreshold: identityUniversalAuth.lockoutThreshold,
lockoutDuration: identityUniversalAuth.lockoutDuration,
lockoutCounterReset: identityUniversalAuth.lockoutCounterReset
lockoutDurationSeconds: identityUniversalAuth.lockoutDurationSeconds,
lockoutCounterResetSeconds: identityUniversalAuth.lockoutCounterResetSeconds
}
}
});

View File

@@ -131,7 +131,7 @@ export const identityUaServiceFactory = ({
await keyStore.setItemWithExpiry(
LOCKOUT_KEY,
lockout.lockedOut ? identityUa.lockoutDuration : identityUa.lockoutCounterReset,
lockout.lockedOut ? identityUa.lockoutDurationSeconds : identityUa.lockoutCounterResetSeconds,
JSON.stringify(lockout)
);
}
@@ -251,8 +251,8 @@ export const identityUaServiceFactory = ({
accessTokenPeriod,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
}: TAttachUaDTO) => {
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
@@ -325,8 +325,8 @@ export const identityUaServiceFactory = ({
accessTokenPeriod,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
},
tx
);
@@ -349,8 +349,8 @@ export const identityUaServiceFactory = ({
actorOrgId,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
}: TUpdateUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
@@ -429,8 +429,8 @@ export const identityUaServiceFactory = ({
: undefined,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
});
return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId };
};

View File

@@ -11,8 +11,8 @@ export type TAttachUaDTO = {
isActorSuperAdmin?: boolean;
lockoutEnabled: boolean;
lockoutThreshold: number;
lockoutDuration: number;
lockoutCounterReset: number;
lockoutDurationSeconds: number;
lockoutCounterResetSeconds: number;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateUaDTO = {
@@ -25,8 +25,8 @@ export type TUpdateUaDTO = {
accessTokenTrustedIps?: { ipAddress: string }[];
lockoutEnabled?: boolean;
lockoutThreshold?: number;
lockoutDuration?: number;
lockoutCounterReset?: number;
lockoutDurationSeconds?: number;
lockoutCounterResetSeconds?: number;
} & Omit<TProjectPermission, "projectId">;
export type TGetUaDTO = {

View File

@@ -153,8 +153,8 @@ export const useAddIdentityUniversalAuth = () => {
accessTokenTrustedIps,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
}) => {
const {
data: { identityUniversalAuth }
@@ -166,8 +166,8 @@ export const useAddIdentityUniversalAuth = () => {
accessTokenTrustedIps,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
});
return identityUniversalAuth;
},
@@ -196,8 +196,8 @@ export const useUpdateIdentityUniversalAuth = () => {
accessTokenPeriod,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
}) => {
const {
data: { identityUniversalAuth }
@@ -210,8 +210,8 @@ export const useUpdateIdentityUniversalAuth = () => {
accessTokenPeriod,
lockoutEnabled,
lockoutThreshold,
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
});
return identityUniversalAuth;
},

View File

@@ -116,8 +116,8 @@ export type IdentityUniversalAuth = {
accessTokenPeriod: number;
lockoutEnabled: boolean;
lockoutThreshold: number;
lockoutDuration: number;
lockoutCounterReset: number;
lockoutDurationSeconds: number;
lockoutCounterResetSeconds: number;
};
export type AddIdentityUniversalAuthDTO = {
@@ -135,8 +135,8 @@ export type AddIdentityUniversalAuthDTO = {
}[];
lockoutEnabled: boolean;
lockoutThreshold: number;
lockoutDuration: number;
lockoutCounterReset: number;
lockoutDurationSeconds: number;
lockoutCounterResetSeconds: number;
};
export type UpdateIdentityUniversalAuthDTO = {
@@ -154,8 +154,8 @@ export type UpdateIdentityUniversalAuthDTO = {
}[];
lockoutEnabled?: boolean;
lockoutThreshold?: number;
lockoutDuration?: number;
lockoutCounterReset?: number;
lockoutDurationSeconds?: number;
lockoutCounterResetSeconds?: number;
};
export type DeleteIdentityUniversalAuthDTO = {

View File

@@ -151,8 +151,8 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
accessTokenPeriod: 0,
lockoutEnabled: true,
lockoutThreshold: 3,
lockoutDuration: 300,
lockoutCounterReset: 30
lockoutDurationSeconds: 300,
lockoutCounterResetSeconds: 30
});
handlePopUpToggle("identity", false);

View File

@@ -158,8 +158,8 @@ export const IdentityUniversalAuthForm = ({
useEffect(() => {
if (data) {
const lockoutDurationObj = getObjectFromSeconds(data.lockoutDuration);
const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterReset);
const lockoutDurationObj = getObjectFromSeconds(data.lockoutDurationSeconds);
const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterResetSeconds);
reset({
accessTokenTTL: String(data.accessTokenTTL),
@@ -222,8 +222,11 @@ export const IdentityUniversalAuthForm = ({
try {
if (!identityId) return;
const lockoutDuration = durationToSeconds(Number(lockoutDurationValue), lockoutDurationUnit);
const lockoutCounterReset = durationToSeconds(
const lockoutDurationSeconds = durationToSeconds(
Number(lockoutDurationValue),
lockoutDurationUnit
);
const lockoutCounterResetSeconds = durationToSeconds(
Number(lockoutCounterResetValue),
lockoutCounterResetUnit
);
@@ -241,8 +244,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenPeriod: Number(accessTokenPeriod),
lockoutEnabled,
lockoutThreshold: Number(lockoutThreshold),
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds,
lockoutCounterResetSeconds
});
} else {
// create new universal auth configuration
@@ -258,8 +261,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenPeriod: Number(accessTokenPeriod),
lockoutEnabled,
lockoutThreshold: Number(lockoutThreshold),
lockoutDuration,
lockoutCounterReset
lockoutDurationSeconds: Number(lockoutDurationSeconds),
lockoutCounterResetSeconds: Number(lockoutCounterResetSeconds)
});
}

View File

@@ -1,5 +1,6 @@
import { faBan, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import ms from "ms";
import { EmptyState, IconButton, Spinner, Tooltip } from "@app/components/v2";
import { useTimedReset } from "@app/hooks";