diff --git a/backend/src/db/migrations/20250502201443_invalidate-cache-status-superadmin.ts b/backend/src/db/migrations/20250502201443_invalidate-cache-status-superadmin.ts deleted file mode 100644 index a3a5805c1..000000000 --- a/backend/src/db/migrations/20250502201443_invalidate-cache-status-superadmin.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Knex } from "knex"; - -import { TableName } from "../schemas"; - -export async function up(knex: Knex): Promise { - const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "invalidatingCache"); - if (!hasColumn) { - await knex.schema.alterTable(TableName.SuperAdmin, (t) => { - t.boolean("invalidatingCache").notNullable().defaultTo(false); - }); - } -} - -export async function down(knex: Knex): Promise { - const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "invalidatingCache"); - if (hasColumn) { - await knex.schema.alterTable(TableName.SuperAdmin, (t) => { - t.dropColumn("invalidatingCache"); - }); - } -} diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index 18e45a5f0..ec35042ad 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -29,8 +29,7 @@ export const SuperAdminSchema = z.object({ adminIdentityIds: z.string().array().nullable().optional(), encryptedMicrosoftTeamsAppId: zodBuffer.nullable().optional(), encryptedMicrosoftTeamsClientSecret: zodBuffer.nullable().optional(), - encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional(), - invalidatingCache: z.boolean().default(false) + encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional() }); export type TSuperAdmin = z.infer; diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts index 5c0d97ee8..a25482c8c 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts @@ -219,7 +219,7 @@ export const parseRotationErrorMessage = (err: unknown): string => { if (err instanceof AxiosError) { errorMessage += err?.response?.data ? JSON.stringify(err?.response?.data) - : err?.message ?? "An unknown error occurred."; + : (err?.message ?? "An unknown error occurred."); } else { errorMessage += (err as Error)?.message || "An unknown error occurred."; } diff --git a/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts b/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts index 312b7966b..d58644d90 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts @@ -282,7 +282,7 @@ export const sshCertificateAuthorityServiceFactory = ({ // set [keyId] depending on if [allowCustomKeyIds] is true or false const keyId = sshCertificateTemplate.allowCustomKeyIds - ? requestedKeyId ?? `${actor}-${actorId}` + ? (requestedKeyId ?? `${actor}-${actorId}`) : `${actor}-${actorId}`; const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId }); @@ -404,7 +404,7 @@ export const sshCertificateAuthorityServiceFactory = ({ // set [keyId] depending on if [allowCustomKeyIds] is true or false const keyId = sshCertificateTemplate.allowCustomKeyIds - ? requestedKeyId ?? `${actor}-${actorId}` + ? (requestedKeyId ?? `${actor}-${actorId}`) : `${actor}-${actorId}`; const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId }); diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 14aa8f038..2e3f19a2c 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -401,8 +401,8 @@ export const authLoginServiceFactory = ({ } const shouldCheckMfa = selectedOrg.enforceMfa || user.isMfaEnabled; - const orgMfaMethod = selectedOrg.enforceMfa ? selectedOrg.selectedMfaMethod ?? MfaMethod.EMAIL : undefined; - const userMfaMethod = user.isMfaEnabled ? user.selectedMfaMethod ?? MfaMethod.EMAIL : undefined; + const orgMfaMethod = selectedOrg.enforceMfa ? (selectedOrg.selectedMfaMethod ?? MfaMethod.EMAIL) : undefined; + const userMfaMethod = user.isMfaEnabled ? (user.selectedMfaMethod ?? MfaMethod.EMAIL) : undefined; const mfaMethod = orgMfaMethod ?? userMfaMethod; if (shouldCheckMfa && (!decodedToken.isMfaVerified || decodedToken.mfaMethod !== mfaMethod)) { diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index f5737edb3..5749852d7 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -291,7 +291,7 @@ export const parseSyncErrorMessage = (err: unknown): string => { } else if (err instanceof AxiosError) { errorMessage = err?.response?.data ? JSON.stringify(err?.response?.data) - : err?.message ?? "An unknown error occurred."; + : (err?.message ?? "An unknown error occurred."); } else { errorMessage = (err as Error)?.message || "An unknown error occurred."; } diff --git a/backend/src/services/super-admin/invalidate-cache-queue.ts b/backend/src/services/super-admin/invalidate-cache-queue.ts index 0ac195343..c2a12f5d5 100644 --- a/backend/src/services/super-admin/invalidate-cache-queue.ts +++ b/backend/src/services/super-admin/invalidate-cache-queue.ts @@ -21,7 +21,7 @@ export const invalidateCacheQueueFactory = ({ queueService, keyStore }: TInvalid await queueService.queue(QueueName.InvalidateCache, QueueJobs.InvalidateCache, dto, { removeOnComplete: true, removeOnFail: true, - jobId: "invalidate-cache" + jobId: `invalidate-cache-${dto.data.type}` }); }; diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index b21b97911..7c9ca4f38 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -176,8 +176,8 @@ export const superAdminServiceFactory = ({ const canServerAdminAccessAfterApply = data.enabledLoginMethods.some((loginMethod) => - loginMethodToAuthMethod[loginMethod as LoginMethod].some( - (authMethod) => superAdminUser.authMethods?.includes(authMethod) + loginMethodToAuthMethod[loginMethod as LoginMethod].some((authMethod) => + superAdminUser.authMethods?.includes(authMethod) ) ) || isUserSamlAccessEnabled || diff --git a/frontend/src/pages/admin/OverviewPage/components/CachingPanel.tsx b/frontend/src/pages/admin/OverviewPage/components/CachingPanel.tsx index 49d6d9107..f46aa4cca 100644 --- a/frontend/src/pages/admin/OverviewPage/components/CachingPanel.tsx +++ b/frontend/src/pages/admin/OverviewPage/components/CachingPanel.tsx @@ -1,14 +1,15 @@ +/* eslint-disable no-return-assign, consistent-return */ import { useEffect, useRef, useState } from "react"; +import { faRotate } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { Badge, Button, DeleteActionModal } from "@app/components/v2"; import { useOrgPermission } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useInvalidateCache } from "@app/hooks/api"; -import { CacheType } from "@app/hooks/api/admin/types"; import { useGetInvalidatingCacheStatus } from "@app/hooks/api/admin/queries"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faRotate } from "@fortawesome/free-solid-svg-icons"; +import { CacheType } from "@app/hooks/api/admin/types"; export const CachingPanel = () => { const { mutateAsync: invalidateCache } = useInvalidateCache(); @@ -17,6 +18,9 @@ export const CachingPanel = () => { const { membership } = useOrgPermission(); const ignoreInitial = useRef(true); + const timeoutRef = useRef(null); + const pollingRef = useRef(null); + const [type, setType] = useState(null); const [buttonsDisabled, setButtonsDisabled] = useState(false); @@ -24,21 +28,14 @@ export const CachingPanel = () => { "invalidateCache" ] as const); - const success = () => { - createNotification({ - text: `Successfully invalidated cache`, - type: "success" - }); - setButtonsDisabled(false); + const disableButtonsTemporarily = () => { + setButtonsDisabled(true); + timeoutRef.current = setTimeout(() => setButtonsDisabled(false), 10000); }; - const timeoutRef = useRef(null); - const disableButtons = () => { - // Enable buttons after 10 seconds, even if still invalidating - setButtonsDisabled(true); - timeoutRef.current = setTimeout(() => { - setButtonsDisabled(false); - }, 10000); + const success = () => { + createNotification({ text: "Successfully invalidated cache", type: "success" }); + setButtonsDisabled(false); }; const handleInvalidateCacheSubmit = async () => { @@ -46,13 +43,8 @@ export const CachingPanel = () => { try { await invalidateCache({ type }); - - createNotification({ - text: `Began invalidating ${type} cache`, - type: "success" - }); - - disableButtons(); + createNotification({ text: `Began invalidating ${type} cache`, type: "success" }); + disableButtonsTemporarily(); handlePopUpClose("invalidateCache"); if (!(await refetchInvalidatingStatus()).data) { @@ -61,59 +53,45 @@ export const CachingPanel = () => { } } catch (err) { console.error(err); - createNotification({ - text: `Failed to invalidate ${type} cache`, - type: "error" - }); + createNotification({ text: `Failed to invalidate ${type} cache`, type: "error" }); } setType(null); }; - const pollingRef = useRef(null); - - // Update the "invalidating cache" status useEffect(() => { - if (!isInvalidating) return; - - if (pollingRef.current) clearInterval(pollingRef.current); - if (timeoutRef.current) clearTimeout(timeoutRef.current); - - // Start polling every 3 seconds - pollingRef.current = setInterval(async () => { - try { - await refetchInvalidatingStatus(); - } catch (err) { - console.error("Polling error:", err); - } - }, 3000); - - disableButtons(); - - return () => { - if (pollingRef.current) clearInterval(pollingRef.current); - if (timeoutRef.current) clearTimeout(timeoutRef.current); - }; - }, [isInvalidating]); - - // Helper to ignore the initial useEffect calls for isInvalidating - useEffect(() => { - const timer = setTimeout(() => { - ignoreInitial.current = false; - }, 1000); - + const timer = setTimeout(() => (ignoreInitial.current = false), 1000); return () => clearTimeout(timer); }, []); + useEffect(() => { + if (!isInvalidating) return; + + clearInterval(pollingRef.current!); + clearTimeout(timeoutRef.current!); + + pollingRef.current = setInterval(() => { + refetchInvalidatingStatus().catch((err) => console.error("Polling error:", err)); + }, 3000); + + disableButtonsTemporarily(); + + return () => { + clearInterval(pollingRef.current!); + clearTimeout(timeoutRef.current!); + }; + }, [isInvalidating]); + useEffect(() => { if (!ignoreInitial.current && isInvalidating === false) { success(); - - if (pollingRef.current) clearInterval(pollingRef.current); - if (timeoutRef.current) clearTimeout(timeoutRef.current); + clearInterval(pollingRef.current!); + clearTimeout(timeoutRef.current!); } }, [isInvalidating]); + const isAdmin = membership?.role === "admin"; + return ( <>
@@ -142,35 +120,12 @@ export const CachingPanel = () => { setType(CacheType.SECRETS); handlePopUpOpen("invalidateCache"); }} - isDisabled={Boolean(membership && membership.role !== "admin") || buttonsDisabled} + isDisabled={!isAdmin || buttonsDisabled} > Invalidate Secrets Cache
- {/* Uncomment this when we have more than one cache type */} - {/*
-
- All Cache - - All cache refers to the entirety of cached data throughout the system, including secrets - and miscellaneous information. - -
- - -
*/} -