From 7e9ee7b5e3513bd7cc43d6549dcb024802b1834c Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 15 May 2025 20:01:08 -0700 Subject: [PATCH 01/12] fix: add empty display for sso general tab if no sso is enabled --- .../SecretSyncOptionsFields.tsx | 1 + .../components/OrgSsoTab/OrgSsoTab.tsx | 190 +++++++++--------- 2 files changed, 99 insertions(+), 92 deletions(-) diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index a497c3ba9..e51de48ca 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -144,6 +144,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { Key Schema {" "} diff --git a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx index 65f97cc2d..ca27b7517 100644 --- a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx +++ b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx @@ -1,7 +1,7 @@ import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; -import { Button, ContentLoader } from "@app/components/v2"; +import { Button, ContentLoader, EmptyState } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, @@ -60,102 +60,108 @@ export const OrgSsoTab = withPermission( const shouldShowCreateIdentityProviderView = !isOidcConfigured && !isSamlConfigured && !isLdapConfigured; - const createIdentityProviderView = (shouldDisplaySection(LoginMethod.SAML) || + const createIdentityProviderView = + shouldDisplaySection(LoginMethod.SAML) || shouldDisplaySection(LoginMethod.OIDC) || - shouldDisplaySection(LoginMethod.LDAP)) && ( - <> -
-

Connect an Identity Provider

-

- Connect your identity provider to simplify user management -

- {shouldDisplaySection(LoginMethod.SAML) && ( -
-

SAML

- -
- )} - {shouldDisplaySection(LoginMethod.OIDC) && ( -
-

OIDC

- +
+ )} + {shouldDisplaySection(LoginMethod.OIDC) && ( +
- Connect - -
- )} - {shouldDisplaySection(LoginMethod.LDAP) && ( -
-

LDAP

- -
- )} -
- - - - - ); + handlePopUpOpen("addOIDC"); + }} + > + Connect + + + )} + {shouldDisplaySection(LoginMethod.LDAP) && ( +
+

LDAP

+ +
+ )} + + + + + + ) : ( + +

Single Sign-On (SSO) has been disabled

+

Contact your server administrator

+
+ ); if (areConfigsLoading) { return ; From ef70de1e0bd06475b14b0db70a9ca45c7ea295c0 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 15 May 2025 20:05:56 -0700 Subject: [PATCH 02/12] fix: add noopenner to doc link --- .../forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index e51de48ca..7c2b13936 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -144,7 +144,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { Key Schema {" "} From 8e43d2a9940e985008d22d1a86d3aac70c56f603 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 16 May 2025 00:08:55 -0400 Subject: [PATCH 03/12] 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} />