diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index f947f267c..f3c9c6aaa 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -124,9 +124,12 @@ export const accessApprovalPolicyServiceFactory = ({ const verifyAllApprovers = [...approverUserIds]; for (const groupId of groupApprovers) { - usersPromises.push(groupDAL.findAllGroupMembers({ orgId: actorOrgId, groupId, offset: 0 })); + usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 })); } - const verifyGroupApprovers = (await Promise.all(usersPromises)).flat().map((user) => user.id); + const verifyGroupApprovers = (await Promise.all(usersPromises)) + .flat() + .filter((user) => user.isPartOfGroup) + .map((user) => user.id); verifyAllApprovers.push(...verifyGroupApprovers); await verifyApprovers({ @@ -312,7 +315,7 @@ export const accessApprovalPolicyServiceFactory = ({ >[] = []; for (const groupId of groupApprovers) { - usersPromises.push(groupDAL.findAllGroupMembers({ orgId: actorOrgId, groupId, offset: 0 })); + usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 })); } const verifyGroupApprovers = (await Promise.all(usersPromises)).flat().map((user) => user.id); diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 005ecb228..298109eeb 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -58,7 +58,7 @@ type TSecretApprovalRequestServiceFactoryDep = { TAccessApprovalRequestReviewerDALFactory, "create" | "find" | "findOne" | "transaction" >; - groupDAL: Pick; + groupDAL: Pick; projectMembershipDAL: Pick; smtpService: Pick; userDAL: Pick< @@ -145,14 +145,14 @@ export const accessApprovalRequestServiceFactory = ({ const groupUsers = ( await Promise.all( approverGroupIds.map((groupApproverId) => - groupDAL.findAllGroupMembers({ + groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId: groupApproverId }) ) ) ).flat(); - approverIds.push(...groupUsers.map((user) => user.id)); + approverIds.push(...groupUsers.filter((user) => user.isPartOfGroup).map((user) => user.id)); const approverUsers = await userDAL.find({ $in: { diff --git a/backend/src/ee/services/group/group-dal.ts b/backend/src/ee/services/group/group-dal.ts index 4f8ffa664..6d4a3df79 100644 --- a/backend/src/ee/services/group/group-dal.ts +++ b/backend/src/ee/services/group/group-dal.ts @@ -60,7 +60,7 @@ export const groupDALFactory = (db: TDbClient) => { }; // special query - const findAllGroupMembers = async ({ + const findAllGroupPossibleMembers = async ({ orgId, groupId, offset = 0, @@ -125,7 +125,7 @@ export const groupDALFactory = (db: TDbClient) => { return { findGroups, findByOrgId, - findAllGroupMembers, + findAllGroupPossibleMembers, ...groupOrm }; }; diff --git a/backend/src/ee/services/group/group-service.ts b/backend/src/ee/services/group/group-service.ts index 48942c52a..c6b80135f 100644 --- a/backend/src/ee/services/group/group-service.ts +++ b/backend/src/ee/services/group/group-service.ts @@ -30,7 +30,10 @@ import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal"; type TGroupServiceFactoryDep = { userDAL: Pick; - groupDAL: Pick; + groupDAL: Pick< + TGroupDALFactory, + "create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" + >; groupProjectDAL: Pick; orgDAL: Pick; userGroupMembershipDAL: Pick< @@ -242,7 +245,7 @@ export const groupServiceFactory = ({ message: `Failed to find group with ID ${id}` }); - const users = await groupDAL.findAllGroupMembers({ + const users = await groupDAL.findAllGroupPossibleMembers({ orgId: group.orgId, groupId: group.id, offset, diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 9352d09cc..482bc4cc7 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -75,7 +75,14 @@ type TScimServiceFactoryDep = { projectMembershipDAL: Pick; groupDAL: Pick< TGroupDALFactory, - "create" | "findOne" | "findAllGroupMembers" | "delete" | "findGroups" | "transaction" | "updateById" | "update" + | "create" + | "findOne" + | "findAllGroupPossibleMembers" + | "delete" + | "findGroups" + | "transaction" + | "updateById" + | "update" >; groupProjectDAL: Pick; userGroupMembershipDAL: Pick< @@ -775,7 +782,7 @@ export const scimServiceFactory = ({ }); } - const users = await groupDAL.findAllGroupMembers({ + const users = await groupDAL.findAllGroupPossibleMembers({ orgId: group.orgId, groupId: group.id }); diff --git a/backend/src/lib/fn/string.ts b/backend/src/lib/fn/string.ts index c3651fd4e..26e8f27df 100644 --- a/backend/src/lib/fn/string.ts +++ b/backend/src/lib/fn/string.ts @@ -9,3 +9,8 @@ export const removeTrailingSlash = (str: string) => { return str.endsWith("/") ? str.slice(0, -1) : str; }; + +export const prefixWithSlash = (str: string) => { + if (str.startsWith("/")) return str; + return `/${str}`; +}; diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 276bb36ef..f67f9ec67 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { SecretFoldersSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { FOLDERS } from "@app/lib/api-docs"; -import { removeTrailingSlash } from "@app/lib/fn"; +import { prefixWithSlash, removeTrailingSlash } from "@app/lib/fn"; import { readLimit, secretsLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -26,9 +26,21 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => workspaceId: z.string().trim().describe(FOLDERS.CREATE.workspaceId), environment: z.string().trim().describe(FOLDERS.CREATE.environment), name: z.string().trim().describe(FOLDERS.CREATE.name), - path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.path), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.CREATE.path), // backward compatiability with cli - directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.directory) + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.CREATE.directory) }), response: { 200: z.object({ @@ -86,9 +98,21 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId), environment: z.string().trim().describe(FOLDERS.UPDATE.environment), name: z.string().trim().describe(FOLDERS.UPDATE.name), - path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.path), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.path), // backward compatiability with cli - directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.directory) + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.directory) }), response: { 200: z.object({ @@ -147,7 +171,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => id: z.string().describe(FOLDERS.UPDATE.folderId), environment: z.string().trim().describe(FOLDERS.UPDATE.environment), name: z.string().trim().describe(FOLDERS.UPDATE.name), - path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.path) + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.path) }) .array() .min(1) @@ -211,9 +241,21 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => body: z.object({ workspaceId: z.string().trim().describe(FOLDERS.DELETE.workspaceId), environment: z.string().trim().describe(FOLDERS.DELETE.environment), - path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.path), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.DELETE.path), // keep this here as cli need directory - directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.directory) + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.DELETE.directory) }), response: { 200: z.object({ @@ -267,9 +309,21 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => querystring: z.object({ workspaceId: z.string().trim().describe(FOLDERS.LIST.workspaceId), environment: z.string().trim().describe(FOLDERS.LIST.environment), - path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.path), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.LIST.path), // backward compatiability with cli - directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.directory) + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.LIST.directory) }), response: { 200: z.object({ diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index 1086a36ea..236963a7c 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -8,7 +8,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal"; import { getConfig } from "@app/lib/config/env"; -import { BadRequestError } from "@app/lib/errors"; +import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal"; @@ -129,7 +129,7 @@ export const projectMembershipServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member); const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { username }); - if (!membership) throw new BadRequestError({ message: `Project membership not found for user ${username}` }); + if (!membership) throw new NotFoundError({ message: `Project membership not found for user ${username}` }); return membership; }; diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index a3136e2a8..c2ddd2fbe 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -189,6 +189,22 @@ export const useGetImportedSecretsAllEnvs = ({ })) }); + const getEnvImportedSecretKeyCount = useCallback( + (env: string) => { + const selectedEnvIndex = environments.indexOf(env); + let totalSecrets = 0; + + if (selectedEnvIndex !== -1) { + secretImports?.[selectedEnvIndex]?.data?.forEach((secret) => { + totalSecrets += secret.secrets.length; + }); + } + + return totalSecrets; + }, + [(secretImports || []).map((response) => response.data)] + ); + const isImportedSecretPresentInEnv = useCallback( (envSlug: string, secretName: string) => { const selectedEnvIndex = environments.indexOf(envSlug); @@ -226,7 +242,12 @@ export const useGetImportedSecretsAllEnvs = ({ [(secretImports || []).map((response) => response.data)] ); - return { secretImports, isImportedSecretPresentInEnv, getImportedSecretByKey }; + return { + secretImports, + isImportedSecretPresentInEnv, + getImportedSecretByKey, + getEnvImportedSecretKeyCount + }; }; export const useGetImportedFoldersByEnv = ({ diff --git a/frontend/src/views/Org/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/views/Org/AuditLogsPage/AuditLogsPage.tsx index 2b6ec6744..aa55e8fbb 100644 --- a/frontend/src/views/Org/AuditLogsPage/AuditLogsPage.tsx +++ b/frontend/src/views/Org/AuditLogsPage/AuditLogsPage.tsx @@ -1,3 +1,4 @@ +import { NoticeBanner } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { withPermission } from "@app/hoc"; @@ -10,6 +11,14 @@ export const AuditLogsPage = withPermission(

Audit Logs

+ {(window.location.origin.includes("https://app.infisical.com") || + window.location.origin.includes("https://gamma.infisical.com")) && ( + + We are currently working on improving the performance of querying audit logs. + However, please note that audit logs are still being published as usual, so there’s + no disruption to log generation. + + )}
diff --git a/frontend/src/views/Org/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/views/Org/AuditLogsPage/components/LogsFilter.tsx index 9ea92f9a9..bc99057ab 100644 --- a/frontend/src/views/Org/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Org/AuditLogsPage/components/LogsFilter.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-nested-ternary */ -import { useState } from "react"; -import { Control, Controller, UseFormReset, UseFormWatch } from "react-hook-form"; +import { useEffect, useState } from "react"; +import { Control, Controller, UseFormReset, UseFormSetValue, UseFormWatch } from "react-hook-form"; import { faCheckCircle, faChevronDown, @@ -41,6 +41,7 @@ type Props = { }; className?: string; isOrgAuditLogs?: boolean; + setValue: UseFormSetValue; control: Control; reset: UseFormReset; watch: UseFormWatch; @@ -51,6 +52,7 @@ export const LogsFilter = ({ isOrgAuditLogs, className, control, + setValue, reset, watch }: Props) => { @@ -60,6 +62,12 @@ export const LogsFilter = ({ const { currentWorkspace, workspaces } = useWorkspace(); const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?.id ?? ""); + useEffect(() => { + if (workspaces.length) { + setValue("projectId", workspaces[0].id); + } + }, [workspaces]); + const renderActorSelectItem = (actor: Actor) => { switch (actor.type) { case ActorType.USER: @@ -243,20 +251,14 @@ export const LogsFilter = ({ className="w-40" >