From 8e43d2a9940e985008d22d1a86d3aac70c56f603 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 16 May 2025 00:08:55 -0400 Subject: [PATCH] feat(project): Enable / Disable Secret Sharing --- ...021501_toggle-secret-sharing-on-project.ts | 21 +++++++ backend/src/db/schemas/projects.ts | 3 +- backend/src/lib/api-docs/constants.ts | 3 +- backend/src/server/routes/sanitizedSchemas.ts | 3 +- .../src/server/routes/v1/project-router.ts | 6 +- .../src/services/project/project-service.ts | 3 +- backend/src/services/project/project-types.ts | 1 + frontend/src/hooks/api/workspace/queries.tsx | 11 +++- frontend/src/hooks/api/workspace/types.ts | 4 +- .../SecretListView/SecretDetailSidebar.tsx | 14 ++++- .../components/SecretListView/SecretItem.tsx | 2 +- .../ProjectGeneralTab/ProjectGeneralTab.tsx | 2 + .../SecretSharingSection.tsx | 60 +++++++++++++++++++ .../components/SecretSharingSection/index.tsx | 1 + 14 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 backend/src/db/migrations/20250516021501_toggle-secret-sharing-on-project.ts create mode 100644 frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx create mode 100644 frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/index.tsx diff --git a/backend/src/db/migrations/20250516021501_toggle-secret-sharing-on-project.ts b/backend/src/db/migrations/20250516021501_toggle-secret-sharing-on-project.ts new file mode 100644 index 000000000..2600ae0f0 --- /dev/null +++ b/backend/src/db/migrations/20250516021501_toggle-secret-sharing-on-project.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasSecretSharingColumn = await knex.schema.hasColumn(TableName.Project, "secretSharing"); + if (!hasSecretSharingColumn) { + await knex.schema.table(TableName.Project, (table) => { + table.boolean("secretSharing").notNullable().defaultTo(true); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasSecretSharingColumn = await knex.schema.hasColumn(TableName.Project, "secretSharing"); + if (hasSecretSharingColumn) { + await knex.schema.table(TableName.Project, (table) => { + table.dropColumn("secretSharing"); + }); + } +} diff --git a/backend/src/db/schemas/projects.ts b/backend/src/db/schemas/projects.ts index 297601fd0..c1e96e8ce 100644 --- a/backend/src/db/schemas/projects.ts +++ b/backend/src/db/schemas/projects.ts @@ -27,7 +27,8 @@ export const ProjectsSchema = z.object({ description: z.string().nullable().optional(), type: z.string(), enforceCapitalization: z.boolean().default(false), - hasDeleteProtection: z.boolean().default(false).nullable().optional() + hasDeleteProtection: z.boolean().default(false).nullable().optional(), + secretSharing: z.boolean().default(true) }); export type TProjects = z.infer; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 0bcee6405..cb3809d71 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -606,7 +606,8 @@ export const PROJECTS = { projectDescription: "An optional description label for the project.", autoCapitalization: "Disable or enable auto-capitalization for the project.", slug: "An optional slug for the project. (must be unique within the organization)", - hasDeleteProtection: "Enable or disable delete protection for the project." + hasDeleteProtection: "Enable or disable delete protection for the project.", + secretSharing: "Enable or disable secret sharing for the project." }, GET_KEY: { workspaceId: "The ID of the project to get the key from." diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index da300981c..87d82c241 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -261,7 +261,8 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({ pitVersionLimit: true, kmsCertificateKeyId: true, auditLogsRetentionDays: true, - hasDeleteProtection: true + hasDeleteProtection: true, + secretSharing: true }); export const SanitizedTagSchema = SecretTagsSchema.pick({ diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index e6d9134d3..2e983cb83 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -346,7 +346,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { "Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore." }) .optional() - .describe(PROJECTS.UPDATE.slug) + .describe(PROJECTS.UPDATE.slug), + secretSharing: z.boolean().optional().describe(PROJECTS.UPDATE.secretSharing) }), response: { 200: z.object({ @@ -366,7 +367,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { description: req.body.description, autoCapitalization: req.body.autoCapitalization, hasDeleteProtection: req.body.hasDeleteProtection, - slug: req.body.slug + slug: req.body.slug, + secretSharing: req.body.secretSharing }, actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 8cfa20697..38631a8fa 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -658,7 +658,8 @@ export const projectServiceFactory = ({ autoCapitalization: update.autoCapitalization, enforceCapitalization: update.autoCapitalization, hasDeleteProtection: update.hasDeleteProtection, - slug: update.slug + slug: update.slug, + secretSharing: update.secretSharing }); return updatedProject; diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 9f74e123c..be052f1cb 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -93,6 +93,7 @@ export type TUpdateProjectDTO = { autoCapitalization?: boolean; hasDeleteProtection?: boolean; slug?: string; + secretSharing?: boolean; }; } & Omit; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 278b62bc8..c040a1267 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -277,13 +277,20 @@ export const useUpdateProject = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ projectID, newProjectName, newProjectDescription, newSlug }) => { + mutationFn: async ({ + projectID, + newProjectName, + newProjectDescription, + newSlug, + secretSharing + }) => { const { data } = await apiRequest.patch<{ workspace: Workspace }>( `/api/v1/workspace/${projectID}`, { name: newProjectName, description: newProjectDescription, - slug: newSlug + slug: newSlug, + secretSharing } ); return data.workspace; diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index ddcf383fb..382e4189c 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -37,6 +37,7 @@ export type Workspace = { createdAt: string; roles?: TProjectRole[]; hasDeleteProtection: boolean; + secretSharing: boolean; }; export type WorkspaceEnv = { @@ -73,9 +74,10 @@ export type CreateWorkspaceDTO = { export type UpdateProjectDTO = { projectID: string; - newProjectName: string; + newProjectName?: string; newProjectDescription?: string; newSlug?: string; + secretSharing?: boolean; }; export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx index da4e89741..8eb55b81e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -398,11 +398,19 @@ export const SecretDetailSidebar = ({ autoFocus={false} />