Merge branch 'main' into feat/multipleApprovalEnvs

This commit is contained in:
Carlos Monastyrski
2025-07-24 12:03:07 -03:00
595 changed files with 12408 additions and 6322 deletions
@@ -4,6 +4,8 @@ import { useNavigate, useSearch } from "@tanstack/react-router";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProjectAccessControlTabs } from "@app/types/project";
import {
@@ -24,7 +26,7 @@ const Page = () => {
const updateSelectedTab = (tab: string) => {
navigate({
to: "/projects/$projectId/access-management",
to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const,
search: (prev) => ({ ...prev, selectedTab: tab }),
params: {
projectId: currentWorkspace.id
@@ -32,6 +34,8 @@ const Page = () => {
});
};
const isSecretManager = currentWorkspace.type === ProjectType.SecretManager;
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl">
@@ -48,7 +52,9 @@ const Page = () => {
<p>Machine Identities</p>
</div>
</Tab>
<Tab value={ProjectAccessControlTabs.ServiceTokens}>Service Tokens</Tab>
{isSecretManager && (
<Tab value={ProjectAccessControlTabs.ServiceTokens}>Service Tokens</Tab>
)}
<Tab value={ProjectAccessControlTabs.Roles}>Project Roles</Tab>
</TabList>
<TabPanel value={ProjectAccessControlTabs.Member}>
@@ -60,9 +66,11 @@ const Page = () => {
<TabPanel value={ProjectAccessControlTabs.Identities}>
<IdentityTab />
</TabPanel>
<TabPanel value={ProjectAccessControlTabs.ServiceTokens}>
<ServiceTokenTab />
</TabPanel>
{isSecretManager && (
<TabPanel value={ProjectAccessControlTabs.ServiceTokens}>
<ServiceTokenTab />
</TabPanel>
)}
<TabPanel value={ProjectAccessControlTabs.Roles}>
<ProjectRoleListTab />
</TabPanel>
@@ -33,6 +33,7 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import {
getUserTablePreference,
PreferenceKey,
@@ -158,7 +159,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
onKeyDown={(evt) => {
if (evt.key === "Enter") {
navigate({
to: "/projects/$projectId/groups/$groupId",
to: `${getProjectBaseURL(currentWorkspace.type)}/groups/$groupId` as const,
params: {
projectId: currentWorkspace.id,
groupId: id
@@ -168,7 +169,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
}}
onClick={() =>
navigate({
to: "/projects/$projectId/groups/$groupId",
to: `${getProjectBaseURL(currentWorkspace.type)}/groups/$groupId` as const,
params: {
projectId: currentWorkspace.id,
groupId: id
@@ -46,6 +46,7 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { formatProjectRoleName } from "@app/helpers/roles";
import {
getUserTablePreference,
@@ -260,7 +261,7 @@ export const IdentityTab = withProjectPermission(
onKeyDown={(evt) => {
if (evt.key === "Enter") {
navigate({
to: "/projects/$projectId/identities/$identityId",
to: `${getProjectBaseURL(currentWorkspace.type)}/identities/$identityId` as const,
params: {
projectId: currentWorkspace.id,
identityId: id
@@ -270,7 +271,7 @@ export const IdentityTab = withProjectPermission(
}}
onClick={() =>
navigate({
to: "/projects/$projectId/identities/$identityId",
to: `${getProjectBaseURL(currentWorkspace.type)}/identities/$identityId` as const,
params: {
projectId: currentWorkspace.id,
identityId: id
@@ -2,6 +2,7 @@ import { Link } from "@tanstack/react-router";
import { Alert, AlertDescription } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { TWorkspaceUser } from "@app/hooks/api/types";
import { MemberRbacSection } from "./MemberRbacSection";
@@ -21,7 +22,7 @@ export const MemberRoleForm = ({ projectMember, onOpenUpgradeModal }: Props) =>
>
<AlertDescription>
<Link
to="/projects/$projectId/members/$membershipId"
to={`${getProjectBaseURL(currentWorkspace.type)}/members/$membershipId` as const}
params={{
projectId: currentWorkspace.id,
membershipId: projectMember.id
@@ -50,6 +50,7 @@ import {
useUser,
useWorkspace
} from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { formatProjectRoleName } from "@app/helpers/roles";
import {
getUserTablePreference,
@@ -311,7 +312,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
onKeyDown={(evt) => {
if (evt.key === "Enter") {
navigate({
to: "/projects/$projectId/members/$membershipId",
to: `${getProjectBaseURL(currentWorkspace.type)}/members/$membershipId`,
params: {
projectId: workspaceId,
membershipId
@@ -321,7 +322,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
}}
onClick={() =>
navigate({
to: "/projects/$projectId/members/$membershipId",
to: `${getProjectBaseURL(currentWorkspace.type)}/members/$membershipId`,
params: {
projectId: workspaceId,
membershipId
@@ -40,6 +40,7 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { isCustomProjectRole } from "@app/helpers/roles";
import {
getUserTablePreference,
@@ -249,7 +250,7 @@ export const ProjectRoleList = () => {
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() =>
navigate({
to: "/projects/$projectId/roles/$roleSlug",
to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug`,
params: {
projectId: currentWorkspace.id,
roleSlug: slug
@@ -291,7 +292,7 @@ export const ProjectRoleList = () => {
onClick={(e) => {
e.stopPropagation();
navigate({
to: "/projects/$projectId/roles/$roleSlug",
to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug`,
params: {
projectId: currentWorkspace.id,
roleSlug: slug
@@ -0,0 +1,32 @@
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { ProjectAccessControlTabs } from "@app/types/project";
import { AccessControlPage } from "./AccessControlPage";
const AccessControlPageQuerySchema = z.object({
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
requesterEmail: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management"
)({
component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema),
search: {
middlewares: [stripSearchParams({ requesterEmail: "" })]
},
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control"
}
]
};
}
});
@@ -0,0 +1,32 @@
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { ProjectAccessControlTabs } from "@app/types/project";
import { AccessControlPage } from "./AccessControlPage";
const AccessControlPageQuerySchema = z.object({
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
requesterEmail: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management"
)({
component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema),
search: {
middlewares: [stripSearchParams({ requesterEmail: "" })]
},
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control"
}
]
};
}
});
@@ -0,0 +1,32 @@
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { ProjectAccessControlTabs } from "@app/types/project";
import { AccessControlPage } from "./AccessControlPage";
const AccessControlPageQuerySchema = z.object({
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
requesterEmail: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management"
)({
component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema),
search: {
middlewares: [stripSearchParams({ requesterEmail: "" })]
},
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control"
}
]
};
}
});
@@ -12,7 +12,7 @@ const AccessControlPageQuerySchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/_project-general-layout/access-management"
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management"
)({
component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema),
@@ -0,0 +1,32 @@
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { ProjectAccessControlTabs } from "@app/types/project";
import { AccessControlPage } from "./AccessControlPage";
const AccessControlPageQuerySchema = z.object({
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
requesterEmail: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management"
)({
component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema),
search: {
middlewares: [stripSearchParams({ requesterEmail: "" })]
},
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control"
}
]
};
}
});
@@ -15,6 +15,7 @@ import {
} from "@app/components/v2";
import { CopyButton } from "@app/components/v2/CopyButton";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { usePopUp } from "@app/hooks";
import { useDeleteGroupFromWorkspace } from "@app/hooks/api";
import { TGroupMembership } from "@app/hooks/api/groups/types";
@@ -46,7 +47,7 @@ export const GroupDetailsSection = ({ groupMembership }: Props) => {
});
navigate({
to: "/projects/$projectId/access-management",
to: `${getProjectBaseURL(currentWorkspace.type)}/access-management`,
params: {
projectId: currentWorkspace.id
},
@@ -24,7 +24,7 @@ import {
Tr
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
import { getProjectHomePage } from "@app/helpers/project";
import {
getUserTablePreference,
PreferenceKey,
@@ -36,7 +36,6 @@ import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useListProjectGroupUsers } from "@app/hooks/api/groups/queries";
import { EFilterReturnedUsers, TGroupMembership } from "@app/hooks/api/groups/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { GroupMembershipRow } from "./GroupMembershipRow";
@@ -137,9 +136,7 @@ export const GroupMembersTable = ({ groupMembership }: Props) => {
text: "User privilege assumption has started"
});
const url = getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
);
const url = getProjectHomePage(currentWorkspace.type);
window.location.href = url.replace("$projectId", currentWorkspace.id);
}
}
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/groups/$groupId"
)({
component: GroupDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/cert-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Groups
}
})
},
{
label: "Group"
}
]
};
}
});
@@ -5,7 +5,7 @@ import { ProjectAccessControlTabs } from "@app/types/project";
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/_project-general-layout/groups/$groupId"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/groups/$groupId"
)({
component: GroupDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Access Control",
link: linkOptions({
to: "/projects/$projectId/access-management",
to: "/projects/kms/$projectId/access-management",
params: {
projectId: params.projectId
},
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/groups/$groupId"
)({
component: GroupDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Groups
}
})
},
{
label: "Group"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId"
)({
component: GroupDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Groups
}
})
},
{
label: "Group"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/groups/$groupId"
)({
component: GroupDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/ssh/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Groups
}
})
},
{
label: "Group"
}
]
};
}
});
@@ -20,7 +20,7 @@ import {
ProjectPermissionSub,
useWorkspace
} from "@app/context";
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
import { getProjectBaseURL, getProjectHomePage } from "@app/helpers/project";
import { usePopUp } from "@app/hooks";
import {
useAssumeProjectPrivileges,
@@ -28,7 +28,6 @@ import {
useGetWorkspaceIdentityMembershipDetails
} from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { IdentityProjectAdditionalPrivilegeSection } from "./components/IdentityProjectAdditionalPrivilegeSection";
import { IdentityRoleDetailsSection } from "./components/IdentityRoleDetailsSection";
@@ -68,9 +67,7 @@ const Page = () => {
type: "success",
text: "Identity privilege assumption has started"
});
const url = getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
);
const url = getProjectHomePage(currentWorkspace.type);
window.location.href = url.replace("$projectId", currentWorkspace.id);
}
}
@@ -89,7 +86,7 @@ const Page = () => {
});
handlePopUpClose("deleteIdentity");
navigate({
to: "/projects/$projectId/access-management",
to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const,
params: {
projectId: workspaceId
},
@@ -225,7 +225,7 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
>
Save
</Button>
<AddPoliciesButton isDisabled={isDisabled} />
<AddPoliciesButton isDisabled={isDisabled} projectType={currentWorkspace.type} />
</div>
</div>
</div>
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/identities/$identityId"
)({
component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/cert-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Identities
}
})
},
{
label: "Machine Identity"
}
]
};
}
});
@@ -5,7 +5,7 @@ import { ProjectAccessControlTabs } from "@app/types/project";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/_project-general-layout/identities/$identityId"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/identities/$identityId"
)({
component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Access Control",
link: linkOptions({
to: "/projects/$projectId/access-management",
to: "/projects/kms/$projectId/access-management",
params: {
projectId: params.projectId
},
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/identities/$identityId"
)({
component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Identities
}
})
},
{
label: "Machine Identity"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId"
)({
component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Identities
}
})
},
{
label: "Machine Identity"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/identities/$identityId"
)({
component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/ssh/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Identities
}
})
},
{
label: "Machine Identity"
}
]
};
}
});
@@ -21,7 +21,7 @@ import {
useOrganization,
useWorkspace
} from "@app/context";
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
import { getProjectBaseURL, getProjectHomePage } from "@app/helpers/project";
import { usePopUp } from "@app/hooks";
import {
useAssumeProjectPrivileges,
@@ -29,7 +29,6 @@ import {
useGetWorkspaceUserDetails
} from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { MemberProjectAdditionalPrivilegeSection } from "./components/MemberProjectAdditionalPrivilegeSection";
import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection";
@@ -73,9 +72,7 @@ export const Page = () => {
text: "User privilege assumption has started"
});
const url = getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
);
const url = getProjectHomePage(currentWorkspace.type);
window.location.href = url.replace("$projectId", currentWorkspace.id);
}
}
@@ -96,7 +93,7 @@ export const Page = () => {
type: "success"
});
navigate({
to: "/projects/$projectId/access-management",
to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const,
params: {
projectId: currentWorkspace.id
}
@@ -221,7 +221,7 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
>
Save
</Button>
<AddPoliciesButton isDisabled={isDisabled} />
<AddPoliciesButton isDisabled={isDisabled} projectType={currentWorkspace.type} />
</div>
</div>
</div>
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/members/$membershipId"
)({
component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/cert-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Member
}
})
},
{
label: "User"
}
]
};
}
});
@@ -5,7 +5,7 @@ import { ProjectAccessControlTabs } from "@app/types/project";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/_project-general-layout/members/$membershipId"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/members/$membershipId"
)({
component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Access Control",
link: linkOptions({
to: "/projects/$projectId/access-management",
to: "/projects/kms/$projectId/access-management",
params: {
projectId: params.projectId
},
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/members/$membershipId"
)({
component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Member
}
})
},
{
label: "User"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/members/$membershipId"
)({
component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Member
}
})
},
{
label: "User"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/members/$membershipId"
)({
component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/ssh/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Member
}
})
},
{
label: "User"
}
]
};
}
});
@@ -17,6 +17,7 @@ import {
PageHeader
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { useDeleteProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { usePopUp } from "@app/hooks/usePopUp";
@@ -60,7 +61,7 @@ const Page = () => {
});
handlePopUpClose("deleteRole");
navigate({
to: "/projects/$projectId/access-management",
to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const,
params: {
projectId
},
@@ -111,6 +112,32 @@ const Page = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={2} className="p-1">
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(data.id);
createNotification({
text: "Copied ID to clipboard",
type: "info"
});
}}
icon={<FontAwesomeIcon icon={faCopy} />}
>
Copy ID
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(data.slug);
createNotification({
text: "Copied slug to clipboard",
type: "info"
});
}}
icon={<FontAwesomeIcon icon={faCopy} />}
>
Copy Slug
</DropdownMenuItem>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Role}
@@ -9,14 +9,16 @@ import {
IconButton
} from "@app/components/v2";
import { usePopUp } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
import { PolicyTemplateModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal";
type Props = {
isDisabled?: boolean;
projectType: ProjectType;
};
export const AddPoliciesButton = ({ isDisabled }: Props) => {
export const AddPoliciesButton = ({ isDisabled, projectType }: Props) => {
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addPolicy",
"addPolicyOptions",
@@ -66,10 +68,12 @@ export const AddPoliciesButton = ({ isDisabled }: Props) => {
</DropdownMenuContent>
</DropdownMenu>
<PolicySelectionModal
type={projectType}
isOpen={popUp.addPolicy.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addPolicy", isOpen)}
/>
<PolicyTemplateModal
type={projectType}
isOpen={popUp.applyTemplate.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("applyTemplate", isOpen)}
/>
@@ -7,6 +7,7 @@ import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent, Spinner } from "@app/components/v2";
import { ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { getProjectBaseURL } from "@app/helpers/project";
import { useCreateProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api";
import { TProjectRole } from "@app/hooks/api/roles/types";
import { slugSchema } from "@app/lib/schemas";
@@ -80,7 +81,7 @@ const Content = ({ role, onClose }: ContentProps) => {
});
navigate({
to: "/projects/$projectId/roles/$roleSlug",
to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug` as const,
params: {
roleSlug: newRole.slug,
projectId: currentWorkspace.id
@@ -124,7 +124,7 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
};
return (
<div className="overflow-clip border border-mineshaft-600 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md">
<div className="overflow-clip border border-mineshaft-600 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md hover:bg-mineshaft-700">
<div
className="flex h-14 cursor-pointer items-center px-5 py-4 text-sm text-gray-300"
role="button"
@@ -136,9 +136,9 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
}
}}
>
<FontAwesomeIcon className="mr-8" icon={isOpen ? faChevronDown : faChevronRight} />
<FontAwesomeIcon className="mr-6 w-4" icon={isOpen ? faChevronDown : faChevronRight} />
<div className="flex-grow text-base">{title}</div>
<div className="flex-grow select-none text-base">{title}</div>
{fields.length > 1 && (
<div>
<Tag size="xs" className="mr-2 px-2">
@@ -19,26 +19,31 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/workspace/types";
import {
EXCLUDED_PERMISSION_SUBS,
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
ProjectTypePermissionSubjects,
TFormSchema
} from "./ProjectRoleModifySection.utils";
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
type: ProjectType;
};
type ContentProps = {
onClose: () => void;
type: ProjectType;
};
type TForm = { permissions: Record<ProjectPermissionSub, boolean> };
const Content = ({ onClose }: ContentProps) => {
const Content = ({ onClose, type: projectType }: ContentProps) => {
const rootForm = useFormContext<TFormSchema>();
const [search, setSearch] = useState("");
const {
@@ -56,7 +61,12 @@ const Content = ({ onClose }: ContentProps) => {
});
const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT)
.filter(([, { title }]) => (search ? title.toLowerCase().includes(search.toLowerCase()) : true))
.filter(
([subject, { title }]) =>
ProjectTypePermissionSubjects[projectType ?? ProjectType.SecretManager][
subject as ProjectPermissionSub
] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true)
)
.filter(([subject]) => !EXCLUDED_PERMISSION_SUBS.includes(subject as ProjectPermissionSub))
.sort((a, b) => a[1].title.localeCompare(b[1].title))
.map(([subject]) => subject);
@@ -191,7 +201,7 @@ const Content = ({ onClose }: ContentProps) => {
);
};
export const PolicySelectionModal = ({ isOpen, onOpenChange }: Props) => {
export const PolicySelectionModal = ({ isOpen, onOpenChange, type }: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
@@ -199,7 +209,7 @@ export const PolicySelectionModal = ({ isOpen, onOpenChange }: Props) => {
subTitle="Select one or more policies to add to this role."
className="max-w-3xl"
>
<Content onClose={() => onOpenChange(false)} />
<Content onClose={() => onOpenChange(false)} type={type} />
</ModalContent>
</Modal>
);
@@ -13,6 +13,7 @@ import {
ModalContent
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/workspace/types";
import {
PROJECT_PERMISSION_OBJECT,
@@ -24,19 +25,23 @@ import {
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
type: ProjectType;
};
type ContentProps = {
onClose: () => void;
type: ProjectType;
};
const Content = ({ onClose }: ContentProps) => {
const Content = ({ onClose, type: projectType }: ContentProps) => {
const rootForm = useFormContext<TFormSchema>();
const [selectedTemplate, setSelectedTemplate] = useState<RoleTemplate>();
const [conflictingSubjects, setConflictingSubjects] = useState<ProjectPermissionSub[]>([]);
const [showConflictingSubjects, setShowConflictingSubjects] = useState(false);
const templates = RoleTemplates[projectType ?? ProjectType.SecretManager];
const onSubmit = (skipConflicting = false) => {
if (!selectedTemplate) {
createNotification({ type: "error", text: "Please select a template" });
@@ -121,12 +126,12 @@ const Content = ({ onClose }: ContentProps) => {
type="single"
value={selectedTemplate?.id}
onValueChange={(value) =>
setSelectedTemplate(RoleTemplates.find((template) => template.id === value))
setSelectedTemplate(templates.find((template) => template.id === value))
}
collapsible
className="w-full border-collapse"
>
{RoleTemplates.map(({ name, description, permissions, id }) => (
{templates.map(({ name, description, permissions, id }) => (
<AccordionItem
key={id}
value={id}
@@ -182,7 +187,7 @@ const Content = ({ onClose }: ContentProps) => {
);
};
export const PolicyTemplateModal = ({ isOpen, onOpenChange }: Props) => {
export const PolicyTemplateModal = ({ isOpen, onOpenChange, type }: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
@@ -190,7 +195,7 @@ export const PolicyTemplateModal = ({ isOpen, onOpenChange }: Props) => {
subTitle="Select a template with prepopulated policies to get started. You can always add more policies later."
className="max-w-3xl"
>
<Content onClose={() => onOpenChange(false)} />
<Content onClose={() => onOpenChange(false)} type={type} />
</ModalContent>
</Modal>
);
@@ -1645,41 +1645,378 @@ export type RoleTemplate = {
permissions: { subject: ProjectPermissionSub; actions: string[] }[];
};
export const RoleTemplates = [
{
id: "project-manager",
name: "Project Management Policies",
description: "Grants access to manage project members and settings",
permissions: [
const projectManagerTemplate = (
additionalPermissions: RoleTemplate["permissions"] = []
): RoleTemplate => ({
id: "project-manager",
name: "Project Management Policies",
description: "Grants access to manage project members and settings",
permissions: [
{
subject: ProjectPermissionSub.AuditLogs,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.Groups,
actions: Object.values(ProjectPermissionGroupActions)
},
{
subject: ProjectPermissionSub.Member,
actions: Object.values(ProjectPermissionMemberActions)
},
{
subject: ProjectPermissionSub.Identity,
actions: Object.values(ProjectPermissionIdentityActions)
},
{
subject: ProjectPermissionSub.Project,
actions: [ProjectPermissionActions.Edit, ProjectPermissionActions.Delete]
},
{ subject: ProjectPermissionSub.Role, actions: Object.values(ProjectPermissionActions) },
{
subject: ProjectPermissionSub.Settings,
actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Edit]
},
...additionalPermissions
]
});
export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
[ProjectType.SSH]: [
{
id: "ssh-viewer",
name: "SSH Viewing Policies",
description: "Grants read access to SSH certificates and hosts",
permissions: [
{
subject: ProjectPermissionSub.SshCertificateAuthorities,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SshCertificates,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SshCertificateTemplates,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SshHosts,
actions: [ProjectPermissionSshHostActions.Read]
},
{
subject: ProjectPermissionSub.SshHostGroups,
actions: [ProjectPermissionActions.Read]
}
]
},
{
id: "ssh-cert-editor",
name: "SSH Certificate Editing Policies",
description: "Grants read and edit access to SSH certificates",
permissions: [
{
subject: ProjectPermissionSub.SshCertificateAuthorities,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SshCertificates,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SshCertificateTemplates,
actions: Object.values(ProjectPermissionActions)
}
]
},
{
id: "ssh-host-editor",
name: "SSH Host Editing Policies",
description: "Grants read and edit access to SSH hosts",
permissions: [
{
subject: ProjectPermissionSub.SshHosts,
actions: Object.values(ProjectPermissionSshHostActions)
},
{
subject: ProjectPermissionSub.SshHostGroups,
actions: Object.values(ProjectPermissionActions)
}
]
},
projectManagerTemplate()
],
[ProjectType.KMS]: [
{
id: "kms-viewer",
name: "KMS Viewing Policies",
description: "Grants read access to KMS keys and KMIP clients",
permissions: [
{
subject: ProjectPermissionSub.Cmek,
actions: [ProjectPermissionCmekActions.Read]
},
{
subject: ProjectPermissionSub.Kmip,
actions: [ProjectPermissionKmipActions.ReadClients]
}
]
},
{
id: "key-editor",
name: "KMS Key Editing Policies",
description: "Grants read and edit access to KMS keys",
permissions: [
{
subject: ProjectPermissionSub.Cmek,
actions: Object.values(ProjectPermissionCmekActions)
}
]
},
{
id: "kmip-editor",
name: "KMIP Client Editing Policies",
description: "Grants read and edit access to KMIP clients",
permissions: [
{
subject: ProjectPermissionSub.Kmip,
actions: Object.values(ProjectPermissionKmipActions)
}
]
},
projectManagerTemplate()
],
[ProjectType.CertificateManager]: [
{
id: "cert-viewer",
name: "Certificate Viewing Policies",
description: "Grants read access to certificates and related resources",
permissions: [
{
subject: ProjectPermissionSub.PkiCollections,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.PkiAlerts,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.CertificateAuthorities,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.CertificateTemplates,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.Certificates,
actions: [
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.ReadPrivateKey
]
}
]
},
{
id: "cert-editor",
name: "Certificate Editing Policies",
description: "Grants read and edit access to certificates and related resources",
permissions: [
{
subject: ProjectPermissionSub.PkiCollections,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.PkiAlerts,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.CertificateAuthorities,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.CertificateTemplates,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.Certificates,
actions: Object.values(ProjectPermissionCertificateActions)
}
]
},
projectManagerTemplate()
],
[ProjectType.SecretScanning]: [
{
id: "scanning-viewer",
name: "Secret Scanning Viewing Policies",
description: "Grants read access to data sources and findings",
permissions: [
{
subject: ProjectPermissionSub.SecretScanningDataSources,
actions: [
ProjectPermissionSecretScanningDataSourceActions.Read,
ProjectPermissionSecretScanningDataSourceActions.ReadResources,
ProjectPermissionSecretScanningDataSourceActions.ReadScans
]
},
{
subject: ProjectPermissionSub.SecretScanningFindings,
actions: [ProjectPermissionSecretScanningFindingActions.Read]
},
{
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: [ProjectPermissionSecretScanningConfigActions.Read]
}
]
},
{
id: "scanning-editor",
name: "Secret Scanning Editing Policies",
description: "Grants read and edit access to data sources and findings",
permissions: [
{
subject: ProjectPermissionSub.SecretScanningDataSources,
actions: Object.values(ProjectPermissionSecretScanningDataSourceActions)
},
{
subject: ProjectPermissionSub.SecretScanningFindings,
actions: Object.values(ProjectPermissionSecretScanningFindingActions)
},
{
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: [ProjectPermissionSecretScanningConfigActions.Read]
}
]
},
projectManagerTemplate([
{
subject: ProjectPermissionSub.AuditLogs,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.Groups,
actions: Object.values(ProjectPermissionGroupActions)
},
{
subject: ProjectPermissionSub.Member,
actions: Object.values(ProjectPermissionMemberActions)
},
{
subject: ProjectPermissionSub.Identity,
actions: Object.values(ProjectPermissionIdentityActions)
},
{
subject: ProjectPermissionSub.Project,
actions: [ProjectPermissionActions.Edit, ProjectPermissionActions.Delete]
},
{ subject: ProjectPermissionSub.Role, actions: Object.values(ProjectPermissionActions) },
{
subject: ProjectPermissionSub.Settings,
actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Edit]
},
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: Object.values(ProjectPermissionSecretScanningConfigActions)
}
])
],
[ProjectType.SecretManager]: [
{
id: "secret-viewer",
name: "Secret Viewing Policies",
description: "Grants read access to secrets and related resources",
permissions: [
{
subject: ProjectPermissionSub.SecretRollback,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SecretImports,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.Secrets,
actions: [
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue
]
},
{
subject: ProjectPermissionSub.DynamicSecrets,
actions: [ProjectPermissionDynamicSecretActions.ReadRootCredential]
},
{
subject: ProjectPermissionSub.Environments,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.Tags,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SecretRotation,
actions: [ProjectPermissionSecretRotationActions.Read]
},
{
subject: ProjectPermissionSub.Integrations,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SecretSyncs,
actions: [ProjectPermissionSecretSyncActions.Read]
},
{
subject: ProjectPermissionSub.Commits,
actions: [ProjectPermissionCommitsActions.Read]
}
]
},
{
id: "secret-editor",
name: "Secret Editing Policies",
description: "Grants read and edit access to secrets and related resources",
permissions: [
{
subject: ProjectPermissionSub.Environments,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.DynamicSecrets,
actions: Object.values(ProjectPermissionDynamicSecretActions)
},
{
subject: ProjectPermissionSub.Secrets,
actions: [
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Delete
]
},
{
subject: ProjectPermissionSub.SecretRollback,
actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Create]
},
{
subject: ProjectPermissionSub.Tags,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SecretImports,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SecretRotation,
actions: Object.values(ProjectPermissionSecretRotationActions)
},
{
subject: ProjectPermissionSub.SecretFolders,
actions: [
ProjectPermissionActions.Create,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Delete
]
},
{
subject: ProjectPermissionSub.Integrations,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SecretSyncs,
actions: Object.values(ProjectPermissionSecretSyncActions)
},
{
subject: ProjectPermissionSub.Commits,
actions: Object.values(ProjectPermissionCommitsActions)
}
]
},
projectManagerTemplate([
{
subject: ProjectPermissionSub.IpAllowList,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.Kms,
actions: [ProjectPermissionActions.Edit]
},
{
subject: ProjectPermissionSub.SecretApproval,
actions: Object.values(ProjectPermissionActions)
@@ -1692,314 +2029,6 @@ export const RoleTemplates = [
subject: ProjectPermissionSub.Webhooks,
actions: Object.values(ProjectPermissionActions)
}
]
},
{
id: "ssh-viewer",
name: "SSH Viewing Policies",
description: "Grants read access to SSH certificates and hosts",
permissions: [
{
subject: ProjectPermissionSub.SshCertificateAuthorities,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SshCertificates,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SshCertificateTemplates,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SshHosts,
actions: [ProjectPermissionSshHostActions.Read]
},
{
subject: ProjectPermissionSub.SshHostGroups,
actions: [ProjectPermissionActions.Read]
}
]
},
{
id: "ssh-cert-editor",
name: "SSH Certificate Editing Policies",
description: "Grants read and edit access to SSH certificates",
permissions: [
{
subject: ProjectPermissionSub.SshCertificateAuthorities,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SshCertificates,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SshCertificateTemplates,
actions: Object.values(ProjectPermissionActions)
}
]
},
{
id: "ssh-host-editor",
name: "SSH Host Editing Policies",
description: "Grants read and edit access to SSH hosts",
permissions: [
{
subject: ProjectPermissionSub.SshHosts,
actions: Object.values(ProjectPermissionSshHostActions)
},
{
subject: ProjectPermissionSub.SshHostGroups,
actions: Object.values(ProjectPermissionActions)
}
]
},
{
id: "kms-viewer",
name: "KMS Viewing Policies",
description: "Grants read access to KMS keys and KMIP clients",
permissions: [
{
subject: ProjectPermissionSub.Cmek,
actions: [ProjectPermissionCmekActions.Read]
},
{
subject: ProjectPermissionSub.Kmip,
actions: [ProjectPermissionKmipActions.ReadClients]
}
]
},
{
id: "key-editor",
name: "KMS Key Editing Policies",
description: "Grants read and edit access to KMS keys",
permissions: [
{
subject: ProjectPermissionSub.Cmek,
actions: Object.values(ProjectPermissionCmekActions)
}
]
},
{
id: "kmip-editor",
name: "KMIP Client Editing Policies",
description: "Grants read and edit access to KMIP clients",
permissions: [
{
subject: ProjectPermissionSub.Kmip,
actions: Object.values(ProjectPermissionKmipActions)
}
]
},
{
id: "cert-viewer",
name: "Certificate Viewing Policies",
description: "Grants read access to certificates and related resources",
permissions: [
{
subject: ProjectPermissionSub.PkiCollections,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.PkiAlerts,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.CertificateAuthorities,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.CertificateTemplates,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.Certificates,
actions: [
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.ReadPrivateKey
]
}
]
},
{
id: "cert-editor",
name: "Certificate Editing Policies",
description: "Grants read and edit access to certificates and related resources",
permissions: [
{
subject: ProjectPermissionSub.PkiCollections,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.PkiAlerts,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.CertificateAuthorities,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.CertificateTemplates,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.Certificates,
actions: Object.values(ProjectPermissionCertificateActions)
}
]
},
{
id: "scanning-viewer",
name: "Secret Scanning Viewing Policies",
description: "Grants read access to data sources and findings",
permissions: [
{
subject: ProjectPermissionSub.SecretScanningDataSources,
actions: [
ProjectPermissionSecretScanningDataSourceActions.Read,
ProjectPermissionSecretScanningDataSourceActions.ReadResources,
ProjectPermissionSecretScanningDataSourceActions.ReadScans
]
},
{
subject: ProjectPermissionSub.SecretScanningFindings,
actions: [ProjectPermissionSecretScanningFindingActions.Read]
},
{
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: [ProjectPermissionSecretScanningConfigActions.Read]
}
]
},
{
id: "scanning-editor",
name: "Secret Scanning Editing Policies",
description: "Grants read and edit access to data sources and findings",
permissions: [
{
subject: ProjectPermissionSub.SecretScanningDataSources,
actions: Object.values(ProjectPermissionSecretScanningDataSourceActions)
},
{
subject: ProjectPermissionSub.SecretScanningFindings,
actions: Object.values(ProjectPermissionSecretScanningFindingActions)
},
{
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: [ProjectPermissionSecretScanningConfigActions.Read]
}
]
},
{
id: "secret-viewer",
name: "Secret Viewing Policies",
description: "Grants read access to secrets and related resources",
permissions: [
{
subject: ProjectPermissionSub.SecretRollback,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SecretImports,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.Secrets,
actions: [
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue
]
},
{
subject: ProjectPermissionSub.DynamicSecrets,
actions: [ProjectPermissionDynamicSecretActions.ReadRootCredential]
},
{
subject: ProjectPermissionSub.Environments,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.Tags,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SecretRotation,
actions: [ProjectPermissionSecretRotationActions.Read]
},
{
subject: ProjectPermissionSub.Integrations,
actions: [ProjectPermissionActions.Read]
},
{
subject: ProjectPermissionSub.SecretSyncs,
actions: [ProjectPermissionSecretSyncActions.Read]
},
{
subject: ProjectPermissionSub.Commits,
actions: [ProjectPermissionCommitsActions.Read]
}
]
},
{
id: "secret-editor",
name: "Secret Editing Policies",
description: "Grants read and edit access to secrets and related resources",
permissions: [
{
subject: ProjectPermissionSub.Environments,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.DynamicSecrets,
actions: Object.values(ProjectPermissionDynamicSecretActions)
},
{
subject: ProjectPermissionSub.Secrets,
actions: [
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Delete
]
},
{
subject: ProjectPermissionSub.SecretRollback,
actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Create]
},
{
subject: ProjectPermissionSub.Tags,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SecretImports,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SecretRotation,
actions: Object.values(ProjectPermissionSecretRotationActions)
},
{
subject: ProjectPermissionSub.SecretFolders,
actions: [
ProjectPermissionActions.Create,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Delete
]
},
{
subject: ProjectPermissionSub.Integrations,
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.SecretSyncs,
actions: Object.values(ProjectPermissionSecretSyncActions)
},
{
subject: ProjectPermissionSub.Commits,
actions: Object.values(ProjectPermissionCommitsActions)
}
]
}
];
])
]
};
@@ -7,6 +7,7 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import {
useCreateProjectRole,
useGetProjectRoleBySlug,
@@ -100,7 +101,7 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
});
navigate({
to: "/projects/$projectId/roles/$roleSlug",
to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug` as const,
params: {
roleSlug: newRole.slug,
projectId
@@ -4,7 +4,6 @@ import { MongoAbility, MongoQuery, RawRuleOf } from "@casl/ability";
import { faSave } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { AccessTree } from "@app/components/permissions";
@@ -14,6 +13,7 @@ import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
import { evaluatePermissionsAbility } from "@app/helpers/permissions";
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { AddPoliciesButton } from "./AddPoliciesButton";
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
@@ -121,6 +121,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
(role?.slug ?? "") as ProjectMembershipRole
);
const isSecretManagerProject = currentWorkspace.type === ProjectType.SecretManager;
const permissions = form.watch("permissions");
const formattedPermissions = useMemo(
@@ -163,7 +165,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
<Button
colorSchema="secondary"
type="submit"
className={twMerge("h-10 border")}
className="h-10 border"
isDisabled={isSubmitting || !isDirty}
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={faSave} />}
@@ -171,7 +173,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
Save
</Button>
<div className="ml-2 border-l border-mineshaft-500 pl-4">
<AddPoliciesButton isDisabled={isDisabled} />
<AddPoliciesButton isDisabled={isDisabled} projectType={currentWorkspace.type} />
</div>
</div>
)}
@@ -206,7 +208,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
</div>
</FormProvider>
</form>
{showAccessTree && (
{isSecretManagerProject && showAccessTree && (
<AccessTree
permissions={formattedPermissions}
subject={showAccessTree}
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/roles/$roleSlug"
)({
component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/cert-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Roles
}
})
},
{
label: "Roles"
}
]
};
}
});
@@ -5,7 +5,7 @@ import { ProjectAccessControlTabs } from "@app/types/project";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/_project-general-layout/roles/$roleSlug"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/roles/$roleSlug"
)({
component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Access Control",
link: linkOptions({
to: "/projects/$projectId/access-management",
to: "/projects/kms/$projectId/access-management",
params: {
projectId: params.projectId
},
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/roles/$roleSlug"
)({
component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-management/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Roles
}
})
},
{
label: "Roles"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/roles/$roleSlug"
)({
component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Roles
}
})
},
{
label: "Roles"
}
]
};
}
});
@@ -0,0 +1,33 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { ProjectAccessControlTabs } from "@app/types/project";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/roles/$roleSlug"
)({
component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/projects/ssh/$projectId/access-management",
params: {
projectId: params.projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Roles
}
})
},
{
label: "Roles"
}
]
};
}
});
@@ -1,40 +0,0 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
const tabs = [{ name: "General", key: "tab-project-general", Component: ProjectGeneralTab }];
export const SettingsPage = () => {
const { t } = useTranslation();
return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
</Helmet>
<div className="w-full max-w-7xl">
<PageHeader
title={t("settings.project.title")}
description="Configure your project details and project-specific settings"
/>
<Tabs defaultValue={tabs[0].key}>
<TabList>
{tabs.map((tab) => (
<Tab value={tab.key} key={tab.key}>
{tab.name}
</Tab>
))}
</TabList>
{tabs.map(({ key, Component }) => (
<TabPanel value={key} key={key}>
<Component />
</TabPanel>
))}
</Tabs>
</div>
</div>
);
};
@@ -1,19 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/_project-general-layout/settings"
)({
component: SettingsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Settings"
}
]
};
}
});
@@ -1,9 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
import { ProjectGeneralLayout } from "@app/layouts/ProjectGeneralLayout";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/_project-general-layout"
)({
component: ProjectGeneralLayout
});
-37
View File
@@ -1,37 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
import { BreadcrumbTypes } from "@app/components/v2";
import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { ProjectLayout } from "@app/layouts/ProjectLayout";
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout"
)({
component: ProjectLayout,
beforeLoad: async ({ params, context }) => {
const project = await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
queryFn: () => fetchWorkspaceById(params.projectId)
});
await context.queryClient.ensureQueryData({
queryKey: roleQueryKeys.getUserProjectPermissions({
workspaceId: params.projectId
}),
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
});
return {
project,
breadcrumbs: [
{
type: BreadcrumbTypes.Component,
component: ProjectSelect
}
]
};
}
});