From 02dc2e21060caa128c774bc83e26f948366d7db7 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 12 Sep 2025 22:59:57 +0530 Subject: [PATCH] feat: updated folder, import, approvals and some more to projectid --- ...preciated-secret-approval-policy-router.ts | 293 +++++++++++ backend/src/ee/routes/v1/index.ts | 4 +- .../v1/secret-approval-request-router.ts | 8 +- backend/src/ee/routes/v2/index.ts | 3 + .../secret-approval-policy-router.ts | 10 +- backend/src/lib/api-docs/constants.ts | 16 +- .../v1/depreciated-secret-folder-router.ts | 444 ++++++++++++++++ .../v1/depreciated-secret-import-router.ts | 472 ++++++++++++++++++ backend/src/server/routes/v1/index.ts | 8 +- .../src/server/routes/v1/webhook-router.ts | 9 +- backend/src/server/routes/v2/index.ts | 5 + .../routes/{v1 => v2}/secret-folder-router.ts | 85 +--- .../routes/{v1 => v2}/secret-import-router.ts | 79 +-- .../src/hooks/api/secretApproval/mutation.tsx | 22 +- .../src/hooks/api/secretApproval/queries.tsx | 35 +- .../src/hooks/api/secretApproval/types.ts | 12 +- .../api/secretApprovalRequest/mutation.tsx | 10 +- .../api/secretApprovalRequest/queries.tsx | 32 +- .../hooks/api/secretApprovalRequest/types.ts | 10 +- .../src/hooks/api/secretFolders/queries.tsx | 20 +- .../src/hooks/api/secretImports/mutation.tsx | 16 +- .../src/hooks/api/secretImports/queries.tsx | 18 +- frontend/src/hooks/api/webhooks/mutation.tsx | 20 +- frontend/src/hooks/api/webhooks/types.ts | 8 +- .../components/WebhooksTab/WebhooksTab.tsx | 8 +- 25 files changed, 1380 insertions(+), 267 deletions(-) create mode 100644 backend/src/ee/routes/v1/depreciated-secret-approval-policy-router.ts rename backend/src/ee/routes/{v1 => v2}/secret-approval-policy-router.ts (97%) create mode 100644 backend/src/server/routes/v1/depreciated-secret-folder-router.ts create mode 100644 backend/src/server/routes/v1/depreciated-secret-import-router.ts rename backend/src/server/routes/{v1 => v2}/secret-folder-router.ts (80%) rename backend/src/server/routes/{v1 => v2}/secret-import-router.ts (85%) diff --git a/backend/src/ee/routes/v1/depreciated-secret-approval-policy-router.ts b/backend/src/ee/routes/v1/depreciated-secret-approval-policy-router.ts new file mode 100644 index 000000000..8b03f2d7e --- /dev/null +++ b/backend/src/ee/routes/v1/depreciated-secret-approval-policy-router.ts @@ -0,0 +1,293 @@ +import { nanoid } from "nanoid"; +import { z } from "zod"; + +import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types"; +import { removeTrailingSlash } from "@app/lib/fn"; +import { EnforcementLevel } from "@app/lib/types"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { sapPubSchema } from "@app/server/routes/sanitizedSchemas"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerDepreciatedSecretApprovalPolicyRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/", + method: "POST", + config: { + rateLimit: writeLimit + }, + schema: { + body: z + .object({ + workspaceId: z.string(), + name: z.string().optional(), + environment: z.string().optional(), + environments: z.string().array().optional(), + secretPath: z + .string() + .min(1, { message: "Secret path cannot be empty" }) + .transform((val) => removeTrailingSlash(val)), + approvers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(ApproverType.Group), id: z.string() }), + z.object({ + type: z.literal(ApproverType.User), + id: z.string().optional(), + username: z.string().optional() + }) + ]) + .array() + .min(1, { message: "At least one approver should be provided" }) + .max(100, "Cannot have more than 100 approvers"), + bypassers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(BypasserType.Group), id: z.string() }), + z.object({ + type: z.literal(BypasserType.User), + id: z.string().optional(), + username: z.string().optional() + }) + ]) + .array() + .max(100, "Cannot have more than 100 bypassers") + .optional(), + approvals: z.number().min(1).default(1), + enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), + allowedSelfApprovals: z.boolean().default(true) + }) + .refine((data) => data.environment || data.environments, "At least one environment should be provided"), + response: { + 200: z.object({ + approval: sapPubSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.body.workspaceId, + ...req.body, + name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`, + enforcementLevel: req.body.enforcementLevel + }); + return { approval }; + } + }); + + server.route({ + url: "/:sapId", + method: "PATCH", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + sapId: z.string() + }), + body: z.object({ + name: z.string().optional(), + approvers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(ApproverType.Group), id: z.string() }), + z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() }) + ]) + .array() + .min(1, { message: "At least one approver should be provided" }) + .max(100, "Cannot have more than 100 approvers"), + bypassers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(BypasserType.Group), id: z.string() }), + z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() }) + ]) + .array() + .max(100, "Cannot have more than 100 bypassers") + .optional(), + approvals: z.number().min(1).default(1), + secretPath: z + .string() + .trim() + .min(1, { message: "Secret path cannot be empty" }) + .optional() + .transform((val) => (val ? removeTrailingSlash(val) : undefined)), + enforcementLevel: z.nativeEnum(EnforcementLevel).optional(), + allowedSelfApprovals: z.boolean().default(true), + environments: z.array(z.string()).optional() + }), + response: { + 200: z.object({ + approval: sapPubSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + secretPolicyId: req.params.sapId + }); + return { approval }; + } + }); + + server.route({ + url: "/:sapId", + method: "DELETE", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + sapId: z.string() + }), + response: { + 200: z.object({ + approval: sapPubSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + secretPolicyId: req.params.sapId + }); + return { approval }; + } + }); + + server.route({ + url: "/", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + querystring: z.object({ + workspaceId: z.string().trim() + }), + response: { + 200: z.object({ + approvals: sapPubSchema + .extend({ + approvers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(ApproverType) + }) + .array(), + bypassers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(BypasserType) + }) + .array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.query.workspaceId + }); + return { approvals }; + } + }); + + server.route({ + url: "/:sapId", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + sapId: z.string() + }), + response: { + 200: z.object({ + approval: sapPubSchema.extend({ + approvers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(ApproverType), + username: z.string().nullable().optional() + }) + .array(), + bypassers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(BypasserType), + username: z.string().nullable().optional() + }) + .array() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.getSecretApprovalPolicyById({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.params + }); + + return { approval }; + } + }); + + server.route({ + url: "/board", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + secretPath: z.string().trim().transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + policy: sapPubSchema + .extend({ + userApprovers: z.object({ userId: z.string().nullable().optional() }).array() + }) + .optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.query.workspaceId, + ...req.query + }); + return { policy }; + } + }); +}; diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index ecf56eaf1..6eec1d6b4 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -29,7 +29,7 @@ import { registerRateLimitRouter } from "./rate-limit-router"; import { registerRelayRouter } from "./relay-router"; import { registerSamlRouter } from "./saml-router"; import { registerScimRouter } from "./scim-router"; -import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router"; +import { registerDepreciatedSecretApprovalPolicyRouter } from "./depreciated-secret-approval-policy-router"; import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router"; import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router"; import { registerSecretRotationRouter } from "./secret-rotation-router"; @@ -71,7 +71,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { await server.register(registerSnapshotRouter, { prefix: "/secret-snapshot" }); await server.register(registerPITRouter, { prefix: "/pit" }); - await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" }); + await server.register(registerDepreciatedSecretApprovalPolicyRouter, { prefix: "/secret-approvals" }); await server.register(registerSecretApprovalRequestRouter, { prefix: "/secret-approval-requests" }); diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index bdc9c2dcd..6a9b59f00 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -27,7 +27,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }, schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim().optional(), committer: z.string().trim().optional(), search: z.string().trim().optional(), @@ -80,7 +80,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, - projectId: req.query.workspaceId + projectId: req.query.projectId }); return { approvals, totalCount }; } @@ -94,7 +94,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }, schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), policyId: z.string().trim().optional() }), response: { @@ -113,7 +113,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId, + projectId: req.query.projectId, policyId: req.query.policyId }); return { approvals }; diff --git a/backend/src/ee/routes/v2/index.ts b/backend/src/ee/routes/v2/index.ts index 9f6aa0511..d98505e94 100644 --- a/backend/src/ee/routes/v2/index.ts +++ b/backend/src/ee/routes/v2/index.ts @@ -10,6 +10,7 @@ import { import { registerGatewayV2Router } from "./gateway-router"; import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router"; import { registerDepreciatedProjectRoleRouter } from "./depreciated-project-role-router"; +import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router"; export const registerV2EERoutes = async (server: FastifyZodProvider) => { await server.register( @@ -26,6 +27,8 @@ export const registerV2EERoutes = async (server: FastifyZodProvider) => { await server.register(registerGatewayV2Router, { prefix: "/gateways" }); + await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" }); + await server.register( async (secretRotationV2Router) => { // register generic secret rotation endpoints diff --git a/backend/src/ee/routes/v1/secret-approval-policy-router.ts b/backend/src/ee/routes/v2/secret-approval-policy-router.ts similarity index 97% rename from backend/src/ee/routes/v1/secret-approval-policy-router.ts rename to backend/src/ee/routes/v2/secret-approval-policy-router.ts index dc87b83f2..f7f770197 100644 --- a/backend/src/ee/routes/v1/secret-approval-policy-router.ts +++ b/backend/src/ee/routes/v2/secret-approval-policy-router.ts @@ -19,7 +19,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi schema: { body: z .object({ - workspaceId: z.string(), + projectId: z.string(), name: z.string().optional(), environment: z.string().optional(), environments: z.string().array().optional(), @@ -69,7 +69,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.body.workspaceId, ...req.body, name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`, enforcementLevel: req.body.enforcementLevel @@ -174,7 +173,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi }, schema: { querystring: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -204,7 +203,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId + projectId: req.query.projectId }); return { approvals }; } @@ -263,7 +262,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi }, schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim(), secretPath: z.string().trim().transform(removeTrailingSlash) }), @@ -284,7 +283,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId, ...req.query }); return { policy }; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index ba799ec36..c35dc2f33 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -901,7 +901,7 @@ export const ENVIRONMENTS = { export const FOLDERS = { LIST: { - workspaceId: "The ID of the project to list folders from.", + projectId: "The ID of the project to list folders from.", environment: "The slug of the environment to list folders from.", path: "The path to list folders from.", directory: "The directory to list folders from. (Deprecated in favor of path)", @@ -913,7 +913,7 @@ export const FOLDERS = { folderId: "The ID of the folder to get details." }, CREATE: { - workspaceId: "The ID of the project to create the folder in.", + projectId: "The ID of the project to create the folder in.", environment: "The slug of the environment to create the folder in.", name: "The name of the folder to create.", path: "The path of the folder to create.", @@ -927,12 +927,12 @@ export const FOLDERS = { path: "The path of the folder to update.", directory: "The new directory of the folder to update. (Deprecated in favor of path)", projectSlug: "The slug of the project where the folder is located.", - workspaceId: "The ID of the project where the folder is located.", + projectId: "The ID of the project where the folder is located.", description: "An optional description label for the folder." }, DELETE: { folderIdOrName: "The ID or name of the folder to delete.", - workspaceId: "The ID of the project to delete the folder from.", + projectId: "The ID of the project to delete the folder from.", environment: "The slug of the environment where the folder is located.", directory: "The directory of the folder to delete. (Deprecated in favor of path)", path: "The path of the folder to delete." @@ -1043,7 +1043,7 @@ export const RAW_SECRETS = { export const SECRET_IMPORTS = { LIST: { - workspaceId: "The ID of the project to list secret imports from.", + projectId: "The ID of the project to list secret imports from.", environment: "The slug of the environment to list secret imports from.", path: "The path to list secret imports from." }, @@ -1053,7 +1053,7 @@ export const SECRET_IMPORTS = { CREATE: { environment: "The slug of the environment to import into.", path: "The path to import into.", - workspaceId: "The ID of the project you are working in.", + projectId: "The ID of the project you are working in.", isReplication: "When true, secrets from the source will be automatically sent to the destination. If approval policies exist at the destination, the secrets will be sent as approval requests instead of being applied immediately.", import: { @@ -1070,10 +1070,10 @@ export const SECRET_IMPORTS = { position: "The new position of the secret import. The lowest number will be displayed as the first import." }, path: "The path of the secret import to update.", - workspaceId: "The ID of the project where the secret import is located." + projectId: "The ID of the project where the secret import is located." }, DELETE: { - workspaceId: "The ID of the project to delete the secret import from.", + projectId: "The ID of the project to delete the secret import from.", secretImportId: "The ID of the secret import to delete.", environment: "The slug of the environment where the secret import is located.", path: "The path of the secret import to delete." diff --git a/backend/src/server/routes/v1/depreciated-secret-folder-router.ts b/backend/src/server/routes/v1/depreciated-secret-folder-router.ts new file mode 100644 index 000000000..d64f4da9c --- /dev/null +++ b/backend/src/server/routes/v1/depreciated-secret-folder-router.ts @@ -0,0 +1,444 @@ +import { z } from "zod"; + +import { SecretFoldersSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, FOLDERS } from "@app/lib/api-docs"; +import { prefixWithSlash, removeTrailingSlash } from "@app/lib/fn"; +import { isValidFolderName } from "@app/lib/validator"; +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"; + +import { booleanSchema } from "../sanitizedSchemas"; + +export const registerDepreciatedSecretFolderRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/", + method: "POST", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Create folders", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.CREATE.projectId), + environment: z.string().trim().describe(FOLDERS.CREATE.environment), + name: z + .string() + .trim() + .describe(FOLDERS.CREATE.name) + .refine((name) => isValidFolderName(name), { + message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed." + }), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.CREATE.path) + .optional(), + // backward compatibility with cli + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.CREATE.directory) + .optional(), + description: z.string().optional().nullable().describe(FOLDERS.CREATE.description) + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema.extend({ + path: z.string() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.body.path || req.body.directory || "/"; + const folder = await server.services.folder.createFolder({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + path, + description: req.body.description + }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.CREATE_FOLDER, + metadata: { + environment: req.body.environment, + folderId: folder.id, + folderName: folder.name, + folderPath: path, + ...(req.body.description ? { description: req.body.description } : {}) + } + } + }); + return { folder }; + } + }); + + server.route({ + url: "/:folderId", + method: "PATCH", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Update folder", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + // old way this was name + folderId: z.string().describe(FOLDERS.UPDATE.folderId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.UPDATE.projectId), + environment: z.string().trim().describe(FOLDERS.UPDATE.environment), + name: z + .string() + .trim() + .describe(FOLDERS.UPDATE.name) + .refine((name) => isValidFolderName(name), { + message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed." + }), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.path) + .optional(), + // backward compatibility with cli + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.directory) + .optional(), + description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description) + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema.extend({ + path: z.string() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.body.path || req.body.directory || "/"; + const { folder, old } = await server.services.folder.updateFolder({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + id: req.params.folderId, + path + }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.UPDATE_FOLDER, + metadata: { + environment: req.body.environment, + folderId: folder.id, + folderPath: path, + newFolderName: folder.name, + oldFolderName: old.name + } + } + }); + return { folder }; + } + }); + + server.route({ + url: "/batch", + method: "PATCH", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Update folders by batch", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug), + folders: z + .object({ + id: z.string().describe(FOLDERS.UPDATE.folderId), + environment: z.string().trim().describe(FOLDERS.UPDATE.environment), + name: z + .string() + .trim() + .describe(FOLDERS.UPDATE.name) + .refine((name) => isValidFolderName(name), { + message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed." + }), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.path), + description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description) + }) + .array() + .min(1) + }), + response: { + 200: z.object({ + folders: SecretFoldersSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { newFolders, oldFolders, projectId } = await server.services.folder.updateManyFolders({ + ...req.body, + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId + }); + + await Promise.all( + req.body.folders.map(async (folder, index) => { + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.UPDATE_FOLDER, + metadata: { + environment: oldFolders[index].envId, + folderId: oldFolders[index].id, + folderPath: folder.path, + newFolderName: newFolders[index].name, + oldFolderName: oldFolders[index].name + } + } + }); + }) + ); + + return { folders: newFolders }; + } + }); + + // TODO(daniel): Expose this route in api reference and write docs for it. + server.route({ + method: "DELETE", + url: "/:folderIdOrName", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Delete a folder", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName) + }), + body: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.DELETE.projectId), + environment: z.string().trim().describe(FOLDERS.DELETE.environment), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.DELETE.path) + .optional(), + // keep this here as cli need directory + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.DELETE.directory) + .optional() + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.body.path || req.body.directory || "/"; + const folder = await server.services.folder.deleteFolder({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + idOrName: req.params.folderIdOrName, + path + }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.DELETE_FOLDER, + metadata: { + environment: req.body.environment, + folderId: folder.id, + folderPath: path, + folderName: folder.name + } + } + }); + return { folder }; + } + }); + + server.route({ + method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Get folders", + security: [ + { + bearerAuth: [] + } + ], + querystring: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.LIST.projectId), + environment: z.string().trim().describe(FOLDERS.LIST.environment), + lastSecretModified: z.string().datetime().trim().optional().describe(FOLDERS.LIST.lastSecretModified), + path: z + .string() + .trim() + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.LIST.path) + .optional(), + // backward compatibility with cli + directory: z + .string() + .trim() + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.LIST.directory) + .optional(), + recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive) + }), + response: { + 200: z.object({ + folders: SecretFoldersSchema.extend({ + relativePath: z.string().optional() + }).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.query.path || req.query.directory || "/"; + const folders = await server.services.folder.getFolders({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId, + path + }); + return { folders }; + } + }); + + server.route({ + method: "GET", + url: "/:id", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Get folder by id", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + id: z.string().trim().describe(FOLDERS.GET_BY_ID.folderId) + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema.extend({ + environment: z.object({ + envId: z.string(), + envName: z.string(), + envSlug: z.string() + }), + path: z.string(), + projectId: z.string() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const folder = await server.services.folder.getFolderById({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.id + }); + return { folder }; + } + }); +}; diff --git a/backend/src/server/routes/v1/depreciated-secret-import-router.ts b/backend/src/server/routes/v1/depreciated-secret-import-router.ts new file mode 100644 index 000000000..7bc6912e2 --- /dev/null +++ b/backend/src/server/routes/v1/depreciated-secret-import-router.ts @@ -0,0 +1,472 @@ +import { z } from "zod"; + +import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, SECRET_IMPORTS } from "@app/lib/api-docs"; +import { 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"; + +import { secretRawSchema } from "../sanitizedSchemas"; + +export const registerDepreciatedSecretImportRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Create secret imports", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path), + import: z.object({ + environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.import.environment), + path: z.string().trim().transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.import.path) + }), + isReplication: z.boolean().default(false).describe(SECRET_IMPORTS.CREATE.isReplication) + }), + response: { + 200: z.object({ + message: z.string(), + secretImport: SecretImportsSchema.omit({ importEnv: true }).merge( + z.object({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.createImport({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + data: req.body.import + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.CREATE_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId, + importFromSecretPath: secretImport.importPath, + importFromEnvironment: secretImport.importEnv.slug, + importToEnvironment: req.body.environment, + importToSecretPath: req.body.path + } + } + }); + return { message: "Successfully created secret import", secretImport }; + } + }); + + server.route({ + method: "PATCH", + url: "/:secretImportId", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Update secret imports", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path), + import: z.object({ + environment: z.string().trim().optional().describe(SECRET_IMPORTS.UPDATE.import.environment), + path: z + .string() + .trim() + .optional() + .transform((val) => (val ? removeTrailingSlash(val) : val)) + .describe(SECRET_IMPORTS.UPDATE.import.path), + position: z.number().optional().describe(SECRET_IMPORTS.UPDATE.import.position) + }) + }), + response: { + 200: z.object({ + message: z.string(), + secretImport: SecretImportsSchema.omit({ importEnv: true }).merge( + z.object({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.updateImport({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId, + ...req.body, + projectId: req.body.workspaceId, + data: req.body.import + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.UPDATE_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId, + position: secretImport.position, + importToEnvironment: req.body.environment, + importToSecretPath: req.body.path + } + } + }); + + return { message: "Successfully updated secret import", secretImport }; + } + }); + + server.route({ + method: "DELETE", + url: "/:secretImportId", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Delete secret imports", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path) + }), + response: { + 200: z.object({ + message: z.string(), + secretImport: SecretImportsSchema.omit({ importEnv: true }).merge( + z.object({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.deleteImport({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId, + ...req.body, + projectId: req.body.workspaceId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.DELETE_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId, + importFromEnvironment: secretImport.importEnv.slug, + importFromSecretPath: secretImport.importPath, + importToEnvironment: req.body.environment, + importToSecretPath: req.body.path + } + } + }); + return { message: "Successfully deleted secret import", secretImport }; + } + }); + + server.route({ + method: "POST", + url: "/:secretImportId/replication-resync", + config: { + rateLimit: secretsLimit + }, + schema: { + description: "Resync secret replication of secret imports", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path) + }), + response: { + 200: z.object({ + message: z.string() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { message } = await server.services.secretImport.resyncSecretImportReplication({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId, + ...req.body, + projectId: req.body.workspaceId + }); + + return { message }; + } + }); + + server.route({ + method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Get secret imports", + security: [ + { + bearerAuth: [] + } + ], + querystring: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path) + }), + response: { + 200: z.object({ + message: z.string(), + secretImports: SecretImportsSchema.omit({ importEnv: true }) + .extend({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImports = await server.services.secretImport.getImports({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.query.workspaceId, + event: { + type: EventType.GET_SECRET_IMPORTS, + metadata: { + environment: req.query.environment, + folderId: secretImports?.[0]?.folderId, + numberOfImports: secretImports.length + } + } + }); + return { message: "Successfully fetched secret imports", secretImports }; + } + }); + + server.route({ + url: "/:secretImportId", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Get single secret import", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.GET.secretImportId) + }), + response: { + 200: z.object({ + secretImport: SecretImportsSchema.omit({ importEnv: true }).extend({ + environment: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + projectId: z.string(), + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }), + secretPath: z.string() + }) + }) + } + }, + + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.getImportById({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: secretImport.projectId, + event: { + type: EventType.GET_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId + } + } + }); + + return { secretImport }; + } + }); + + server.route({ + url: "/secrets", + method: "GET", + config: { + rateLimit: secretsLimit + }, + schema: { + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + path: z.string().trim().default("/").transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + secrets: z + .object({ + secretPath: z.string(), + environment: z.string(), + environmentInfo: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + folderId: z.string().optional(), + secrets: SecretsSchema.omit({ secretBlindIndex: true }).array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const importedSecrets = await server.services.secretImport.getSecretsFromImports({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId + }); + return { secrets: importedSecrets }; + } + }); + + server.route({ + url: "/secrets/raw", + method: "GET", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + path: z.string().trim().default("/").transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + secrets: z + .object({ + secretPath: z.string(), + environment: z.string(), + environmentInfo: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + folderId: z.string().optional(), + secrets: secretRawSchema.array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const importedSecrets = await server.services.secretImport.getRawSecretsFromImports({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId + }); + return { secrets: importedSecrets }; + } + }); +}; diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 4aaa6083b..408c94d5e 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -50,8 +50,8 @@ import { registerProjectKeyRouter } from "./project-key-router"; import { registerProjectMembershipRouter } from "./project-membership-router"; import { registerProjectRouter } from "./project-router"; import { SECRET_REMINDER_REGISTER_ROUTER_MAP } from "./reminder-routers"; -import { registerSecretFolderRouter } from "./secret-folder-router"; -import { registerSecretImportRouter } from "./secret-import-router"; +import { registerDepreciatedSecretFolderRouter } from "./depreciated-secret-folder-router"; +import { registerDepreciatedSecretImportRouter } from "./depreciated-secret-import-router"; import { registerSecretRequestsRouter } from "./secret-requests-router"; import { registerSecretSharingRouter } from "./secret-sharing-router"; import { registerSecretTagRouter } from "./secret-tag-router"; @@ -93,8 +93,8 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerNotificationRouter, { prefix: "/notifications" }); await server.register(registerInviteOrgRouter, { prefix: "/invite-org" }); await server.register(registerUserActionRouter, { prefix: "/user-action" }); - await server.register(registerSecretImportRouter, { prefix: "/secret-imports" }); - await server.register(registerSecretFolderRouter, { prefix: "/folders" }); + await server.register(registerDepreciatedSecretImportRouter, { prefix: "/secret-imports" }); + await server.register(registerDepreciatedSecretFolderRouter, { prefix: "/folders" }); await server.register( async (workflowIntegrationRouter) => { diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 377af135c..386628d28 100644 --- a/backend/src/server/routes/v1/webhook-router.ts +++ b/backend/src/server/routes/v1/webhook-router.ts @@ -39,7 +39,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { body: z .object({ type: z.nativeEnum(WebhookType).default(WebhookType.GENERAL), - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim(), webhookUrl: z.string().url().trim(), webhookSecretKey: z.string().trim().optional(), @@ -67,13 +67,12 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.body.workspaceId, ...req.body }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.CREATE_WEBHOOK, metadata: { @@ -216,7 +215,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim().optional(), secretPath: z .string() @@ -238,7 +237,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, - projectId: req.query.workspaceId + projectId: req.query.projectId }); return { message: "Successfully fetched webhook", webhooks }; } diff --git a/backend/src/server/routes/v2/index.ts b/backend/src/server/routes/v2/index.ts index 2735dfcc9..ab6505454 100644 --- a/backend/src/server/routes/v2/index.ts +++ b/backend/src/server/routes/v2/index.ts @@ -10,6 +10,8 @@ import { registerDepreciatedProjectMembershipRouter } from "./depreciated-projec import { registerDepreciatedProjectRouter } from "./depreciated-project-router"; import { registerServiceTokenRouter } from "./service-token-router"; import { registerUserRouter } from "./user-router"; +import { registerSecretFolderRouter } from "./secret-folder-router"; +import { registerSecretImportRouter } from "./secret-import-router"; export const registerV2Routes = async (server: FastifyZodProvider) => { await server.register(registerMfaRouter, { prefix: "/auth" }); @@ -33,6 +35,9 @@ export const registerV2Routes = async (server: FastifyZodProvider) => { { prefix: "/organizations" } ); + await server.register(registerSecretFolderRouter, { prefix: "/folders" }); + await server.register(registerSecretImportRouter, { prefix: "/secret-imports" }); + // moved to v1/projects await server.register( async (projectServer) => { diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v2/secret-folder-router.ts similarity index 80% rename from backend/src/server/routes/v1/secret-folder-router.ts rename to backend/src/server/routes/v2/secret-folder-router.ts index 871259147..0bf062452 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v2/secret-folder-router.ts @@ -28,7 +28,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } ], body: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.CREATE.workspaceId), + projectId: z.string().trim().describe(FOLDERS.CREATE.projectId), environment: z.string().trim().describe(FOLDERS.CREATE.environment), name: z .string() @@ -43,17 +43,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .default("/") .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) - .describe(FOLDERS.CREATE.path) - .optional(), - // backward compatibility with cli - directory: z - .string() - .trim() - .default("/") - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.CREATE.directory) - .optional(), + .describe(FOLDERS.CREATE.path), description: z.string().optional().nullable().describe(FOLDERS.CREATE.description) }), response: { @@ -66,27 +56,24 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.body.path || req.body.directory || "/"; const folder = await server.services.folder.createFolder({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, - path, description: req.body.description }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.CREATE_FOLDER, metadata: { environment: req.body.environment, folderId: folder.id, folderName: folder.name, - folderPath: path, + folderPath: req.body.path, ...(req.body.description ? { description: req.body.description } : {}) } } @@ -115,7 +102,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => folderId: z.string().describe(FOLDERS.UPDATE.folderId) }), body: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId), + projectId: z.string().trim().describe(FOLDERS.UPDATE.projectId), environment: z.string().trim().describe(FOLDERS.UPDATE.environment), name: z .string() @@ -130,17 +117,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .default("/") .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) - .describe(FOLDERS.UPDATE.path) - .optional(), - // backward compatibility with cli - directory: z - .string() - .trim() - .default("/") - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.UPDATE.directory) - .optional(), + .describe(FOLDERS.UPDATE.path), description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description) }), response: { @@ -153,26 +130,23 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.body.path || req.body.directory || "/"; const { folder, old } = await server.services.folder.updateFolder({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, - id: req.params.folderId, - path + id: req.params.folderId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.UPDATE_FOLDER, metadata: { environment: req.body.environment, folderId: folder.id, - folderPath: path, + folderPath: req.body.path, newFolderName: folder.name, oldFolderName: old.name } @@ -198,7 +172,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } ], body: z.object({ - projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug), + projectId: z.string().trim().describe(FOLDERS.UPDATE.projectId), folders: z .object({ id: z.string().describe(FOLDERS.UPDATE.folderId), @@ -281,7 +255,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName) }), body: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.DELETE.workspaceId), + projectId: z.string().trim().describe(FOLDERS.DELETE.projectId), environment: z.string().trim().describe(FOLDERS.DELETE.environment), path: z .string() @@ -290,16 +264,6 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) .describe(FOLDERS.DELETE.path) - .optional(), - // keep this here as cli need directory - directory: z - .string() - .trim() - .default("/") - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.DELETE.directory) - .optional() }), response: { 200: z.object({ @@ -309,26 +273,23 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.body.path || req.body.directory || "/"; const folder = await server.services.folder.deleteFolder({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, - idOrName: req.params.folderIdOrName, - path + idOrName: req.params.folderIdOrName }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.DELETE_FOLDER, metadata: { environment: req.body.environment, folderId: folder.id, - folderPath: path, + folderPath: req.body.path, folderName: folder.name } } @@ -353,7 +314,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } ], querystring: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.LIST.workspaceId), + projectId: z.string().trim().describe(FOLDERS.LIST.projectId), environment: z.string().trim().describe(FOLDERS.LIST.environment), lastSecretModified: z.string().datetime().trim().optional().describe(FOLDERS.LIST.lastSecretModified), path: z @@ -361,16 +322,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .trim() .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) - .describe(FOLDERS.LIST.path) - .optional(), - // backward compatibility with cli - directory: z - .string() - .trim() - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.LIST.directory) - .optional(), + .describe(FOLDERS.LIST.path), recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive) }), response: { @@ -383,15 +335,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.query.path || req.query.directory || "/"; const folders = await server.services.folder.getFolders({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - ...req.query, - projectId: req.query.workspaceId, - path + ...req.query }); return { folders }; } diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v2/secret-import-router.ts similarity index 85% rename from backend/src/server/routes/v1/secret-import-router.ts rename to backend/src/server/routes/v2/secret-import-router.ts index fca11f8a0..d9802c4b3 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v2/secret-import-router.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas"; +import { SecretImportsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, SECRET_IMPORTS } from "@app/lib/api-docs"; import { removeTrailingSlash } from "@app/lib/fn"; @@ -27,7 +27,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => } ], body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.CREATE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path), import: z.object({ @@ -55,13 +55,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, + projectId: req.body.projectId, data: req.body.import }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.CREATE_SECRET_IMPORT, metadata: { @@ -97,7 +97,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) }), body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path), import: z.object({ @@ -131,13 +131,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, - projectId: req.body.workspaceId, + projectId: req.body.projectId, data: req.body.import }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.UPDATE_SECRET_IMPORT, metadata: { @@ -173,7 +173,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId) }), body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.DELETE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path) }), @@ -197,12 +197,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, - projectId: req.body.workspaceId + projectId: req.body.projectId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.DELETE_SECRET_IMPORT, metadata: { @@ -236,7 +236,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) }), body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path) }), @@ -255,7 +255,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, - projectId: req.body.workspaceId + projectId: req.body.projectId }); return { message }; @@ -278,7 +278,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => } ], querystring: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.LIST.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path) }), @@ -301,12 +301,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, - projectId: req.query.workspaceId + projectId: req.query.projectId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.query.workspaceId, + projectId: req.query.projectId, event: { type: EventType.GET_SECRET_IMPORTS, metadata: { @@ -386,55 +386,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => config: { rateLimit: secretsLimit }, - schema: { - querystring: z.object({ - workspaceId: z.string().trim(), - environment: z.string().trim(), - path: z.string().trim().default("/").transform(removeTrailingSlash) - }), - response: { - 200: z.object({ - secrets: z - .object({ - secretPath: z.string(), - environment: z.string(), - environmentInfo: z.object({ - id: z.string(), - name: z.string(), - slug: z.string() - }), - folderId: z.string().optional(), - secrets: SecretsSchema.omit({ secretBlindIndex: true }).array() - }) - .array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const importedSecrets = await server.services.secretImport.getSecretsFromImports({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - ...req.query, - projectId: req.query.workspaceId - }); - return { secrets: importedSecrets }; - } - }); - - server.route({ - url: "/secrets/raw", - method: "GET", - config: { - rateLimit: secretsLimit - }, schema: { hide: false, tags: [ApiDocsTags.SecretImports], querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim(), path: z.string().trim().default("/").transform(removeTrailingSlash) }), @@ -463,8 +419,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - ...req.query, - projectId: req.query.workspaceId + ...req.query }); return { secrets: importedSecrets }; } diff --git a/frontend/src/hooks/api/secretApproval/mutation.tsx b/frontend/src/hooks/api/secretApproval/mutation.tsx index 8370d0367..c8fe51d19 100644 --- a/frontend/src/hooks/api/secretApproval/mutation.tsx +++ b/frontend/src/hooks/api/secretApproval/mutation.tsx @@ -11,7 +11,7 @@ export const useCreateSecretApprovalPolicy = () => { return useMutation({ mutationFn: async ({ environments, - workspaceId, + projectId, approvals, approvers, bypassers, @@ -20,9 +20,9 @@ export const useCreateSecretApprovalPolicy = () => { enforcementLevel, allowedSelfApprovals }) => { - const { data } = await apiRequest.post("/api/v1/secret-approvals", { + const { data } = await apiRequest.post("/api/v2/secret-approvals", { environments, - workspaceId, + projectId, approvals, approvers, bypassers, @@ -33,9 +33,9 @@ export const useCreateSecretApprovalPolicy = () => { }); return data; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId) + queryKey: secretApprovalKeys.getApprovalPolicies(projectId) }); } }); @@ -56,7 +56,7 @@ export const useUpdateSecretApprovalPolicy = () => { allowedSelfApprovals, environments }) => { - const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, { + const { data } = await apiRequest.patch(`/api/v2/secret-approvals/${id}`, { approvals, approvers, bypassers, @@ -68,9 +68,9 @@ export const useUpdateSecretApprovalPolicy = () => { }); return data; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId) + queryKey: secretApprovalKeys.getApprovalPolicies(projectId) }); } }); @@ -81,12 +81,12 @@ export const useDeleteSecretApprovalPolicy = () => { return useMutation({ mutationFn: async ({ id }) => { - const { data } = await apiRequest.delete(`/api/v1/secret-approvals/${id}`); + const { data } = await apiRequest.delete(`/api/v2/secret-approvals/${id}`); return data; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId) + queryKey: secretApprovalKeys.getApprovalPolicies(projectId) }); } }); diff --git a/frontend/src/hooks/api/secretApproval/queries.tsx b/frontend/src/hooks/api/secretApproval/queries.tsx index 52c3743fb..e81af93b8 100644 --- a/frontend/src/hooks/api/secretApproval/queries.tsx +++ b/frontend/src/hooks/api/secretApproval/queries.tsx @@ -10,54 +10,53 @@ import { } from "./types"; export const secretApprovalKeys = { - getApprovalPolicies: (workspaceId: string) => - [{ workspaceId }, "secret-approval-policies"] as const, - getApprovalPolicyOfABoard: (workspaceId: string, environment: string, secretPath: string) => [ - { workspaceId, environment, secretPath }, + getApprovalPolicies: (projectId: string) => [{ projectId }, "secret-approval-policies"] as const, + getApprovalPolicyOfABoard: (projectId: string, environment: string, secretPath: string) => [ + { projectId, environment, secretPath }, "Secret-approval-policy" ] }; -const fetchApprovalPolicies = async (workspaceId: string) => { +const fetchApprovalPolicies = async (projectId: string) => { const { data } = await apiRequest.get<{ approvals: TSecretApprovalPolicy[] }>( - "/api/v1/secret-approvals", - { params: { workspaceId } } + "/api/v2/secret-approvals", + { params: { projectId } } ); return data.approvals; }; export const useGetSecretApprovalPolicies = ({ - workspaceId, + projectId, options = {} }: TGetSecretApprovalPoliciesDTO & TReactQueryOptions) => useQuery({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId), - queryFn: () => fetchApprovalPolicies(workspaceId), + queryKey: secretApprovalKeys.getApprovalPolicies(projectId), + queryFn: () => fetchApprovalPolicies(projectId), ...options, - enabled: Boolean(workspaceId) && (options?.enabled ?? true) + enabled: Boolean(projectId) && (options?.enabled ?? true) }); const fetchApprovalPolicyOfABoard = async ( - workspaceId: string, + projectId: string, environment: string, secretPath: string ) => { const { data } = await apiRequest.get<{ policy: TSecretApprovalPolicy }>( - "/api/v1/secret-approvals/board", - { params: { workspaceId, environment, secretPath } } + "/api/v2/secret-approvals/board", + { params: { projectId, environment, secretPath } } ); return data.policy || ""; }; export const useGetSecretApprovalPolicyOfABoard = ({ - workspaceId, + projectId, secretPath = "/", environment, options = {} }: TGetSecretApprovalPolicyOfBoardDTO & TReactQueryOptions) => useQuery({ - queryKey: secretApprovalKeys.getApprovalPolicyOfABoard(workspaceId, environment, secretPath), - queryFn: () => fetchApprovalPolicyOfABoard(workspaceId, environment, secretPath), + queryKey: secretApprovalKeys.getApprovalPolicyOfABoard(projectId, environment, secretPath), + queryFn: () => fetchApprovalPolicyOfABoard(projectId, environment, secretPath), ...options, - enabled: Boolean(workspaceId && secretPath && environment) && (options?.enabled ?? true) + enabled: Boolean(projectId && secretPath && environment) && (options?.enabled ?? true) }); diff --git a/frontend/src/hooks/api/secretApproval/types.ts b/frontend/src/hooks/api/secretApproval/types.ts index 1785ffa8a..6b4aeba63 100644 --- a/frontend/src/hooks/api/secretApproval/types.ts +++ b/frontend/src/hooks/api/secretApproval/types.ts @@ -3,7 +3,7 @@ import { WorkspaceEnv } from "../workspace/types"; export type TSecretApprovalPolicy = { id: string; - workspace: string; + project: string; name: string; environments: WorkspaceEnv[]; secretPath?: string; @@ -36,17 +36,17 @@ export type Bypasser = { }; export type TGetSecretApprovalPoliciesDTO = { - workspaceId: string; + projectId: string; }; export type TGetSecretApprovalPolicyOfBoardDTO = { - workspaceId: string; + projectId: string; environment: string; secretPath: string; }; export type TCreateSecretPolicyDTO = { - workspaceId: string; + projectId: string; name?: string; environments: string[]; secretPath: string; @@ -67,12 +67,12 @@ export type TUpdateSecretPolicyDTO = { allowedSelfApprovals?: boolean; enforcementLevel?: EnforcementLevel; // for invalidating list - workspaceId: string; + projectId: string; environments?: string[]; }; export type TDeleteSecretPolicyDTO = { id: string; // for invalidating list - workspaceId: string; + projectId: string; }; diff --git a/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx b/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx index 78e1b37a1..6a03b431c 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx +++ b/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx @@ -36,9 +36,9 @@ export const useUpdateSecretApprovalRequestStatus = () => { }); return data; }, - onSuccess: (_, { id, workspaceId }) => { + onSuccess: (_, { id, projectId }) => { queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.detail({ id }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); } }); }; @@ -53,10 +53,10 @@ export const usePerformSecretApprovalRequestMerge = () => { }); return data; }, - onSuccess: (_, { id, workspaceId }) => { + onSuccess: (_, { id, projectId }) => { queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.detail({ id }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.list({ workspaceId }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.list({ projectId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); } }); }; diff --git a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx index a1736f70f..63cdd455e 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx +++ b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx @@ -14,7 +14,7 @@ import { export const secretApprovalRequestKeys = { list: ({ - workspaceId, + projectId, environment, status, committer, @@ -23,20 +23,20 @@ export const secretApprovalRequestKeys = { search }: TGetSecretApprovalRequestList) => [ - { workspaceId, environment, status, committer, offset, limit, search }, + { projectId, environment, status, committer, offset, limit, search }, "secret-approval-requests" ] as const, detail: ({ id }: Omit) => [{ id }, "secret-approval-request-detail"] as const, - count: ({ workspaceId, policyId }: TGetSecretApprovalRequestCount) => [ - { workspaceId }, + count: ({ projectId, policyId }: TGetSecretApprovalRequestCount) => [ + { projectId }, "secret-approval-request-count", ...(policyId ? [policyId] : []) ] }; const fetchSecretApprovalRequestList = async ({ - workspaceId, + projectId, environment, committer, status = "open", @@ -49,7 +49,7 @@ const fetchSecretApprovalRequestList = async ({ totalCount: number; }>("/api/v1/secret-approval-requests", { params: { - workspaceId, + projectId, environment, committer, status, @@ -63,7 +63,7 @@ const fetchSecretApprovalRequestList = async ({ }; export const useGetSecretApprovalRequests = ({ - workspaceId, + projectId, environment, options = {}, status, @@ -74,7 +74,7 @@ export const useGetSecretApprovalRequests = ({ }: TGetSecretApprovalRequestList & TReactQueryOptions) => useQuery({ queryKey: secretApprovalRequestKeys.list({ - workspaceId, + projectId, environment, committer, status, @@ -84,7 +84,7 @@ export const useGetSecretApprovalRequests = ({ }), queryFn: () => fetchSecretApprovalRequestList({ - workspaceId, + projectId, environment, status, committer, @@ -92,7 +92,7 @@ export const useGetSecretApprovalRequests = ({ offset, search }), - enabled: Boolean(workspaceId) && (options?.enabled ?? true), + enabled: Boolean(projectId) && (options?.enabled ?? true), placeholderData: (previousData) => previousData }); @@ -127,19 +127,19 @@ export const useGetSecretApprovalRequestDetails = ({ }); const fetchSecretApprovalRequestCount = async ({ - workspaceId, + projectId, policyId }: TGetSecretApprovalRequestCount) => { const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequestCount }>( "/api/v1/secret-approval-requests/count", - { params: { workspaceId, policyId } } + { params: { projectId, policyId } } ); return data.approvals; }; export const useGetSecretApprovalRequestCount = ({ - workspaceId, + projectId, policyId, options = {} }: TGetSecretApprovalRequestCount & { @@ -154,8 +154,8 @@ export const useGetSecretApprovalRequestCount = ({ >; }) => useQuery({ - queryKey: secretApprovalRequestKeys.count({ workspaceId, policyId }), + queryKey: secretApprovalRequestKeys.count({ projectId, policyId }), refetchInterval: 15000, - queryFn: () => fetchSecretApprovalRequestCount({ workspaceId, policyId }), - enabled: Boolean(workspaceId) && (options?.enabled ?? true) + queryFn: () => fetchSecretApprovalRequestCount({ projectId, policyId }), + enabled: Boolean(projectId) && (options?.enabled ?? true) }); diff --git a/frontend/src/hooks/api/secretApprovalRequest/types.ts b/frontend/src/hooks/api/secretApprovalRequest/types.ts index 0eef3fa3d..5a381e871 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/types.ts +++ b/frontend/src/hooks/api/secretApprovalRequest/types.ts @@ -55,7 +55,7 @@ export type TSecretApprovalRequest = { username: string; isOrgMembershipActive: boolean; }[]; - workspace: string; + project: string; environment: string; folderId: string; secretPath: string; @@ -110,7 +110,7 @@ export type TSecretApprovalRequestCount = { }; export type TGetSecretApprovalRequestList = { - workspaceId: string; + projectId: string; environment?: string; status?: "open" | "close"; committer?: string; @@ -120,7 +120,7 @@ export type TGetSecretApprovalRequestList = { }; export type TGetSecretApprovalRequestCount = { - workspaceId: string; + projectId: string; policyId?: string; }; @@ -137,11 +137,11 @@ export type TUpdateSecretApprovalReviewStatusDTO = { export type TUpdateSecretApprovalRequestStatusDTO = { status: "open" | "close"; id: string; - workspaceId: string; + projectId: string; }; export type TPerformSecretApprovalRequestMerge = { id: string; - workspaceId: string; + projectId: string; bypassReason?: string; }; diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 250db962a..843c39883 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -30,10 +30,10 @@ export const folderQueryKeys = { ["secret-folders", "environment", projectId] as const }; -const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => { - const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", { +const fetchProjectFolders = async (projectId: string, environment: string, path = "/") => { + const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v2/folders", { params: { - workspaceId, + projectId, environment, path } @@ -145,9 +145,9 @@ export const useCreateFolder = () => { return useMutation({ mutationFn: async (dto) => { - const { data } = await apiRequest.post("/api/v1/folders", { + const { data } = await apiRequest.post("/api/v2/folders", { ...dto, - workspaceId: dto.projectId + projectId: dto.projectId }); return data; }, @@ -179,10 +179,10 @@ export const useUpdateFolder = () => { return useMutation({ mutationFn: async ({ path = "/", folderId, name, environment, projectId, description }) => { - const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, { + const { data } = await apiRequest.patch(`/api/v2/folders/${folderId}`, { name, environment, - workspaceId: projectId, + projectId, path, description }); @@ -219,10 +219,10 @@ export const useDeleteFolder = () => { return useMutation({ mutationFn: async ({ path = "/", folderId, environment, projectId }) => { - const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, { + const { data } = await apiRequest.delete(`/api/v2/folders/${folderId}`, { data: { environment, - workspaceId: projectId, + projectId, path } }); @@ -259,7 +259,7 @@ export const useUpdateFolderBatch = () => { return useMutation({ mutationFn: async ({ projectSlug, folders }) => { - const { data } = await apiRequest.patch("/api/v1/folders/batch", { + const { data } = await apiRequest.patch("/api/v2/folders/batch", { projectSlug, folders }); diff --git a/frontend/src/hooks/api/secretImports/mutation.tsx b/frontend/src/hooks/api/secretImports/mutation.tsx index 72254bae2..c9484ccfb 100644 --- a/frontend/src/hooks/api/secretImports/mutation.tsx +++ b/frontend/src/hooks/api/secretImports/mutation.tsx @@ -16,10 +16,10 @@ export const useCreateSecretImport = () => { return useMutation({ mutationFn: async ({ import: secretImport, environment, isReplication, projectId, path }) => { - const { data } = await apiRequest.post("/api/v1/secret-imports", { + const { data } = await apiRequest.post("/api/v2/secret-imports", { import: secretImport, environment, - workspaceId: projectId, + projectId, path, isReplication }); @@ -44,11 +44,11 @@ export const useUpdateSecretImport = () => { return useMutation({ mutationFn: async ({ environment, import: secretImports, projectId, path, id }) => { - const { data } = await apiRequest.patch(`/api/v1/secret-imports/${id}`, { + const { data } = await apiRequest.patch(`/api/v2/secret-imports/${id}`, { import: secretImports, environment, path, - workspaceId: projectId + projectId }); return data; }, @@ -69,10 +69,10 @@ export const useUpdateSecretImport = () => { export const useResyncSecretReplication = () => { return useMutation({ mutationFn: async ({ environment, projectId, path, id }) => { - const { data } = await apiRequest.post(`/api/v1/secret-imports/${id}/replication-resync`, { + const { data } = await apiRequest.post(`/api/v2/secret-imports/${id}/replication-resync`, { environment, path, - workspaceId: projectId + projectId }); return data; } @@ -84,9 +84,9 @@ export const useDeleteSecretImport = () => { return useMutation({ mutationFn: async ({ id, projectId, path, environment }) => { - const { data } = await apiRequest.delete(`/api/v1/secret-imports/${id}`, { + const { data } = await apiRequest.delete(`/api/v2/secret-imports/${id}`, { data: { - workspaceId: projectId, + projectId, path, environment } diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index ea6ea68ef..71716cb7c 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -30,10 +30,10 @@ export const secretImportKeys = { const fetchSecretImport = async ({ projectId, environment, path = "/" }: TGetSecretImports) => { const { data } = await apiRequest.get<{ secretImports: TSecretImport[] }>( - "/api/v1/secret-imports", + "/api/v2/secret-imports", { params: { - workspaceId: projectId, + projectId, environment, path } @@ -65,16 +65,12 @@ export const useGetSecretImports = ({ queryFn: () => fetchSecretImport({ path, projectId, environment }) }); -const fetchImportedSecrets = async ( - workspaceId: string, - environment: string, - directory?: string -) => { +const fetchImportedSecrets = async (projectId: string, environment: string, directory?: string) => { const { data } = await apiRequest.get<{ secrets: TImportedSecrets[] }>( - "/api/v1/secret-imports/secrets/raw", + "/api/v2/secret-imports/secrets", { params: { - workspaceId, + projectId, environment, path: directory } @@ -89,10 +85,10 @@ const fetchImportedFolders = async ({ path }: TGetImportedFoldersByEnvDTO) => { const { data } = await apiRequest.get<{ secretImports: TSecretImport[] }>( - "/api/v1/secret-imports", + "/api/v2/secret-imports", { params: { - workspaceId: projectId, + projectId, environment, path } diff --git a/frontend/src/hooks/api/webhooks/mutation.tsx b/frontend/src/hooks/api/webhooks/mutation.tsx index 10d5e472e..66eb8ae17 100644 --- a/frontend/src/hooks/api/webhooks/mutation.tsx +++ b/frontend/src/hooks/api/webhooks/mutation.tsx @@ -13,8 +13,8 @@ export const useCreateWebhook = () => { const { data } = await apiRequest.post("/api/v1/webhooks", dto); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; @@ -27,11 +27,11 @@ export const useTestWebhook = () => { const { data } = await apiRequest.post(`/api/v1/webhooks/${webhookId}/test`); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); }, - onError: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onError: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; @@ -46,8 +46,8 @@ export const useUpdateWebhook = () => { }); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; @@ -60,8 +60,8 @@ export const useDeleteWebhook = () => { const { data } = await apiRequest.delete(`/api/v1/webhooks/${dto.webhookId}`); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; diff --git a/frontend/src/hooks/api/webhooks/types.ts b/frontend/src/hooks/api/webhooks/types.ts index 86183bf1b..7e2758ff8 100644 --- a/frontend/src/hooks/api/webhooks/types.ts +++ b/frontend/src/hooks/api/webhooks/types.ts @@ -23,7 +23,7 @@ export type TWebhook = { }; export type TCreateWebhookDto = { - workspaceId: string; + projectId: string; environment: string; webhookUrl: string; webhookSecretKey?: string; @@ -33,16 +33,16 @@ export type TCreateWebhookDto = { export type TUpdateWebhookDto = { webhookId: string; - workspaceId: string; + projectId: string; isDisabled?: boolean; }; export type TDeleteWebhookDto = { webhookId: string; - workspaceId: string; + projectId: string; }; export type TTestWebhookDTO = { webhookId: string; - workspaceId: string; + projectId: string; }; diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx index 8c8d75f47..ea2f39dc4 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx @@ -62,7 +62,7 @@ export const WebhooksTab = withProjectPermission( try { await createWebhook({ ...data, - workspaceId + projectId: workspaceId }); handlePopUpClose("addWebhook"); createNotification({ @@ -82,7 +82,7 @@ export const WebhooksTab = withProjectPermission( try { await updateWebhook({ webhookId, - workspaceId, + projectId: workspaceId, isDisabled }); createNotification({ @@ -103,7 +103,7 @@ export const WebhooksTab = withProjectPermission( const webhookId = popUp?.deleteWebhook?.data as string; await deleteWebhook({ webhookId, - workspaceId + projectId: workspaceId }); handlePopUpClose("deleteWebhook"); createNotification({ @@ -123,7 +123,7 @@ export const WebhooksTab = withProjectPermission( try { await testWebhook({ webhookId, - workspaceId + projectId: workspaceId }); createNotification({ type: "success",