diff --git a/backend/src/ee/routes/v1/project-router.ts b/backend/src/ee/routes/v1/project-router.ts index 294150c83..3870123fd 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas"; import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; +import { removeTrailingSlash } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -15,7 +16,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), querystring: z.object({ environment: z.string().trim(), - path: z.string().trim().default("/"), + path: z.string().trim().default("/").transform(removeTrailingSlash), offset: z.coerce.number().default(0), limit: z.coerce.number().default(20) }), @@ -46,7 +47,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), querystring: z.object({ environment: z.string().trim(), - path: z.string().trim().default("/") + path: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ diff --git a/backend/src/ee/routes/v1/secret-rotation-router.ts b/backend/src/ee/routes/v1/secret-rotation-router.ts index 95eb0bc88..062c51980 100644 --- a/backend/src/ee/routes/v1/secret-rotation-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas"; +import { removeTrailingSlash } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -11,7 +12,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = schema: { body: z.object({ workspaceId: z.string().trim(), - secretPath: z.string().trim(), + secretPath: z.string().trim().transform(removeTrailingSlash), environment: z.string().trim(), interval: z.number().min(1), provider: z.string().trim(), diff --git a/backend/src/lib/fn/string.ts b/backend/src/lib/fn/string.ts index 27d84357d..2d7466c35 100644 --- a/backend/src/lib/fn/string.ts +++ b/backend/src/lib/fn/string.ts @@ -3,3 +3,9 @@ import path from "path"; // given two paths irrespective of ending with / or not // this will return true if its equal export const isSamePath = async (from: string, to: string) => !path.relative(from, to); + +export const removeTrailingSlash = (str: string) => { + if (str === "/") return str; + + return str.endsWith("/") ? str.slice(0, -1) : str; +}; diff --git a/backend/src/server/plugins/error-handler.ts b/backend/src/server/plugins/error-handler.ts index da3162ef8..cacc91885 100644 --- a/backend/src/server/plugins/error-handler.ts +++ b/backend/src/server/plugins/error-handler.ts @@ -15,15 +15,15 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider req.log.error(error); if (error instanceof BadRequestError) { res.status(400).send({ statusCode: 400, message: error.message, error: error.name }); - } else if (error instanceof UnauthorizedError || error instanceof ForbiddenRequestError) { + } else if (error instanceof UnauthorizedError) { res.status(403).send({ statusCode: 403, message: error.message, error: error.name }); } else if (error instanceof DatabaseError || error instanceof InternalServerError) { res.status(500).send({ statusCode: 500, message: "Something went wrong", error: error.name }); } else if (error instanceof ZodError) { res.status(403).send({ statusCode: 403, error: "ValidationFailure", message: error.issues }); } else if (error instanceof ForbiddenError) { - res.status(403).send({ - statusCode: 403, + res.status(401).send({ + statusCode: 401, error: "PermissionDenied", message: `You are not allowed to ${error.action} on ${error.subjectType}` }); diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index 23d8ef8be..db73bf293 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { IntegrationsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; -import { shake } from "@app/lib/fn"; +import { removeTrailingSlash, shake } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -16,7 +16,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { app: z.string().trim().optional(), isActive: z.boolean(), appId: z.string().trim().optional(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), sourceEnvironment: z.string().trim(), targetEnvironment: z.string().trim().optional(), targetEnvironmentId: z.string().trim().optional(), @@ -89,7 +89,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { app: z.string().trim(), appId: z.string().trim(), isActive: z.boolean(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), targetEnvironment: z.string().trim(), owner: z.string().trim(), environment: z.string().trim() diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index fd9798dac..c71767868 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { SecretFoldersSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { removeTrailingSlash } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -14,9 +15,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => workspaceId: z.string().trim(), environment: z.string().trim(), name: z.string().trim(), - path: z.string().trim().default("/"), + path: z.string().trim().default("/").transform(removeTrailingSlash), // backward compatiability with cli - directory: z.string().trim().default("/") + directory: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ @@ -68,9 +69,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => workspaceId: z.string().trim(), environment: z.string().trim(), name: z.string().trim(), - path: z.string().trim().default("/"), + path: z.string().trim().default("/").transform(removeTrailingSlash), // backward compatiability with cli - directory: z.string().trim().default("/") + directory: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ @@ -122,9 +123,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - path: z.string().trim().default("/"), + path: z.string().trim().default("/").transform(removeTrailingSlash), // keep this here as cli need directory - directory: z.string().trim().default("/") + directory: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ @@ -172,9 +173,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - path: z.string().trim().default("/"), + path: z.string().trim().default("/").transform(removeTrailingSlash), // backward compatiability with cli - directory: z.string().trim().default("/") + directory: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v1/secret-import-router.ts index 5f37c5f5e..f27f9e0c2 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v1/secret-import-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { removeTrailingSlash } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -13,10 +14,10 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - path: z.string().trim().default("/"), + path: z.string().trim().default("/").transform(removeTrailingSlash), import: z.object({ environment: z.string().trim(), - path: z.string().trim() + path: z.string().trim().transform(removeTrailingSlash) }) }), response: { @@ -74,10 +75,14 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - path: z.string().trim().default("/"), + path: z.string().trim().default("/").transform(removeTrailingSlash), import: z.object({ environment: z.string().trim().optional(), - path: z.string().trim().optional(), + path: z + .string() + .trim() + .optional() + .transform((val) => (val ? removeTrailingSlash(val) : val)), position: z.number().optional() }) }), @@ -137,7 +142,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - path: z.string().trim().default("/") + path: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ @@ -191,7 +196,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - path: z.string().trim().default("/") + path: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ @@ -243,7 +248,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - path: z.string().trim().default("/") + path: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 89564d9a3..558192f68 100644 --- a/backend/src/server/routes/v1/webhook-router.ts +++ b/backend/src/server/routes/v1/webhook-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { WebhooksSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { removeTrailingSlash } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -33,7 +34,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { environment: z.string().trim(), webhookUrl: z.string().url().trim(), webhookSecretKey: z.string().trim().optional(), - secretPath: z.string().trim().default("/") + secretPath: z.string().trim().default("/").transform(removeTrailingSlash) }), response: { 200: z.object({ @@ -182,7 +183,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim().optional(), - secretPath: z.string().trim().optional() + secretPath: z.string().trim().optional().transform((val)=> val?removeTrailingSlash(val):val) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v2/service-token-router.ts b/backend/src/server/routes/v2/service-token-router.ts index 950720c4d..31f5c22d6 100644 --- a/backend/src/server/routes/v2/service-token-router.ts +++ b/backend/src/server/routes/v2/service-token-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { ServiceTokensSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { removeTrailingSlash } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -42,7 +43,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => scopes: z .object({ environment: z.string().trim(), - secretPath: z.string().trim() + secretPath: z.string().trim().transform(removeTrailingSlash) }) .array() .min(1), diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 04d187aef..cdb92fae6 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -12,6 +12,7 @@ import { import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types"; import { BadRequestError } from "@app/lib/errors"; +import { removeTrailingSlash } from "@app/lib/fn"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { ActorType, AuthMode } from "@app/services/auth/auth-type"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; @@ -39,7 +40,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { querystring: z.object({ workspaceId: z.string().trim().optional(), environment: z.string().trim().optional(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), include_imports: z .enum(["true", "false"]) .default("false") @@ -129,7 +130,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { querystring: z.object({ workspaceId: z.string().trim().optional(), environment: z.string().trim().optional(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), version: z.coerce.number().optional(), type: z.nativeEnum(SecretType).default(SecretType.Shared), include_imports: z @@ -216,7 +217,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secretValue: z .string() .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())), @@ -256,7 +257,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { event: { type: EventType.CREATE_SECRET, metadata: { - environment: req.body.environment, + environment: req.body.environment, secretPath: req.body.secretPath, secretId: secret.id, secretKey: req.params.secretName, @@ -295,7 +296,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretValue: z .string() .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), skipMultilineEncoding: z.boolean().optional(), type: z.nativeEnum(SecretType).default(SecretType.Shared) }), @@ -365,7 +366,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), type: z.nativeEnum(SecretType).default(SecretType.Shared) }), response: { @@ -430,7 +431,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), include_imports: z .enum(["true", "false"]) .default("false") @@ -518,7 +519,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), type: z.nativeEnum(SecretType).default(SecretType.Shared), version: z.coerce.number().optional(), include_imports: z @@ -590,7 +591,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: z.string().trim(), environment: z.string().trim(), type: z.nativeEnum(SecretType).default(SecretType.Shared), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secretKeyCiphertext: z.string().trim(), secretKeyIV: z.string().trim(), secretKeyTag: z.string().trim(), @@ -758,7 +759,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { environment: z.string().trim(), secretId: z.string().trim().optional(), type: z.nativeEnum(SecretType).default(SecretType.Shared), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secretValueCiphertext: z.string().trim(), secretValueIV: z.string().trim(), secretValueTag: z.string().trim(), @@ -935,7 +936,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }), body: z.object({ type: z.nativeEnum(SecretType).default(SecretType.Shared), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secretId: z.string().trim().optional(), workspaceId: z.string().trim(), environment: z.string().trim() @@ -1050,7 +1051,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secrets: z .object({ secretName: z.string().trim(), @@ -1176,7 +1177,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secrets: z .object({ secretName: z.string().trim(), @@ -1301,7 +1302,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), - secretPath: z.string().trim().default("/"), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secrets: z .object({ secretName: z.string().trim(), diff --git a/backend/src/services/secret-blind-index/secret-blind-index-service.ts b/backend/src/services/secret-blind-index/secret-blind-index-service.ts index 215e54992..0c18ddedc 100644 --- a/backend/src/services/secret-blind-index/secret-blind-index-service.ts +++ b/backend/src/services/secret-blind-index/secret-blind-index-service.ts @@ -29,10 +29,7 @@ export const secretBlindIndexServiceFactory = ({ projectId, actorId }: TGetProjectBlindIndexStatusDTO) => { - const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); - if (membership?.role !== ProjectMembershipRole.Admin) { - throw new UnauthorizedError({ message: "User must be admin" }); - } + await permissionService.getProjectPermission(actor, actorId, projectId); const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId); return Number(secretCount); diff --git a/backend/src/services/secret-folder/secret-folder-dal.ts b/backend/src/services/secret-folder/secret-folder-dal.ts index 6129cfa9d..3d25217cb 100644 --- a/backend/src/services/secret-folder/secret-folder-dal.ts +++ b/backend/src/services/secret-folder/secret-folder-dal.ts @@ -8,7 +8,7 @@ import { TSecretFoldersUpdate } from "@app/db/schemas"; import { BadRequestError, DatabaseError } from "@app/lib/errors"; -import { groupBy } from "@app/lib/fn"; +import { groupBy, removeTrailingSlash } from "@app/lib/fn"; import { ormify, selectAllTableCols } from "@app/lib/knex"; export const validateFolderName = (folderName: string) => { @@ -238,10 +238,15 @@ export const secretFolderDALFactory = (db: TDbClient) => { tx?: Knex ) => { try { - const folder = await sqlFindFolderByPathQuery(tx || db, projectId, environment, path) + const folder = await sqlFindFolderByPathQuery( + tx || db, + projectId, + environment, + removeTrailingSlash(path) + ) .orderBy("depth", "desc") .first(); - if (folder && folder.path !== path) { + if (folder && folder.path !== removeTrailingSlash(path)) { return; } if (!folder) return; @@ -262,7 +267,12 @@ export const secretFolderDALFactory = (db: TDbClient) => { tx?: Knex ) => { try { - const folder = await sqlFindFolderByPathQuery(tx || db, projectId, environment, path) + const folder = await sqlFindFolderByPathQuery( + tx || db, + projectId, + environment, + removeTrailingSlash(path) + ) .orderBy("depth", "desc") .first(); if (!folder) return; @@ -278,8 +288,12 @@ export const secretFolderDALFactory = (db: TDbClient) => { tx?: Knex ) => { try { - const folders = await sqlFindMultipleFolderByEnvPathQuery(tx || db, query); - return query.map(({ envId, secretPath }) => + const formatedQuery = query.map(({ secretPath, envId }) => ({ + envId, + secretPath: removeTrailingSlash(secretPath) + })); + const folders = await sqlFindMultipleFolderByEnvPathQuery(tx || db, formatedQuery); + return formatedQuery.map(({ envId, secretPath }) => folders.find( ({ path: targetPath, envId: targetEnvId }) => targetPath === secretPath && targetEnvId === envId diff --git a/frontend/src/components/permissions/ProjectPermissionCan.tsx b/frontend/src/components/permissions/ProjectPermissionCan.tsx index aa64c33d7..f1af141f2 100644 --- a/frontend/src/components/permissions/ProjectPermissionCan.tsx +++ b/frontend/src/components/permissions/ProjectPermissionCan.tsx @@ -25,7 +25,7 @@ export const ProjectPermissionCan: FunctionComponent = ({ allowedLabel, ...props }) => { - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); return ( {(isAllowed, ability) => { diff --git a/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx b/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx index cb6ef9b97..a7023a867 100644 --- a/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx +++ b/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx @@ -1,6 +1,7 @@ import { createContext, ReactNode, useContext } from "react"; import { useGetUserProjectPermissions } from "@app/hooks/api"; +import { TProjectMembership } from "@app/hooks/api/users/types"; import { useWorkspace } from "../WorkspaceContext"; import { TProjectPermission } from "./types"; @@ -9,7 +10,10 @@ type Props = { children: ReactNode; }; -const ProjectPermissionContext = createContext(null); +const ProjectPermissionContext = createContext(null); export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => { const { currentWorkspace, isLoading: isWsLoading } = useWorkspace(); diff --git a/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx b/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx index 911703973..103ff61b7 100644 --- a/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx +++ b/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx @@ -21,7 +21,7 @@ export const withProjectPermission = ["abilities"]> ) => { const HOC = (hocProps: T) => { - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); // akhilmhdh: Set as any due to casl/react ts type bug // REASON: casl due to its type checking can't seem to union even if union intersection is applied @@ -29,13 +29,13 @@ export const withProjectPermission =
@@ -43,7 +43,7 @@ export const withProjectPermission =
-
Permission Denied
+
Permission Denied
You do not have permission to this page.
Kindly contact your organization administrator diff --git a/frontend/src/hooks/api/roles/queries.tsx b/frontend/src/hooks/api/roles/queries.tsx index 38d020491..8647353cb 100644 --- a/frontend/src/hooks/api/roles/queries.tsx +++ b/frontend/src/hooks/api/roles/queries.tsx @@ -8,7 +8,7 @@ import { apiRequest } from "@app/config/request"; import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext/types"; -import { OrgUser } from "../users/types"; +import { OrgUser, TProjectMembership } from "../users/types"; import { TGetUserOrgPermissionsDTO, TGetUserProjectPermissionDTO, @@ -104,10 +104,13 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) = const getUserProjectPermissions = async ({ workspaceId }: TGetUserProjectPermissionDTO) => { const { data } = await apiRequest.get<{ - data: { permissions: PackRule>>[] }; + data: { + permissions: PackRule>>[]; + membership: TProjectMembership; + }; }>(`/api/v1/workspace/${workspaceId}/permissions`, {}); - return data.data.permissions; + return data.data; }; export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPermissionDTO) => @@ -116,8 +119,8 @@ export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPer queryFn: () => getUserProjectPermissions({ workspaceId }), enabled: Boolean(workspaceId), select: (data) => { - const rule = unpackRules>>(data); + const rule = unpackRules>>(data.permissions); const ability = createMongoAbility(rule, { conditionsMatcher }); - return ability; + return { permission: ability, membership: data.membership }; } }); diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 47c733885..ebddefb14 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -52,6 +52,15 @@ export type OrgUser = { roleId: string; }; +export type TProjectMembership = { + id: string; + role: string; + createdAt: string; + updatedAt: string; + projectId: string; + roleId: string; +}; + export type TWorkspaceUser = OrgUser; export type AddUserToWsDTO = { diff --git a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx index b0abc2603..a1b9787dd 100644 --- a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx @@ -30,7 +30,7 @@ export const CloudIntegrationSection = ({ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "deleteConfirmation" ] as const); - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); const { createNotification } = useNotificationContext(); const isEmpty = !isLoading && !cloudIntegrations?.length; @@ -43,7 +43,7 @@ export const CloudIntegrationSection = ({

{t("integrations.cloud-integrations")}

{t("integrations.click-to-start")}

-
+
{isLoading && Array.from({ length: 12 }).map((_, index) => ( diff --git a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx index 27f649bdd..11927a2b1 100644 --- a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx +++ b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx @@ -44,7 +44,7 @@ export const SecretApprovalPolicyList = ({ workspaceId }: Props) => { "deletePolicy", "upgradePlan" ] as const); - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); const { subscription } = useSubscription(); const { createNotification } = useNotificationContext(); diff --git a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretApprovalPolicyRow.tsx b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretApprovalPolicyRow.tsx index 072156b61..c256af1c7 100644 --- a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretApprovalPolicyRow.tsx +++ b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretApprovalPolicyRow.tsx @@ -36,7 +36,7 @@ export const SecretApprovalPolicyRow = ({ }: Props) => { const [selectedApprovers, setSelectedApprovers] = useState([]); const { mutate: updateSecretApprovalPolicy, isLoading } = useUpdateSecretApprovalPolicy(); - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); return ( diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.tsx index d3c1a5b93..f538a4c0a 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.tsx @@ -48,7 +48,7 @@ export const SecretMainPage = () => { const { t } = useTranslation(); const { currentWorkspace } = useWorkspace(); const router = useRouter(); - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); const [isVisible, setIsVisible] = useState(false); const [sortDir, setSortDir] = useState(SortDir.ASC); diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx index a86a6b096..1130c9d14 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx @@ -81,7 +81,7 @@ export const SecretDetailSidebar = ({ resolver: zodResolver(formSchema), values: secret }); - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); const cannotEditSecret = permission.cannot( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx index aec0573ef..ecd35abf8 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx @@ -85,7 +85,7 @@ export const SecretItem = memo( secretPath }: Props) => { const { currentWorkspace } = useWorkspace(); - const permission = useProjectPermission(); + const { permission } = useProjectPermission(); const isReadOnly = permission.can( ProjectPermissionActions.Read, diff --git a/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx b/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx index 2210171b0..329153b1d 100644 --- a/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx +++ b/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx @@ -4,9 +4,15 @@ import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto"; import { Button, Spinner } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { + ProjectPermissionActions, + ProjectPermissionSub, + useProjectPermission, + useWorkspace +} from "@app/context"; import { useToggle } from "@app/hooks"; import { useGetWorkspaceIndexStatus, useNameWorkspaceSecrets } from "@app/hooks/api"; +import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { UserWsKeyPair } from "@app/hooks/api/types"; import { fetchWorkspaceSecrets } from "@app/hooks/api/workspace/queries"; @@ -18,6 +24,7 @@ type Props = { export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => { const { currentWorkspace } = useWorkspace(); + const { membership } = useProjectPermission(); const { data: isBlindIndexed, isLoading: isBlindIndexedLoading } = useGetWorkspaceIndexStatus( currentWorkspace?.id ?? "" ); @@ -74,17 +81,19 @@ export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => {
)} -

Enable Blind Indices

+

Action Required

Your project was created before the introduction of blind indexing. To continue accessing secrets by name through the SDK, public API and web dashboard, please enable blind indexing.{" "} - This is a one time process. + + {membership.role !== ProjectMembershipRole.Admin && "This is an admin only operation."} +

{(isAllowed) => (