From acb22cdf3658c88d1ad65138eb661bd5570a1773 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Tue, 11 Mar 2025 12:58:09 -0300 Subject: [PATCH 1/6] Added new option to enable/disable option to share secrets with anyone --- ...hare-to-anyone-setting-to-organizations.ts | 26 ++++ backend/src/db/schemas/organizations.ts | 3 +- .../server/routes/v1/organization-router.ts | 3 +- backend/src/services/org/org-service.ts | 14 +- backend/src/services/org/org-types.ts | 1 + frontend/src/const/routes.ts | 4 + .../src/hooks/api/organization/queries.tsx | 6 +- frontend/src/hooks/api/organization/types.ts | 2 + .../OrganizationLayout/OrganizationLayout.tsx | 87 ++++------- .../ProductsSideBar/DefaultSideBar.tsx | 58 ++++++++ .../ProductsSideBar/SecretSharingSideBar.tsx | 34 +++++ .../ProductsSideBar/index.ts | 2 + .../SecretSharingSettingsPage.tsx | 35 +++++ .../SecretSharingAllowShareToAnyone.tsx | 58 ++++++++ .../SecretSharingAllowShareToAnyone/index.tsx | 1 + .../SecretSharingSettingsGeneralTab.tsx | 9 ++ .../SecretSharingSettingsGeneralTab/index.tsx | 1 + .../SecretSharingSettingsTabGroup.tsx | 39 +++++ .../SecretSharingSettingsTabGroup/index.tsx | 1 + .../components/index.tsx | 1 + .../SecretSharingSettingsPage/route.tsx | 37 +++++ .../components/ShareSecretForm.tsx | 26 +++- frontend/src/routeTree.gen.ts | 135 ++++++++++++++---- frontend/src/routes.ts | 5 +- 24 files changed, 486 insertions(+), 102 deletions(-) create mode 100644 backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts create mode 100644 frontend/src/layouts/OrganizationLayout/ProductsSideBar/DefaultSideBar.tsx create mode 100644 frontend/src/layouts/OrganizationLayout/ProductsSideBar/SecretSharingSideBar.tsx create mode 100644 frontend/src/layouts/OrganizationLayout/ProductsSideBar/index.ts create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/index.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/SecretSharingSettingsGeneralTab.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/index.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/SecretSharingSettingsTabGroup.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/index.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/components/index.tsx create mode 100644 frontend/src/pages/organization/SecretSharingSettingsPage/route.tsx diff --git a/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts b/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts new file mode 100644 index 0000000000..71c3b736f7 --- /dev/null +++ b/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts @@ -0,0 +1,26 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.Organization)) { + const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareToAnyone"); + + if (!hasSecretShareToAnyoneCol) { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.boolean("secretShareSendToAnyone").defaultTo(true); + }); + } + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.Organization)) { + const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareToAnyone"); + if (hasSecretShareToAnyoneCol) { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.dropColumn("secretShareSendToAnyone"); + }); + } + } +} diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index 3f40447add..284ad8d58c 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -22,7 +22,8 @@ export const OrganizationsSchema = z.object({ kmsEncryptedDataKey: zodBuffer.nullable().optional(), defaultMembershipRole: z.string().default("member"), enforceMfa: z.boolean().default(false), - selectedMfaMethod: z.string().nullable().optional() + selectedMfaMethod: z.string().nullable().optional(), + secretShareSendToAnyone: z.boolean().default(true).nullable().optional() }); export type TOrganizations = z.infer; diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index db0008ebe2..0b6f7c2e18 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -257,7 +257,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { scimEnabled: z.boolean().optional(), defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(), enforceMfa: z.boolean().optional(), - selectedMfaMethod: z.nativeEnum(MfaMethod).optional() + selectedMfaMethod: z.nativeEnum(MfaMethod).optional(), + secretShareSendToAnyone: z.boolean().optional() }), response: { 200: z.object({ diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index b0e5c1f672..12b5a75058 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -286,7 +286,16 @@ export const orgServiceFactory = ({ actorOrgId, actorAuthMethod, orgId, - data: { name, slug, authEnforced, scimEnabled, defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod } + data: { + name, + slug, + authEnforced, + scimEnabled, + defaultMembershipRoleSlug, + enforceMfa, + selectedMfaMethod, + secretShareSendToAnyone + } }: TUpdateOrgDTO) => { const appCfg = getConfig(); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); @@ -358,7 +367,8 @@ export const orgServiceFactory = ({ scimEnabled, defaultMembershipRole, enforceMfa, - selectedMfaMethod + selectedMfaMethod, + secretShareSendToAnyone }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); return org; diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index b9228377bd..b6dabe1216 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -72,6 +72,7 @@ export type TUpdateOrgDTO = { defaultMembershipRoleSlug: string; enforceMfa: boolean; selectedMfaMethod: MfaMethod; + secretShareSendToAnyone: boolean; }>; } & TOrgPermission; diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index c36264f5c2..e0de3da444 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -25,6 +25,10 @@ export const ROUTE_PATHS = Object.freeze({ "/organization/secret-sharing", "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" ), + SecretSharingSettings: setRoute( + "/organization/secret-sharing/settings", + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings" + ), SettingsPage: setRoute( "/organization/settings", "/_authenticate/_inject-org-details/_org-layout/organization/settings" diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index b5637f049e..64f8f2eb12 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -109,7 +109,8 @@ export const useUpdateOrg = () => { orgId, defaultMembershipRoleSlug, enforceMfa, - selectedMfaMethod + selectedMfaMethod, + secretShareSendToAnyone }) => { return apiRequest.patch(`/api/v1/organization/${orgId}`, { name, @@ -118,7 +119,8 @@ export const useUpdateOrg = () => { slug, defaultMembershipRoleSlug, enforceMfa, - selectedMfaMethod + selectedMfaMethod, + secretShareSendToAnyone }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 629c4a330d..6ef32ff7ee 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -15,6 +15,7 @@ export type Organization = { defaultMembershipRole: string; enforceMfa: boolean; selectedMfaMethod?: MfaMethod; + secretShareSendToAnyone?: boolean; }; export type UpdateOrgDTO = { @@ -26,6 +27,7 @@ export type UpdateOrgDTO = { defaultMembershipRoleSlug?: string; enforceMfa?: boolean; selectedMfaMethod?: MfaMethod; + secretShareSendToAnyone?: boolean; }; export type BillingDetails = { diff --git a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx index 0e71c7bb6e..583beed9a5 100644 --- a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx +++ b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx @@ -1,30 +1,37 @@ import { useTranslation } from "react-i18next"; import { faMobile } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link, linkOptions, Outlet, useLocation, useRouterState } from "@tanstack/react-router"; +import { linkOptions, Outlet, useLocation, useRouterState } from "@tanstack/react-router"; import { AnimatePresence, motion } from "framer-motion"; import { twMerge } from "tailwind-merge"; import { CreateOrgModal } from "@app/components/organization/CreateOrgModal"; import { Banner } from "@app/components/page-frames/Banner"; +import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2"; import { - BreadcrumbContainer, - Menu, - MenuGroup, - MenuItem, - TBreadcrumbFormat -} from "@app/components/v2"; -import { useServerConfig } from "@app/context"; + OrgPermissionActions, + OrgPermissionSubjects, + useOrgPermission, + useServerConfig +} from "@app/context"; import { usePopUp } from "@app/hooks"; import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner"; import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar"; import { SidebarHeader } from "./components/SidebarHeader"; +import { DefaultSideBar, SecretSharingSideBar } from "./ProductsSideBar"; export const OrganizationLayout = () => { const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context }); const location = useLocation(); const { config } = useServerConfig(); + const { permission } = useOrgPermission(); + + const shouldShowProductsSidebar = permission.can( + OrgPermissionActions.Edit, + OrgPermissionSubjects.Settings + ); + const isOrganizationSpecificPage = location.pathname.startsWith("/organization"); const breadcrumbs = isOrganizationSpecificPage && matches && "breadcrumbs" in matches @@ -35,16 +42,23 @@ export const OrganizationLayout = () => { const { t } = useTranslation(); + const isSecretSharingPage = ( + [ + linkOptions({ to: "/organization/secret-sharing" }).to, + linkOptions({ to: "/organization/secret-sharing/settings" }).to + ] as string[] + ).includes(location.pathname); + const shouldShowOrgSidebar = location.pathname.startsWith("/organization") && + (!isSecretSharingPage || shouldShowProductsSidebar) && !( [ linkOptions({ to: "/organization/secret-manager/overview" }).to, linkOptions({ to: "/organization/cert-manager/overview" }).to, linkOptions({ to: "/organization/ssh/overview" }).to, linkOptions({ to: "/organization/kms/overview" }).to, - linkOptions({ to: "/organization/secret-scanning" }).to, - linkOptions({ to: "/organization/secret-sharing" }).to + linkOptions({ to: "/organization/secret-scanning" }).to ] as string[] ).includes(location.pathname); @@ -73,58 +87,7 @@ export const OrganizationLayout = () => {
- - - - {({ isActive }) => ( - - Audit Logs - - )} - - {(window.location.origin.includes("https://app.infisical.com") || - window.location.origin.includes("https://eu.infisical.com") || - window.location.origin.includes("https://gamma.infisical.com")) && ( - - {({ isActive }) => ( - - Usage & Billing - - )} - - )} - - - - {({ isActive }) => ( - - Access Control - - )} - - - {({ isActive }) => ( - - App Connections - - )} - - - {({ isActive }) => ( - - Gateways - - )} - - - {({ isActive }) => ( - - Organization Settings - - )} - - - + {isSecretSharingPage ? : } )} diff --git a/frontend/src/layouts/OrganizationLayout/ProductsSideBar/DefaultSideBar.tsx b/frontend/src/layouts/OrganizationLayout/ProductsSideBar/DefaultSideBar.tsx new file mode 100644 index 0000000000..e78d86703e --- /dev/null +++ b/frontend/src/layouts/OrganizationLayout/ProductsSideBar/DefaultSideBar.tsx @@ -0,0 +1,58 @@ +import { Link } from "@tanstack/react-router"; + +import { Menu, MenuGroup, MenuItem } from "@app/components/v2"; + +export const DefaultSideBar = () => ( + + + + {({ isActive }) => ( + + Audit Logs + + )} + + {(window.location.origin.includes("https://app.infisical.com") || + window.location.origin.includes("https://eu.infisical.com") || + window.location.origin.includes("https://gamma.infisical.com")) && ( + + {({ isActive }) => ( + + Usage & Billing + + )} + + )} + + + + {({ isActive }) => ( + + Access Control + + )} + + + {({ isActive }) => ( + + App Connections + + )} + + + {({ isActive }) => ( + + Gateways + + )} + + + {({ isActive }) => ( + + Organization Settings + + )} + + + +); diff --git a/frontend/src/layouts/OrganizationLayout/ProductsSideBar/SecretSharingSideBar.tsx b/frontend/src/layouts/OrganizationLayout/ProductsSideBar/SecretSharingSideBar.tsx new file mode 100644 index 0000000000..fbfc018c0c --- /dev/null +++ b/frontend/src/layouts/OrganizationLayout/ProductsSideBar/SecretSharingSideBar.tsx @@ -0,0 +1,34 @@ +import { Link, useMatchRoute } from "@tanstack/react-router"; + +import { Menu, MenuGroup, MenuItem } from "@app/components/v2"; + +export const SecretSharingSideBar = () => { + const matchRoute = useMatchRoute(); + const isOverviewActive = !!matchRoute({ + to: "/organization/secret-sharing", + fuzzy: false + }); + + return ( + + + + {() => ( + + Secret Sharing + + )} + + + + + {({ isActive }) => ( + + Settings + + )} + + + + ); +}; diff --git a/frontend/src/layouts/OrganizationLayout/ProductsSideBar/index.ts b/frontend/src/layouts/OrganizationLayout/ProductsSideBar/index.ts new file mode 100644 index 0000000000..b2c79d873c --- /dev/null +++ b/frontend/src/layouts/OrganizationLayout/ProductsSideBar/index.ts @@ -0,0 +1,2 @@ +export * from "./DefaultSideBar"; +export * from "./SecretSharingSideBar"; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx new file mode 100644 index 0000000000..0a9752242f --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx @@ -0,0 +1,35 @@ +import { Helmet } from "react-helmet"; +import { useTranslation } from "react-i18next"; + +import { PageHeader } from "@app/components/v2"; +import { + OrgPermissionAppConnectionActions, + OrgPermissionSubjects +} from "@app/context/OrgPermissionContext/types"; +import { withPermission } from "@app/hoc"; + +import { SecretSharingSettingsTabGroup } from "./components"; + +export const SecretSharingSettingsPage = withPermission( + () => { + const { t } = useTranslation(); + + return ( + <> + + {t("common.head-title", { title: t("settings.org.title") })} + +
+
+ + +
+
+ + ); + }, + { + action: OrgPermissionAppConnectionActions.Edit, + subject: OrgPermissionSubjects.Settings + } +); diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx new file mode 100644 index 0000000000..71fa8fa779 --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx @@ -0,0 +1,58 @@ +import { createNotification } from "@app/components/notifications"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Switch } from "@app/components/v2"; +import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { useUpdateOrg } from "@app/hooks/api"; + +export const SecretSharingAllowShareToAnyone = () => { + const { currentOrg } = useOrganization(); + const { mutateAsync } = useUpdateOrg(); + + const handleSecretSharingToggle = async (value: boolean) => { + try { + if (!currentOrg?.id) return; + + await mutateAsync({ + orgId: currentOrg.id, + secretShareSendToAnyone: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} secret sharing to members outside of this organization`, + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: (err as { response: { data: { message: string } } }).response.data.message, + type: "error" + }); + } + }; + + return ( +
+
+
+

+ Allow sharing secrets to members outside of this organization +

+ + {(isAllowed) => ( + handleSecretSharingToggle(value)} + isChecked={currentOrg?.secretShareSendToAnyone ?? false} + isDisabled={!isAllowed} + /> + )} + +
+

+ If enabled, team members will be able to share secrets to members outside of this + organization +

+
+
+ ); +}; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/index.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/index.tsx new file mode 100644 index 0000000000..d02460498f --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/index.tsx @@ -0,0 +1 @@ +export { SecretSharingAllowShareToAnyone } from "./SecretSharingAllowShareToAnyone"; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/SecretSharingSettingsGeneralTab.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/SecretSharingSettingsGeneralTab.tsx new file mode 100644 index 0000000000..ede3d9fc82 --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/SecretSharingSettingsGeneralTab.tsx @@ -0,0 +1,9 @@ +import { SecretSharingAllowShareToAnyone } from "../SecretSharingAllowShareToAnyone"; + +export const SecretSharingSettingsGeneralTab = () => { + return ( +
+ +
+ ); +}; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/index.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/index.tsx new file mode 100644 index 0000000000..3061090256 --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsGeneralTab/index.tsx @@ -0,0 +1 @@ +export { SecretSharingSettingsGeneralTab } from "./SecretSharingSettingsGeneralTab"; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/SecretSharingSettingsTabGroup.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/SecretSharingSettingsTabGroup.tsx new file mode 100644 index 0000000000..594df474f6 --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/SecretSharingSettingsTabGroup.tsx @@ -0,0 +1,39 @@ +import { useState } from "react"; +import { useSearch } from "@tanstack/react-router"; + +import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; + +import { SecretSharingSettingsGeneralTab } from "../SecretSharingSettingsGeneralTab"; + +export const SecretSharingSettingsTabGroup = () => { + const search = useSearch({ + from: ROUTE_PATHS.Organization.SecretSharingSettings.id + }); + const tabs = [ + { + name: "General", + key: "tab-secret-sharing-general", + component: SecretSharingSettingsGeneralTab + } + ]; + + const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key); + + return ( + + + {tabs.map((tab) => ( + + {tab.name} + + ))} + + {tabs.map(({ key, component: Component }) => ( + + + + ))} + + ); +}; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/index.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/index.tsx new file mode 100644 index 0000000000..2c60c7e209 --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingSettingsTabGroup/index.tsx @@ -0,0 +1 @@ +export { SecretSharingSettingsTabGroup } from "./SecretSharingSettingsTabGroup"; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/index.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/index.tsx new file mode 100644 index 0000000000..2c60c7e209 --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/index.tsx @@ -0,0 +1 @@ +export { SecretSharingSettingsTabGroup } from "./SecretSharingSettingsTabGroup"; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/route.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/route.tsx new file mode 100644 index 0000000000..b7a9e128d0 --- /dev/null +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/route.tsx @@ -0,0 +1,37 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { SecretSharingSettingsPage } from "./SecretSharingSettingsPage"; + +const SettingsPageQueryParams = z.object({ + selectedTab: z.string().catch("") +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings" +)({ + component: SecretSharingSettingsPage, + validateSearch: zodValidator(SettingsPageQueryParams), + search: { + middlewares: [stripSearchParams({ selectedTab: "" })] + }, + context: () => ({ + breadcrumbs: [ + { + label: "Home", + icon: () => , + link: linkOptions({ to: "/" }) + }, + { + label: "Secret Sharing", + link: linkOptions({ to: "/organization/secret-sharing" }) + }, + { + label: "Settings" + } + ] + }) +}); diff --git a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx index 25866778e3..8357c5db1d 100644 --- a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx +++ b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx @@ -7,6 +7,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2"; +import { useOrganization } from "@app/context"; import { useTimedReset } from "@app/hooks"; import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api"; import { SecretSharingAccessType } from "@app/hooks/api/secretSharing"; @@ -45,6 +46,7 @@ type Props = { export const ShareSecretForm = ({ isPublic, value }: Props) => { const [secretLink, setSecretLink] = useState(""); + const { currentOrg } = useOrganization(); const [, isCopyingSecret, setCopyTextSecret] = useTimedReset({ initialState: "Copy to clipboard" }); @@ -120,7 +122,14 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => { isError={Boolean(error)} errorText={error?.message} > - + )} /> @@ -155,7 +164,16 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => { errorText={error?.message} isOptional > - + )} /> @@ -214,7 +232,9 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => { onValueChange={(e) => onChange(e)} className="w-full" > - Anyone + {currentOrg?.secretShareSendToAnyone && ( + Anyone + )} People within your organization diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index e6bd0c8eea..ef31a5a333 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -42,7 +42,6 @@ import { Route as authProviderSuccessPageRouteImport } from './pages/auth/Provid import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route' import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route' import { Route as organizationSettingsPageRouteImport } from './pages/organization/SettingsPage/route' -import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route' import { Route as organizationSecretScanningPageRouteImport } from './pages/organization/SecretScanningPage/route' import { Route as organizationBillingPageRouteImport } from './pages/organization/BillingPage/route' import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route' @@ -54,6 +53,7 @@ import { Route as secretManagerLayoutImport } from './pages/secret-manager/layou import { Route as kmsLayoutImport } from './pages/kms/layout' import { Route as certManagerLayoutImport } from './pages/cert-manager/layout' import { Route as organizationSshOverviewPageRouteImport } from './pages/organization/SshOverviewPage/route' +import { Route as organizationSecretSharingSettingsPageRouteImport } from './pages/organization/SecretSharingSettingsPage/route' import { Route as organizationSecretManagerOverviewPageRouteImport } from './pages/organization/SecretManagerOverviewPage/route' import { Route as organizationRoleByIDPageRouteImport } from './pages/organization/RoleByIDPage/route' import { Route as organizationUserDetailsByIDPageRouteImport } from './pages/organization/UserDetailsByIDPage/route' @@ -61,6 +61,7 @@ import { Route as organizationKmsOverviewPageRouteImport } from './pages/organiz import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages/organization/IdentityDetailsByIDPage/route' import { Route as organizationGroupDetailsByIDPageRouteImport } from './pages/organization/GroupDetailsByIDPage/route' import { Route as organizationCertManagerOverviewPageRouteImport } from './pages/organization/CertManagerOverviewPage/route' +import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route' import { Route as organizationGatewaysGatewayListPageRouteImport } from './pages/organization/Gateways/GatewayListPage/route' import { Route as organizationAppConnectionsAppConnectionsPageRouteImport } from './pages/organization/AppConnections/AppConnectionsPage/route' import { Route as projectAccessControlPageRouteSshImport } from './pages/project/AccessControlPage/route-ssh' @@ -211,6 +212,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId', )() +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport = + createFileRoute( + '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing', + )() const AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/organization/gateways', @@ -467,6 +472,14 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRoute = getParentRoute: () => organizationLayoutRoute, } as any) +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute = + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport.update({ + id: '/secret-sharing', + path: '/secret-sharing', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, + } as any) + const AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport.update({ id: '/gateways', @@ -505,14 +518,6 @@ const organizationSettingsPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) -const organizationSecretSharingPageRouteRoute = - organizationSecretSharingPageRouteImport.update({ - id: '/secret-sharing', - path: '/secret-sharing', - getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, - } as any) - const organizationSecretScanningPageRouteRoute = organizationSecretScanningPageRouteImport.update({ id: '/secret-scanning', @@ -590,6 +595,14 @@ const organizationSshOverviewPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) +const organizationSecretSharingSettingsPageRouteRoute = + organizationSecretSharingSettingsPageRouteImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute, + } as any) + const organizationSecretManagerOverviewPageRouteRoute = organizationSecretManagerOverviewPageRouteImport.update({ id: '/secret-manager/overview', @@ -646,6 +659,14 @@ const organizationCertManagerOverviewPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) +const organizationSecretSharingPageRouteRoute = + organizationSecretSharingPageRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute, + } as any) + const organizationGatewaysGatewayListPageRouteRoute = organizationGatewaysGatewayListPageRouteImport.update({ id: '/', @@ -1887,13 +1908,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationSecretScanningPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } - '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': { - id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' - path: '/secret-sharing' - fullPath: '/organization/secret-sharing' - preLoaderRoute: typeof organizationSecretSharingPageRouteImport - parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport - } '/_authenticate/_inject-org-details/_org-layout/organization/settings': { id: '/_authenticate/_inject-org-details/_org-layout/organization/settings' path: '/settings' @@ -1929,6 +1943,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } + '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' + path: '/secret-sharing' + fullPath: '/organization/secret-sharing' + preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport + } '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': { id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' path: '/secret-manager/$projectId' @@ -1957,6 +1978,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationGatewaysGatewayListPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport } + '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/' + path: '/' + fullPath: '/organization/secret-sharing/' + preLoaderRoute: typeof organizationSecretSharingPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport + } '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': { id: '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview' path: '/cert-manager/overview' @@ -2006,6 +2034,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationSecretManagerOverviewPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } + '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings' + path: '/settings' + fullPath: '/organization/secret-sharing/settings' + preLoaderRoute: typeof organizationSecretSharingSettingsPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport + } '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview': { id: '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview' path: '/ssh/overview' @@ -2965,16 +3000,34 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteChildren, ) +interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren { + organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute + organizationSecretSharingSettingsPageRouteRoute: typeof organizationSecretSharingSettingsPageRouteRoute +} + +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren = + { + organizationSecretSharingPageRouteRoute: + organizationSecretSharingPageRouteRoute, + organizationSecretSharingSettingsPageRouteRoute: + organizationSecretSharingSettingsPageRouteRoute, + } + +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren = + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute._addFileChildren( + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren, + ) + interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren { organizationAccessManagementPageRouteRoute: typeof organizationAccessManagementPageRouteRoute organizationAdminPageRouteRoute: typeof organizationAdminPageRouteRoute organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute organizationSecretScanningPageRouteRoute: typeof organizationSecretScanningPageRouteRoute - organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute organizationSettingsPageRouteRoute: typeof organizationSettingsPageRouteRoute AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren organizationCertManagerOverviewPageRouteRoute: typeof organizationCertManagerOverviewPageRouteRoute organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute organizationIdentityDetailsByIDPageRouteRoute: typeof organizationIdentityDetailsByIDPageRouteRoute @@ -2994,13 +3047,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica organizationBillingPageRouteRoute: organizationBillingPageRouteRoute, organizationSecretScanningPageRouteRoute: organizationSecretScanningPageRouteRoute, - organizationSecretSharingPageRouteRoute: - organizationSecretSharingPageRouteRoute, organizationSettingsPageRouteRoute: organizationSettingsPageRouteRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren, + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren, organizationCertManagerOverviewPageRouteRoute: organizationCertManagerOverviewPageRouteRoute, organizationGroupDetailsByIDPageRouteRoute: @@ -3672,16 +3725,17 @@ export interface FileRoutesByFullPath { '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute '/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute - '/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute '/organization/settings': typeof organizationSettingsPageRouteRoute '/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren '/kms/$projectId': typeof kmsLayoutRouteWithChildren '/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren '/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren + '/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren '/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren '/ssh/$projectId': typeof sshLayoutRouteWithChildren '/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/organization/gateways/': typeof organizationGatewaysGatewayListPageRouteRoute + '/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -3689,6 +3743,7 @@ export interface FileRoutesByFullPath { '/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute '/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute '/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute + '/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute '/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute @@ -3845,7 +3900,6 @@ export interface FileRoutesByTo { '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute '/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute - '/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute '/organization/settings': typeof organizationSettingsPageRouteRoute '/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren '/kms/$projectId': typeof kmsLayoutRouteWithChildren @@ -3853,6 +3907,7 @@ export interface FileRoutesByTo { '/ssh/$projectId': typeof sshLayoutRouteWithChildren '/organization/app-connections': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/organization/gateways': typeof organizationGatewaysGatewayListPageRouteRoute + '/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -3860,6 +3915,7 @@ export interface FileRoutesByTo { '/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute '/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute '/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute + '/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute '/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute @@ -4025,16 +4081,17 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute - '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/settings': typeof organizationSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/kms/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren + '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/gateways/': typeof organizationGatewaysGatewayListPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -4042,6 +4099,7 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout': typeof certManagerLayoutRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren @@ -4208,16 +4266,17 @@ export interface FileRouteTypes { | '/organization/audit-logs' | '/organization/billing' | '/organization/secret-scanning' - | '/organization/secret-sharing' | '/organization/settings' | '/cert-manager/$projectId' | '/kms/$projectId' | '/organization/app-connections' | '/organization/gateways' + | '/organization/secret-sharing' | '/secret-manager/$projectId' | '/ssh/$projectId' | '/organization/app-connections/' | '/organization/gateways/' + | '/organization/secret-sharing/' | '/organization/cert-manager/overview' | '/organization/groups/$groupId' | '/organization/identities/$identityId' @@ -4225,6 +4284,7 @@ export interface FileRouteTypes { | '/organization/members/$membershipId' | '/organization/roles/$roleId' | '/organization/secret-manager/overview' + | '/organization/secret-sharing/settings' | '/organization/ssh/overview' | '/cert-manager/$projectId/overview' | '/cert-manager/$projectId/settings' @@ -4380,7 +4440,6 @@ export interface FileRouteTypes { | '/organization/audit-logs' | '/organization/billing' | '/organization/secret-scanning' - | '/organization/secret-sharing' | '/organization/settings' | '/cert-manager/$projectId' | '/kms/$projectId' @@ -4388,6 +4447,7 @@ export interface FileRouteTypes { | '/ssh/$projectId' | '/organization/app-connections' | '/organization/gateways' + | '/organization/secret-sharing' | '/organization/cert-manager/overview' | '/organization/groups/$groupId' | '/organization/identities/$identityId' @@ -4395,6 +4455,7 @@ export interface FileRouteTypes { | '/organization/members/$membershipId' | '/organization/roles/$roleId' | '/organization/secret-manager/overview' + | '/organization/secret-sharing/settings' | '/organization/ssh/overview' | '/cert-manager/$projectId/overview' | '/cert-manager/$projectId/settings' @@ -4558,16 +4619,17 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/organization/billing' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning' - | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' | '/_authenticate/_inject-org-details/_org-layout/organization/settings' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections' | '/_authenticate/_inject-org-details/_org-layout/organization/gateways' + | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/' | '/_authenticate/_inject-org-details/_org-layout/organization/gateways/' + | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/' | '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview' | '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId' @@ -4575,6 +4637,7 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId' | '/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview' + | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings' | '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout' @@ -4936,10 +4999,10 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/audit-logs", "/_authenticate/_inject-org-details/_org-layout/organization/billing", "/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning", - "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing", "/_authenticate/_inject-org-details/_org-layout/organization/settings", "/_authenticate/_inject-org-details/_org-layout/organization/app-connections", "/_authenticate/_inject-org-details/_org-layout/organization/gateways", + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing", "/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview", "/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId", @@ -4981,10 +5044,6 @@ export const routeTree = rootRoute "filePath": "organization/SecretScanningPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" }, - "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing": { - "filePath": "organization/SecretSharingPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/organization" - }, "/_authenticate/_inject-org-details/_org-layout/organization/settings": { "filePath": "organization/SettingsPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" @@ -5018,6 +5077,14 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/gateways/" ] }, + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing": { + "filePath": "", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization", + "children": [ + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/", + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings" + ] + }, "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout", @@ -5040,6 +5107,10 @@ export const routeTree = rootRoute "filePath": "organization/Gateways/GatewayListPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization/gateways" }, + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/": { + "filePath": "organization/SecretSharingPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" + }, "/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview": { "filePath": "organization/CertManagerOverviewPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" @@ -5068,6 +5139,10 @@ export const routeTree = rootRoute "filePath": "organization/SecretManagerOverviewPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" }, + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings": { + "filePath": "organization/SecretSharingSettingsPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" + }, "/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview": { "filePath": "organization/SshOverviewPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 983129e337..19cf8fc629 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -16,7 +16,10 @@ const organizationRoutes = route("/organization", [ route("/admin", "organization/AdminPage/route.tsx"), route("/audit-logs", "organization/AuditLogsPage/route.tsx"), route("/billing", "organization/BillingPage/route.tsx"), - route("/secret-sharing", "organization/SecretSharingPage/route.tsx"), + route("/secret-sharing", [ + index("organization/SecretSharingPage/route.tsx"), + route("/settings", "organization/SecretSharingSettingsPage/route.tsx") + ]), route("/settings", "organization/SettingsPage/route.tsx"), route("/secret-scanning", "organization/SecretScanningPage/route.tsx"), route("/groups/$groupId", "organization/GroupDetailsByIDPage/route.tsx"), From e46f10292ccb2d024e43c9d3919c893f41d9d2b1 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Tue, 11 Mar 2025 13:09:38 -0300 Subject: [PATCH 2/6] Fix createFileRoute issue due to a missing / on route definition --- frontend/src/const/routes.ts | 2 +- frontend/src/pages/organization/SecretSharingPage/route.tsx | 2 +- .../SecretSharingSettingsPage/SecretSharingSettingsPage.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index e0de3da444..2be3f3dbc0 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -23,7 +23,7 @@ export const ROUTE_PATHS = Object.freeze({ ), SecretSharing: setRoute( "/organization/secret-sharing", - "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/" ), SecretSharingSettings: setRoute( "/organization/secret-sharing/settings", diff --git a/frontend/src/pages/organization/SecretSharingPage/route.tsx b/frontend/src/pages/organization/SecretSharingPage/route.tsx index 827fc26238..2040fd1bbf 100644 --- a/frontend/src/pages/organization/SecretSharingPage/route.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/route.tsx @@ -11,7 +11,7 @@ const SecretSharingQueryParams = z.object({ }); export const Route = createFileRoute( - "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" + "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/" )({ component: SecretSharingPage, diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx index 0a9752242f..713438a602 100644 --- a/frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/SecretSharingSettingsPage.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { PageHeader } from "@app/components/v2"; import { - OrgPermissionAppConnectionActions, + OrgPermissionActions, OrgPermissionSubjects } from "@app/context/OrgPermissionContext/types"; import { withPermission } from "@app/hoc"; @@ -29,7 +29,7 @@ export const SecretSharingSettingsPage = withPermission( ); }, { - action: OrgPermissionAppConnectionActions.Edit, + action: OrgPermissionActions.Edit, subject: OrgPermissionSubjects.Settings } ); From 71b8e3dbce798aaff42e48780101ab04927c7c15 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Tue, 11 Mar 2025 13:44:42 -0300 Subject: [PATCH 3/6] Fix migration column name on check variable --- ...0311105617_add-share-to-anyone-setting-to-organizations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts b/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts index 71c3b736f7..8c0ccae3bc 100644 --- a/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts +++ b/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts @@ -4,7 +4,7 @@ import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { if (await knex.schema.hasTable(TableName.Organization)) { - const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareToAnyone"); + const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareSendToAnyone"); if (!hasSecretShareToAnyoneCol) { await knex.schema.alterTable(TableName.Organization, (t) => { @@ -16,7 +16,7 @@ export async function up(knex: Knex): Promise { export async function down(knex: Knex): Promise { if (await knex.schema.hasTable(TableName.Organization)) { - const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareToAnyone"); + const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareSendToAnyone"); if (hasSecretShareToAnyoneCol) { await knex.schema.alterTable(TableName.Organization, (t) => { t.dropColumn("secretShareSendToAnyone"); From c0de4ae3ee474d7b07dda7bf00343d9befcb4510 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Thu, 13 Mar 2025 12:44:38 -0300 Subject: [PATCH 4/6] Add secret share permissions --- .../ee/services/permission/org-permission.ts | 18 ++- .../src/context/OrgPermissionContext/types.ts | 10 +- .../OrganizationLayout/OrganizationLayout.tsx | 12 +- .../components/OrgRoleModifySection.utils.ts | 12 +- .../OrgPermissionSecretShareRow.tsx | 130 ++++++++++++++++++ .../RolePermissionRow.tsx | 2 +- .../RolePermissionsSection.tsx | 8 ++ .../SecretSharingSettingsPage.tsx | 6 +- 8 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index dbabcc2c12..8a26462278 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -32,6 +32,10 @@ export enum OrgPermissionAdminConsoleAction { AccessAllProjects = "access-all-projects" } +export enum OrgPermissionSecretShareAction { + ManageSettings = "manage-settings" +} + export enum OrgPermissionGatewayActions { // is there a better word for this. This mean can an identity be a gateway CreateGateways = "create-gateways", @@ -59,7 +63,8 @@ export enum OrgPermissionSubjects { ProjectTemplates = "project-templates", AppConnections = "app-connections", Kmip = "kmip", - Gateway = "gateway" + Gateway = "gateway", + SecretShare = "secret-share" } export type AppConnectionSubjectFields = { @@ -91,7 +96,8 @@ export type OrgPermissionSet = ) ] | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] - | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]; + | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip] + | [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare]; const AppConnectionConditionSchema = z .object({ @@ -185,6 +191,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [ "Describe what action an entity can take." ) }), + z.object({ + subject: z.literal(OrgPermissionSubjects.SecretShare).describe("The entity this permission pertains to."), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionSecretShareAction).describe( + "Describe what action an entity can take." + ) + }), z.object({ subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."), action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe( @@ -292,6 +304,8 @@ const buildAdminPermission = () => { // the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); + can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare); + return rules; }; diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 2a31ed6a8a..9e60b5cb33 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -34,13 +34,18 @@ export enum OrgPermissionSubjects { ProjectTemplates = "project-templates", AppConnections = "app-connections", Kmip = "kmip", - Gateway = "gateway" + Gateway = "gateway", + SecretShare = "secret-share" } export enum OrgPermissionAdminConsoleAction { AccessAllProjects = "access-all-projects" } +export enum OrgPermissionSecretShareAction { + ManageSettings = "manage-settings" +} + export enum OrgPermissionAppConnectionActions { Read = "read", Create = "create", @@ -78,7 +83,8 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates] | [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections] | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip] - | [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]; + | [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway] + | [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare]; // TODO(scott): add back once org UI refactored // | [ // OrgPermissionAppConnectionActions, diff --git a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx index 583beed9a5..7cfc21838a 100644 --- a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx +++ b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx @@ -8,12 +8,8 @@ import { twMerge } from "tailwind-merge"; import { CreateOrgModal } from "@app/components/organization/CreateOrgModal"; import { Banner } from "@app/components/page-frames/Banner"; import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2"; -import { - OrgPermissionActions, - OrgPermissionSubjects, - useOrgPermission, - useServerConfig -} from "@app/context"; +import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context"; +import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner"; @@ -28,8 +24,8 @@ export const OrganizationLayout = () => { const { permission } = useOrgPermission(); const shouldShowProductsSidebar = permission.can( - OrgPermissionActions.Edit, - OrgPermissionSubjects.Settings + OrgPermissionSecretShareAction.ManageSettings, + OrgPermissionSubjects.SecretShare ); const isOrganizationSpecificPage = location.pathname.startsWith("/organization"); diff --git a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts index 42dd314172..512368fbf6 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts +++ b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts @@ -5,7 +5,8 @@ import { OrgPermissionSubjects } from "@app/context"; import { OrgGatewayPermissionActions, OrgPermissionAppConnectionActions, - OrgPermissionKmipActions + OrgPermissionKmipActions, + OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types"; import { TPermission } from "@app/hooks/api/roles/types"; @@ -50,6 +51,12 @@ const adminConsolePermissionSchmea = z }) .optional(); +const secretSharingPermissionSchema = z + .object({ + [OrgPermissionSecretShareAction.ManageSettings]: z.boolean().optional() + }) + .optional(); + export const formSchema = z.object({ name: z.string().trim(), description: z.string().trim().optional(), @@ -83,7 +90,8 @@ export const formSchema = z.object({ [OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema, "app-connections": appConnectionsPermissionSchema, kmip: kmipPermissionSchema, - gateway: orgGatewayPermissionSchema + gateway: orgGatewayPermissionSchema, + "secret-share": secretSharingPermissionSchema }) .optional() }); diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx new file mode 100644 index 0000000000..4a35644c7e --- /dev/null +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx @@ -0,0 +1,130 @@ +import { useEffect, useMemo } from "react"; +import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form"; +import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2"; +import { useToggle } from "@app/hooks"; + +import { TFormSchema } from "../OrgRoleModifySection.utils"; + +type Props = { + isEditable: boolean; + setValue: UseFormSetValue; + control: Control; +}; + +enum Permission { + NoAccess = "no-access", + Custom = "custom" +} + +const PERMISSION_ACTIONS = [{ action: "manage-settings", label: "Manage settings" }] as const; + +export const OrgPermissionSecretShareRow = ({ isEditable, control, setValue }: Props) => { + const [isRowExpanded, setIsRowExpanded] = useToggle(); + const [isCustom, setIsCustom] = useToggle(); + + const rule = useWatch({ + control, + name: "permissions.secret-share" + }); + + const selectedPermissionCategory = useMemo(() => { + if (rule?.["manage-settings"]) { + return Permission.Custom; + } + return Permission.NoAccess; + }, [rule, isCustom]); + + useEffect(() => { + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + }, [selectedPermissionCategory]); + + useEffect(() => { + const isRowCustom = selectedPermissionCategory === Permission.Custom; + if (isRowCustom) { + setIsRowExpanded.on(); + } + }, []); + + const handlePermissionChange = (val: Permission) => { + if (!val) return; + if (val === Permission.Custom) { + setIsRowExpanded.on(); + setIsCustom.on(); + return; + } + setIsCustom.off(); + + if (val === Permission.NoAccess) { + setValue("permissions.secret-share", { "manage-settings": false }, { shouldDirty: true }); + } + }; + + return ( + <> + setIsRowExpanded.toggle()} + > + + + + Secret Share + + + + + {isRowExpanded && ( + + +
+ {PERMISSION_ACTIONS.map(({ action, label }) => { + return ( + ( + { + if (!isEditable) { + createNotification({ + type: "error", + text: "Failed to update default role" + }); + return; + } + field.onChange(e); + }} + id={`permissions.secret-share.${action}`} + > + {label} + + )} + /> + ); + })} +
+ + + )} + + ); +}; diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx index aed309d04a..0bc765eb09 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx @@ -74,7 +74,7 @@ type Props = { title: string; formName: keyof Omit< Exclude, - "workspace" | "organization-admin-console" | "kmip" | "gateway" + "workspace" | "organization-admin-console" | "kmip" | "gateway" | "secret-share" >; setValue: UseFormSetValue; control: Control; diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx index de9fbde802..8d0a3d7d98 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -16,6 +16,7 @@ import { import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow"; import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow"; import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow"; +import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow"; import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow"; import { RolePermissionRow } from "./RolePermissionRow"; @@ -100,6 +101,8 @@ export const RolePermissionsSection = ({ roleId }: Props) => { const onSubmit = async (el: TFormSchema) => { try { + console.log(el.permissions); + console.log(formRolePermission2API(el.permissions)); await updateRole({ orgId, id: roleId, @@ -177,6 +180,11 @@ export const RolePermissionsSection = ({ roleId }: Props) => { setValue={setValue} isEditable={isCustomRole} /> + Date: Fri, 14 Mar 2025 08:54:14 -0300 Subject: [PATCH 5/6] Fix rebase issue with deleted files --- ...d-share-to-anyone-setting-to-organizations.ts | 14 ++++++++++---- backend/src/db/schemas/organizations.ts | 2 +- .../src/server/routes/v1/organization-router.ts | 2 +- backend/src/services/org/org-service.ts | 16 +++++++++++++--- backend/src/services/org/org-types.ts | 2 +- .../secret-sharing/secret-sharing-service.ts | 7 +++++++ frontend/src/hooks/api/organization/queries.tsx | 4 ++-- frontend/src/hooks/api/organization/types.ts | 4 ++-- .../RolePermissionsSection.tsx | 2 -- .../SecretSharingAllowShareToAnyone.tsx | 4 ++-- .../components/ShareSecretForm.tsx | 2 +- 11 files changed, 40 insertions(+), 19 deletions(-) diff --git a/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts b/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts index 8c0ccae3bc..8f767c7485 100644 --- a/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts +++ b/backend/src/db/migrations/20250311105617_add-share-to-anyone-setting-to-organizations.ts @@ -4,11 +4,14 @@ import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { if (await knex.schema.hasTable(TableName.Organization)) { - const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareSendToAnyone"); + const hasSecretShareToAnyoneCol = await knex.schema.hasColumn( + TableName.Organization, + "allowSecretSharingOutsideOrganization" + ); if (!hasSecretShareToAnyoneCol) { await knex.schema.alterTable(TableName.Organization, (t) => { - t.boolean("secretShareSendToAnyone").defaultTo(true); + t.boolean("allowSecretSharingOutsideOrganization").defaultTo(true); }); } } @@ -16,10 +19,13 @@ export async function up(knex: Knex): Promise { export async function down(knex: Knex): Promise { if (await knex.schema.hasTable(TableName.Organization)) { - const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(TableName.Organization, "secretShareSendToAnyone"); + const hasSecretShareToAnyoneCol = await knex.schema.hasColumn( + TableName.Organization, + "allowSecretSharingOutsideOrganization" + ); if (hasSecretShareToAnyoneCol) { await knex.schema.alterTable(TableName.Organization, (t) => { - t.dropColumn("secretShareSendToAnyone"); + t.dropColumn("allowSecretSharingOutsideOrganization"); }); } } diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index 284ad8d58c..6639528a09 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -23,7 +23,7 @@ export const OrganizationsSchema = z.object({ defaultMembershipRole: z.string().default("member"), enforceMfa: z.boolean().default(false), selectedMfaMethod: z.string().nullable().optional(), - secretShareSendToAnyone: z.boolean().default(true).nullable().optional() + allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional() }); export type TOrganizations = z.infer; diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 3cc17a3d82..21723ef581 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -258,7 +258,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(), enforceMfa: z.boolean().optional(), selectedMfaMethod: z.nativeEnum(MfaMethod).optional(), - secretShareSendToAnyone: z.boolean().optional() + allowSecretSharingOutsideOrganization: z.boolean().optional() }), response: { 200: z.object({ diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 12b5a75058..4ef52a551b 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -19,7 +19,11 @@ import { import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { + OrgPermissionActions, + OrgPermissionSecretShareAction, + OrgPermissionSubjects +} from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal"; @@ -294,13 +298,19 @@ export const orgServiceFactory = ({ defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod, - secretShareSendToAnyone + allowSecretSharingOutsideOrganization } }: TUpdateOrgDTO) => { const appCfg = getConfig(); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); + if (allowSecretSharingOutsideOrganization !== undefined) { + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionSecretShareAction.ManageSettings, + OrgPermissionSubjects.SecretShare + ); + } const plan = await licenseService.getPlan(orgId); const currentOrg = await orgDAL.findOrgById(actorOrgId); @@ -368,7 +378,7 @@ export const orgServiceFactory = ({ defaultMembershipRole, enforceMfa, selectedMfaMethod, - secretShareSendToAnyone + allowSecretSharingOutsideOrganization }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); return org; diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index b6dabe1216..3c6faeae95 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -72,7 +72,7 @@ export type TUpdateOrgDTO = { defaultMembershipRoleSlug: string; enforceMfa: boolean; selectedMfaMethod: MfaMethod; - secretShareSendToAnyone: boolean; + allowSecretSharingOutsideOrganization: boolean; }>; } & TOrgPermission; diff --git a/backend/src/services/secret-sharing/secret-sharing-service.ts b/backend/src/services/secret-sharing/secret-sharing-service.ts index 1ce7acf44e..9649be7228 100644 --- a/backend/src/services/secret-sharing/secret-sharing-service.ts +++ b/backend/src/services/secret-sharing/secret-sharing-service.ts @@ -82,6 +82,13 @@ export const secretSharingServiceFactory = ({ if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" }); $validateSharedSecretExpiry(expiresAt); + const org = await orgDAL.findOrgById(orgId); + if (!org.allowSecretSharingOutsideOrganization && accessType === SecretSharingAccessType.Anyone) { + throw new BadRequestError({ + message: "Organization does not allow sharing secrets to members outside of this organization" + }); + } + if (secretValue.length > 10_000) { throw new BadRequestError({ message: "Shared secret value too long" }); } diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 64f8f2eb12..d3e925dcaf 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -110,7 +110,7 @@ export const useUpdateOrg = () => { defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod, - secretShareSendToAnyone + allowSecretSharingOutsideOrganization }) => { return apiRequest.patch(`/api/v1/organization/${orgId}`, { name, @@ -120,7 +120,7 @@ export const useUpdateOrg = () => { defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod, - secretShareSendToAnyone + allowSecretSharingOutsideOrganization }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 6ef32ff7ee..2e082f4083 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -15,7 +15,7 @@ export type Organization = { defaultMembershipRole: string; enforceMfa: boolean; selectedMfaMethod?: MfaMethod; - secretShareSendToAnyone?: boolean; + allowSecretSharingOutsideOrganization?: boolean; }; export type UpdateOrgDTO = { @@ -27,7 +27,7 @@ export type UpdateOrgDTO = { defaultMembershipRoleSlug?: string; enforceMfa?: boolean; selectedMfaMethod?: MfaMethod; - secretShareSendToAnyone?: boolean; + allowSecretSharingOutsideOrganization?: boolean; }; export type BillingDetails = { diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx index 8d0a3d7d98..7a2feba36c 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -101,8 +101,6 @@ export const RolePermissionsSection = ({ roleId }: Props) => { const onSubmit = async (el: TFormSchema) => { try { - console.log(el.permissions); - console.log(formRolePermission2API(el.permissions)); await updateRole({ orgId, id: roleId, diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx index 71fa8fa779..1ea62c187c 100644 --- a/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/SecretSharingAllowShareToAnyone/SecretSharingAllowShareToAnyone.tsx @@ -14,7 +14,7 @@ export const SecretSharingAllowShareToAnyone = () => { await mutateAsync({ orgId: currentOrg.id, - secretShareSendToAnyone: value + allowSecretSharingOutsideOrganization: value }); createNotification({ @@ -42,7 +42,7 @@ export const SecretSharingAllowShareToAnyone = () => { handleSecretSharingToggle(value)} - isChecked={currentOrg?.secretShareSendToAnyone ?? false} + isChecked={currentOrg?.allowSecretSharingOutsideOrganization ?? false} isDisabled={!isAllowed} /> )} diff --git a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx index 8357c5db1d..07a6336fa5 100644 --- a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx +++ b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx @@ -232,7 +232,7 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => { onValueChange={(e) => onChange(e)} className="w-full" > - {currentOrg?.secretShareSendToAnyone && ( + {currentOrg?.allowSecretSharingOutsideOrganization && ( Anyone )} From ea27870ce334380c7574fec1afc16d1faaff703e Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 14 Mar 2025 16:12:40 -0300 Subject: [PATCH 6/6] Move useOrganization outside ShareSecretForm as it's used on a public page --- .../components/ShareSecret/AddShareSecretModal.tsx | 5 +++++ .../ShareSecretPage/components/ShareSecretForm.tsx | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/AddShareSecretModal.tsx b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/AddShareSecretModal.tsx index 3378313b71..45b974755e 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/AddShareSecretModal.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/AddShareSecretModal.tsx @@ -1,4 +1,5 @@ import { Modal, ModalContent } from "@app/components/v2"; +import { useOrganization } from "@app/context"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { ShareSecretForm } from "@app/pages/public/ShareSecretPage/components"; @@ -11,6 +12,7 @@ type Props = { }; export const AddShareSecretModal = ({ popUp, handlePopUpToggle }: Props) => { + const { currentOrg } = useOrganization(); return ( { diff --git a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx index 07a6336fa5..a2228e46b7 100644 --- a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx +++ b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx @@ -7,7 +7,6 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2"; -import { useOrganization } from "@app/context"; import { useTimedReset } from "@app/hooks"; import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api"; import { SecretSharingAccessType } from "@app/hooks/api/secretSharing"; @@ -42,11 +41,15 @@ export type FormData = z.infer; type Props = { isPublic: boolean; // whether or not this is a public (non-authenticated) secret sharing form value?: string; + allowSecretSharingOutsideOrganization?: boolean; }; -export const ShareSecretForm = ({ isPublic, value }: Props) => { +export const ShareSecretForm = ({ + isPublic, + value, + allowSecretSharingOutsideOrganization = true +}: Props) => { const [secretLink, setSecretLink] = useState(""); - const { currentOrg } = useOrganization(); const [, isCopyingSecret, setCopyTextSecret] = useTimedReset({ initialState: "Copy to clipboard" }); @@ -232,7 +235,7 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => { onValueChange={(e) => onChange(e)} className="w-full" > - {currentOrg?.allowSecretSharingOutsideOrganization && ( + {allowSecretSharingOutsideOrganization && ( Anyone )}