From 5181cac9c86caf55ef8335a93f1530e48a11a86d Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 29 May 2025 17:29:06 -0400
Subject: [PATCH 1/4] add manage permission for billing
---
.../ee/services/license/license-service.ts | 34 ++--
.../ee/services/permission/org-permission.ts | 15 +-
.../permissions/organization-permissions.mdx | 10 +-
.../src/context/OrgPermissionContext/types.ts | 7 +-
.../components/OrgRoleModifySection.utils.ts | 10 +-
.../OrgPermissionBillingRow.tsx | 174 ++++++++++++++++++
.../RolePermissionRow.tsx | 81 ++++----
.../RolePermissionsSection.tsx | 10 +-
8 files changed, 261 insertions(+), 80 deletions(-)
create mode 100644 frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionBillingRow.tsx
diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts
index cfc42d038..813b88297 100644
--- a/backend/src/ee/services/license/license-service.ts
+++ b/backend/src/ee/services/license/license-service.ts
@@ -17,7 +17,7 @@ import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal"
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
-import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
+import { OrgPermissionBillingActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
import { TLicenseDALFactory } from "./license-dal";
@@ -288,7 +288,7 @@ export const licenseServiceFactory = ({
billingCycle
}: TOrgPlansTableDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
@@ -310,8 +310,7 @@ export const licenseServiceFactory = ({
success_url
}: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -338,8 +337,7 @@ export const licenseServiceFactory = ({
actorOrgId
}: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -385,7 +383,7 @@ export const licenseServiceFactory = ({
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -413,7 +411,7 @@ export const licenseServiceFactory = ({
// returns org current plan feature table
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -484,7 +482,7 @@ export const licenseServiceFactory = ({
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -509,7 +507,7 @@ export const licenseServiceFactory = ({
email
}: TUpdateOrgBillingDetailsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -529,7 +527,7 @@ export const licenseServiceFactory = ({
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -556,7 +554,7 @@ export const licenseServiceFactory = ({
cancel_url
}: TAddOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -585,7 +583,7 @@ export const licenseServiceFactory = ({
pmtMethodId
}: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -602,7 +600,7 @@ export const licenseServiceFactory = ({
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -620,7 +618,7 @@ export const licenseServiceFactory = ({
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -641,7 +639,7 @@ export const licenseServiceFactory = ({
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -658,7 +656,7 @@ export const licenseServiceFactory = ({
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -675,7 +673,7 @@ export const licenseServiceFactory = ({
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts
index 612914bcc..f0fe73d71 100644
--- a/backend/src/ee/services/permission/org-permission.ts
+++ b/backend/src/ee/services/permission/org-permission.ts
@@ -67,6 +67,11 @@ export enum OrgPermissionGroupActions {
RemoveMembers = "remove-members"
}
+export enum OrgPermissionBillingActions {
+ Read = "read",
+ ManageBilling = "manage-billing"
+}
+
export enum OrgPermissionSubjects {
Workspace = "workspace",
Role = "role",
@@ -107,7 +112,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
- | [OrgPermissionActions, OrgPermissionSubjects.Billing]
+ | [OrgPermissionBillingActions, OrgPermissionSubjects.Billing]
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
@@ -298,10 +303,8 @@ const buildAdminPermission = () => {
can(OrgPermissionGroupActions.AddMembers, OrgPermissionSubjects.Groups);
can(OrgPermissionGroupActions.RemoveMembers, OrgPermissionSubjects.Groups);
- can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
- can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
- can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
- can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
+ can(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
+ can(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
can(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
can(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
@@ -362,7 +365,7 @@ const buildMemberPermission = () => {
can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
- can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
+ can(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
diff --git a/docs/internals/permissions/organization-permissions.mdx b/docs/internals/permissions/organization-permissions.mdx
index 6de3bd6fe..80c843851 100644
--- a/docs/internals/permissions/organization-permissions.mdx
+++ b/docs/internals/permissions/organization-permissions.mdx
@@ -142,12 +142,10 @@ Below is a comprehensive list of all available organization-level subjects and t
#### Subject: `billing`
-| Action | Description |
-| -------- | ------------------------------------------------ |
-| `read` | View billing information and subscription status |
-| `create` | Set up new payment methods or subscriptions |
-| `edit` | Modify billing details or subscription plans |
-| `delete` | Remove payment methods or cancel subscriptions |
+| Action | Description |
+| ---------------- | ------------------------------------------------ |
+| `read` | View billing information and subscription status |
+| `manage-billing` | Manage billing details and subscription plans |
### Templates & Automation
diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts
index a4bd202bf..59446eb07 100644
--- a/frontend/src/context/OrgPermissionContext/types.ts
+++ b/frontend/src/context/OrgPermissionContext/types.ts
@@ -7,6 +7,11 @@ export enum OrgPermissionActions {
Delete = "delete"
}
+export enum OrgPermissionBillingActions {
+ Read = "read",
+ ManageBilling = "manage-billing"
+}
+
export enum OrgGatewayPermissionActions {
// is there a better word for this. This mean can an identity be a gateway
CreateGateways = "create-gateways",
@@ -100,7 +105,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
- | [OrgPermissionActions, OrgPermissionSubjects.Billing]
+ | [OrgPermissionBillingActions, OrgPermissionSubjects.Billing]
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
diff --git a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts
index b0336c465..8a0f1610e 100644
--- a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts
+++ b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts
@@ -5,6 +5,7 @@ import { OrgPermissionSubjects } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionAppConnectionActions,
+ OrgPermissionBillingActions,
OrgPermissionGroupActions,
OrgPermissionIdentityActions,
OrgPermissionKmipActions,
@@ -21,6 +22,13 @@ const generalPermissionSchema = z
})
.optional();
+const billingPermissionSchema = z
+ .object({
+ [OrgPermissionBillingActions.Read]: z.boolean().optional(),
+ [OrgPermissionBillingActions.ManageBilling]: z.boolean().optional()
+ })
+ .optional();
+
const appConnectionsPermissionSchema = z
.object({
[OrgPermissionAppConnectionActions.Read]: z.boolean().optional(),
@@ -113,7 +121,7 @@ export const formSchema = z.object({
scim: generalPermissionSchema,
[OrgPermissionSubjects.GithubOrgSync]: generalPermissionSchema,
ldap: generalPermissionSchema,
- billing: generalPermissionSchema,
+ billing: billingPermissionSchema,
identity: identityPermissionSchema,
"organization-admin-console": adminConsolePermissionSchmea,
[OrgPermissionSubjects.Kms]: generalPermissionSchema,
diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionBillingRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionBillingRow.tsx
new file mode 100644
index 000000000..c99ea2f0e
--- /dev/null
+++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionBillingRow.tsx
@@ -0,0 +1,174 @@
+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 { OrgPermissionBillingActions } from "@app/context/OrgPermissionContext/types";
+import { useToggle } from "@app/hooks";
+
+import { TFormSchema } from "../OrgRoleModifySection.utils";
+
+const PERMISSION_ACTIONS = [
+ { action: OrgPermissionBillingActions.Read, label: "View bills" },
+ { action: OrgPermissionBillingActions.ManageBilling, label: "Manage billing" }
+] as const;
+
+type Props = {
+ isEditable: boolean;
+ setValue: UseFormSetValue;
+ control: Control;
+};
+
+enum Permission {
+ NoAccess = "no-access",
+ ReadOnly = "read-only",
+ FullAccess = "full-access",
+ Custom = "custom"
+}
+
+export const OrgPermissionBillingRow = ({ isEditable, control, setValue }: Props) => {
+ const [isRowExpanded, setIsRowExpanded] = useToggle();
+ const [isCustom, setIsCustom] = useToggle();
+
+ const rule = useWatch({
+ control,
+ name: "permissions.billing"
+ });
+
+ const selectedPermissionCategory = useMemo(() => {
+ const actions = Object.keys(rule || {}) as Array;
+ const totalActions = PERMISSION_ACTIONS.length;
+ const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
+
+ if (isCustom) return Permission.Custom;
+ if (score === 0) return Permission.NoAccess;
+ if (score === totalActions) return Permission.FullAccess;
+ if (score === 1 && rule?.[OrgPermissionBillingActions.Read]) return Permission.ReadOnly;
+ return Permission.Custom;
+ }, [rule, isCustom]);
+
+ useEffect(() => {
+ if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
+ else setIsCustom.off();
+ }, [selectedPermissionCategory]);
+
+ const handlePermissionChange = (val: Permission) => {
+ if (val === Permission.Custom) {
+ setIsRowExpanded.on();
+ setIsCustom.on();
+ return;
+ }
+ setIsCustom.off();
+
+ switch (val) {
+ case Permission.NoAccess:
+ setValue(
+ "permissions.billing",
+ {
+ [OrgPermissionBillingActions.Read]: false,
+ [OrgPermissionBillingActions.ManageBilling]: false
+ },
+ { shouldDirty: true }
+ );
+ break;
+ case Permission.ReadOnly:
+ setValue(
+ "permissions.billing",
+ {
+ [OrgPermissionBillingActions.Read]: true,
+ [OrgPermissionBillingActions.ManageBilling]: false
+ },
+ { shouldDirty: true }
+ );
+ break;
+ case Permission.FullAccess:
+ setValue(
+ "permissions.billing",
+ {
+ [OrgPermissionBillingActions.Read]: true,
+ [OrgPermissionBillingActions.ManageBilling]: true
+ },
+ { shouldDirty: true }
+ );
+ break;
+ default:
+ setValue(
+ "permissions.billing",
+ {
+ [OrgPermissionBillingActions.Read]: false,
+ [OrgPermissionBillingActions.ManageBilling]: false
+ },
+ { shouldDirty: true }
+ );
+ break;
+ }
+ };
+
+ return (
+ <>
+ setIsRowExpanded.toggle()}
+ >
+ |
+
+ |
+ Billing |
+
+
+ |
+
+ {isRowExpanded && (
+
+ |
+
+ {PERMISSION_ACTIONS.map(({ action, label }) => {
+ return (
+ (
+ {
+ if (!isEditable) {
+ createNotification({
+ type: "error",
+ text: "Failed to update default role"
+ });
+ return;
+ }
+ field.onChange(e);
+ }}
+ id={`permissions.billing.${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 0bc765eb0..f7cd67bba 100644
--- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx
+++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx
@@ -38,12 +38,7 @@ const MEMBERS_PERMISSIONS = [
{ action: "delete", label: "Remove members" }
] as const;
-const BILLING_PERMISSIONS = [
- { action: "read", label: "View bills" },
- { action: "create", label: "Add payment methods" },
- { action: "edit", label: "Edit payments" },
- { action: "delete", label: "Remove payments" }
-] as const;
+
const PROJECT_TEMPLATES_PERMISSIONS = [
{ action: "read", label: "View & Apply" },
@@ -52,18 +47,16 @@ const PROJECT_TEMPLATES_PERMISSIONS = [
{ action: "delete", label: "Remove" }
] as const;
-const getPermissionList = (option: string) => {
- switch (option) {
- case "secret-scanning":
- return SECRET_SCANNING_PERMISSIONS;
- case "billing":
- return BILLING_PERMISSIONS;
- case "incident-contact":
- return INCIDENT_CONTACTS_PERMISSIONS;
+const getPermissionList = (formName: Props["formName"]) => {
+ switch (formName) {
case "member":
return MEMBERS_PERMISSIONS;
case OrgPermissionSubjects.ProjectTemplates:
return PROJECT_TEMPLATES_PERMISSIONS;
+ case "secret-scanning":
+ return SECRET_SCANNING_PERMISSIONS;
+ case "incident-contact":
+ return INCIDENT_CONTACTS_PERMISSIONS;
default:
return PERMISSIONS;
}
@@ -74,7 +67,7 @@ type Props = {
title: string;
formName: keyof Omit<
Exclude,
- "workspace" | "organization-admin-console" | "kmip" | "gateway" | "secret-share"
+ "workspace" | "organization-admin-console" | "kmip" | "gateway" | "secret-share" | "billing"
>;
setValue: UseFormSetValue;
control: Control;
@@ -129,35 +122,35 @@ export const RolePermissionRow = ({ isEditable, title, formName, control, setVal
}
setIsCustom.off();
- switch (val) {
- case Permission.NoAccess:
- setValue(
- `permissions.${formName}`,
- { read: false, edit: false, create: false, delete: false },
- { shouldDirty: true }
- );
- break;
- case Permission.FullAccess:
- setValue(
- `permissions.${formName}`,
- { read: true, edit: true, create: true, delete: true },
- { shouldDirty: true }
- );
- break;
- case Permission.ReadOnly:
- setValue(
- `permissions.${formName}`,
- { read: true, edit: false, create: false, delete: false },
- { shouldDirty: true }
- );
- break;
- default:
- setValue(
- `permissions.${formName}`,
- { read: false, edit: false, create: false, delete: false },
- { shouldDirty: true }
- );
- break;
+ switch (val) {
+ case Permission.NoAccess:
+ setValue(
+ `permissions.${formName}`,
+ { read: false, edit: false, create: false, delete: false },
+ { shouldDirty: true }
+ );
+ break;
+ case Permission.FullAccess:
+ setValue(
+ `permissions.${formName}`,
+ { read: true, edit: true, create: true, delete: true },
+ { shouldDirty: true }
+ );
+ break;
+ case Permission.ReadOnly:
+ setValue(
+ `permissions.${formName}`,
+ { read: true, edit: false, create: false, delete: false },
+ { shouldDirty: true }
+ );
+ break;
+ default:
+ setValue(
+ `permissions.${formName}`,
+ { read: false, edit: false, create: false, delete: false },
+ { shouldDirty: true }
+ );
+ break;
}
};
diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx
index 7c9cb9850..b4b77c238 100644
--- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx
+++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx
@@ -18,6 +18,7 @@ import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow";
import { OrgPermissionGroupRow } from "./OrgPermissionGroupRow";
import { OrgPermissionIdentityRow } from "./OrgPermissionIdentityRow";
import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow";
+import { OrgPermissionBillingRow } from "./OrgPermissionBillingRow";
import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow";
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
import { RolePermissionRow } from "./RolePermissionRow";
@@ -27,10 +28,6 @@ const SIMPLE_PERMISSION_OPTIONS = [
title: "User Management",
formName: "member"
},
- {
- title: "Usage & Billing",
- formName: "billing"
- },
{
title: "Role Management",
formName: "role"
@@ -186,6 +183,11 @@ export const RolePermissionsSection = ({ roleId }: Props) => {
setValue={setValue}
isEditable={isCustomRole}
/>
+
Date: Thu, 29 May 2025 17:34:05 -0400
Subject: [PATCH 2/4] fix frontend lint
---
.../RolePermissionRow.tsx | 60 +++++++++----------
.../RolePermissionsSection.tsx | 2 +-
2 files changed, 30 insertions(+), 32 deletions(-)
diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx
index f7cd67bba..11f128b18 100644
--- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx
+++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx
@@ -38,8 +38,6 @@ const MEMBERS_PERMISSIONS = [
{ action: "delete", label: "Remove members" }
] as const;
-
-
const PROJECT_TEMPLATES_PERMISSIONS = [
{ action: "read", label: "View & Apply" },
{ action: "create", label: "Create" },
@@ -122,35 +120,35 @@ export const RolePermissionRow = ({ isEditable, title, formName, control, setVal
}
setIsCustom.off();
- switch (val) {
- case Permission.NoAccess:
- setValue(
- `permissions.${formName}`,
- { read: false, edit: false, create: false, delete: false },
- { shouldDirty: true }
- );
- break;
- case Permission.FullAccess:
- setValue(
- `permissions.${formName}`,
- { read: true, edit: true, create: true, delete: true },
- { shouldDirty: true }
- );
- break;
- case Permission.ReadOnly:
- setValue(
- `permissions.${formName}`,
- { read: true, edit: false, create: false, delete: false },
- { shouldDirty: true }
- );
- break;
- default:
- setValue(
- `permissions.${formName}`,
- { read: false, edit: false, create: false, delete: false },
- { shouldDirty: true }
- );
- break;
+ switch (val) {
+ case Permission.NoAccess:
+ setValue(
+ `permissions.${formName}`,
+ { read: false, edit: false, create: false, delete: false },
+ { shouldDirty: true }
+ );
+ break;
+ case Permission.FullAccess:
+ setValue(
+ `permissions.${formName}`,
+ { read: true, edit: true, create: true, delete: true },
+ { shouldDirty: true }
+ );
+ break;
+ case Permission.ReadOnly:
+ setValue(
+ `permissions.${formName}`,
+ { read: true, edit: false, create: false, delete: false },
+ { shouldDirty: true }
+ );
+ break;
+ default:
+ setValue(
+ `permissions.${formName}`,
+ { read: false, edit: false, create: false, delete: false },
+ { shouldDirty: true }
+ );
+ break;
}
};
diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx
index b4b77c238..32e24bb58 100644
--- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx
+++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx
@@ -14,11 +14,11 @@ import {
TFormSchema
} from "../OrgRoleModifySection.utils";
import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow";
+import { OrgPermissionBillingRow } from "./OrgPermissionBillingRow";
import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow";
import { OrgPermissionGroupRow } from "./OrgPermissionGroupRow";
import { OrgPermissionIdentityRow } from "./OrgPermissionIdentityRow";
import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow";
-import { OrgPermissionBillingRow } from "./OrgPermissionBillingRow";
import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow";
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
import { RolePermissionRow } from "./RolePermissionRow";
From 70860e0d26f064735fbdcfe35aafd89a206667fe Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 29 May 2025 17:48:50 -0400
Subject: [PATCH 3/4] fix backend lint
---
.../ee/services/license/license-service.ts | 35 +++++++++++++++----
1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts
index 813b88297..e2cf09bb1 100644
--- a/backend/src/ee/services/license/license-service.ts
+++ b/backend/src/ee/services/license/license-service.ts
@@ -310,7 +310,10 @@ export const licenseServiceFactory = ({
success_url
}: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(
+ OrgPermissionBillingActions.ManageBilling,
+ OrgPermissionSubjects.Billing
+ );
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -337,7 +340,10 @@ export const licenseServiceFactory = ({
actorOrgId
}: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(
+ OrgPermissionBillingActions.ManageBilling,
+ OrgPermissionSubjects.Billing
+ );
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -507,7 +513,10 @@ export const licenseServiceFactory = ({
email
}: TUpdateOrgBillingDetailsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(
+ OrgPermissionBillingActions.ManageBilling,
+ OrgPermissionSubjects.Billing
+ );
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -554,7 +563,10 @@ export const licenseServiceFactory = ({
cancel_url
}: TAddOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(
+ OrgPermissionBillingActions.ManageBilling,
+ OrgPermissionSubjects.Billing
+ );
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -583,7 +595,10 @@ export const licenseServiceFactory = ({
pmtMethodId
}: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(
+ OrgPermissionBillingActions.ManageBilling,
+ OrgPermissionSubjects.Billing
+ );
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -618,7 +633,10 @@ export const licenseServiceFactory = ({
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(
+ OrgPermissionBillingActions.ManageBilling,
+ OrgPermissionSubjects.Billing
+ );
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
@@ -639,7 +657,10 @@ export const licenseServiceFactory = ({
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
- ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
+ ForbiddenError.from(permission).throwUnlessCan(
+ OrgPermissionBillingActions.ManageBilling,
+ OrgPermissionSubjects.Billing
+ );
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
From d96e8800156149f7107edfd3609c4076f383cb3d Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 29 May 2025 18:26:34 -0400
Subject: [PATCH 4/4] updates billing types else where
---
frontend/src/context/OrgPermissionContext/index.tsx | 1 +
frontend/src/context/index.tsx | 1 +
frontend/src/pages/organization/BillingPage/BillingPage.tsx | 4 ++--
.../components/BillingCloudTab/PreviewSection.tsx | 6 +++---
.../components/BillingDetailsTab/CompanyNameSection.tsx | 4 ++--
.../components/BillingDetailsTab/InvoiceEmailSection.tsx | 4 ++--
.../components/BillingDetailsTab/PmtMethodsSection.tsx | 4 ++--
.../components/BillingDetailsTab/PmtMethodsTable.tsx | 4 ++--
.../components/BillingDetailsTab/TaxIDSection.tsx | 4 ++--
.../BillingPage/components/BillingDetailsTab/TaxIDTable.tsx | 4 ++--
.../components/BillingTabGroup/BillingTabGroup.tsx | 4 ++--
11 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/frontend/src/context/OrgPermissionContext/index.tsx b/frontend/src/context/OrgPermissionContext/index.tsx
index acbbf3902..fccd53935 100644
--- a/frontend/src/context/OrgPermissionContext/index.tsx
+++ b/frontend/src/context/OrgPermissionContext/index.tsx
@@ -2,6 +2,7 @@ export { useOrgPermission } from "./OrgPermissionContext";
export type { TOrgPermission } from "./types";
export {
OrgPermissionActions,
+ OrgPermissionBillingActions,
OrgPermissionGroupActions,
OrgPermissionIdentityActions,
OrgPermissionSubjects
diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx
index 8b64ee58a..c19c990dd 100644
--- a/frontend/src/context/index.tsx
+++ b/frontend/src/context/index.tsx
@@ -2,6 +2,7 @@ export { useOrganization } from "./OrganizationContext";
export type { TOrgPermission } from "./OrgPermissionContext";
export {
OrgPermissionActions,
+ OrgPermissionBillingActions,
OrgPermissionGroupActions,
OrgPermissionIdentityActions,
OrgPermissionSubjects,
diff --git a/frontend/src/pages/organization/BillingPage/BillingPage.tsx b/frontend/src/pages/organization/BillingPage/BillingPage.tsx
index 0ffe5abbb..d0b71cc21 100644
--- a/frontend/src/pages/organization/BillingPage/BillingPage.tsx
+++ b/frontend/src/pages/organization/BillingPage/BillingPage.tsx
@@ -2,7 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { OrgPermissionCan } from "@app/components/permissions";
-import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
+import { OrgPermissionBillingActions, OrgPermissionSubjects } from "@app/context";
import { BillingTabGroup } from "./components";
@@ -24,7 +24,7 @@ export const BillingPage = () => {
diff --git a/frontend/src/pages/organization/BillingPage/components/BillingCloudTab/PreviewSection.tsx b/frontend/src/pages/organization/BillingPage/components/BillingCloudTab/PreviewSection.tsx
index 5741722bd..98309edbd 100644
--- a/frontend/src/pages/organization/BillingPage/components/BillingCloudTab/PreviewSection.tsx
+++ b/frontend/src/pages/organization/BillingPage/components/BillingCloudTab/PreviewSection.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import {
- OrgPermissionActions,
+ OrgPermissionBillingActions,
OrgPermissionSubjects,
useOrganization,
useSubscription
@@ -112,7 +112,7 @@ export const PreviewSection = () => {
Get unlimited members, projects, RBAC, smart alerts, and so much more.
-
+
{(isAllowed) => (