diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index ae7304907..232f4b0b5 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -107,7 +107,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }), name: z.string().trim().optional(), description: z.string().trim().optional(), - permissions: z.any().array() + permissions: z.any().array().optional() }), response: { 200: z.object({ diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index c0e59b2b6..5e5e330e1 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -425,6 +425,21 @@ export const PROJECTS = { }, LIST_INTEGRATION_AUTHORIZATION: { workspaceId: "The ID of the project to list integration auths for." + }, + LIST_CAS: { + slug: "The slug of the project to list CAs for.", + status: "The status of the CA to filter by.", + friendlyName: "The friendly name of the CA to filter by.", + commonName: "The common name of the CA to filter by.", + offset: "The offset to start from. If you enter 10, it will start from the 10th CA.", + limit: "The number of CAs to return." + }, + LIST_CERTIFICATES: { + slug: "The slug of the project to list certificates for.", + friendlyName: "The friendly name of the certificate to filter by.", + commonName: "The common name of the certificate to filter by.", + offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.", + limit: "The number of certificates to return." } } as const; diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index e1c7c2e69..e59291664 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -317,10 +317,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }, schema: { params: z.object({ - slug: slugSchema.describe("The slug of the project to list CAs.") + slug: slugSchema.describe(PROJECTS.LIST_CAS.slug) }), querystring: z.object({ - status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional() + status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional().describe(PROJECTS.LIST_CAS.status), + friendlyName: z.string().optional().describe(PROJECTS.LIST_CAS.friendlyName), + commonName: z.string().optional().describe(PROJECTS.LIST_CAS.commonName), + offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CAS.offset), + limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CAS.limit) }), response: { 200: z.object({ @@ -336,11 +340,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { orgId: req.permission.orgId, type: ProjectFilterType.SLUG }, - status: req.query.status, actorId: req.permission.id, actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, - actor: req.permission.type + actor: req.permission.type, + ...req.query }); return { cas }; } @@ -354,11 +358,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }, schema: { params: z.object({ - slug: slugSchema.describe("The slug of the project to list certificates.") + slug: slugSchema.describe(PROJECTS.LIST_CERTIFICATES.slug) }), querystring: z.object({ - offset: z.coerce.number().min(0).max(100).default(0), - limit: z.coerce.number().min(1).max(100).default(25) + friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName), + commonName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.commonName), + offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset), + limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit) }), response: { 200: z.object({ diff --git a/backend/src/services/certificate/certificate-dal.ts b/backend/src/services/certificate/certificate-dal.ts index 67dca3aca..71c70838c 100644 --- a/backend/src/services/certificate/certificate-dal.ts +++ b/backend/src/services/certificate/certificate-dal.ts @@ -8,19 +8,35 @@ export type TCertificateDALFactory = ReturnType; export const certificateDALFactory = (db: TDbClient) => { const certificateOrm = ormify(db, TableName.Certificate); - const countCertificatesInProject = async (projectId: string) => { + const countCertificatesInProject = async ({ + projectId, + friendlyName, + commonName + }: { + projectId: string; + friendlyName?: string; + commonName?: string; + }) => { try { interface CountResult { count: string; } - const count = await db + let query = db .replicaNode()(TableName.Certificate) .join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`) .join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`) - .where(`${TableName.Project}.id`, projectId) - .count("*") - .first(); + .where(`${TableName.Project}.id`, projectId); + + if (friendlyName) { + query = query.andWhere(`${TableName.Certificate}.friendlyName`, friendlyName); + } + + if (commonName) { + query = query.andWhere(`${TableName.Certificate}.commonName`, commonName); + } + + const count = await query.count("*").first(); return parseInt((count as unknown as CountResult).count || "0", 10); } catch (error) { diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 981f90bb6..b1a53408a 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -575,6 +575,10 @@ export const projectServiceFactory = ({ */ const listProjectCas = async ({ status, + friendlyName, + commonName, + limit = 25, + offset = 0, actorId, actorOrgId, actorAuthMethod, @@ -596,10 +600,15 @@ export const projectServiceFactory = ({ ProjectPermissionSub.CertificateAuthorities ); - const cas = await certificateAuthorityDAL.find({ - projectId: project.id, - ...(status && { status }) - }); + const cas = await certificateAuthorityDAL.find( + { + projectId: project.id, + ...(status && { status }), + ...(friendlyName && { friendlyName }), + ...(commonName && { commonName }) + }, + { offset, limit, sort: [["updatedAt", "desc"]] } + ); return cas; }; @@ -608,8 +617,10 @@ export const projectServiceFactory = ({ * Return list of certificates for project */ const listProjectCertificates = async ({ - offset, - limit, + limit = 25, + offset = 0, + friendlyName, + commonName, actorId, actorOrgId, actorAuthMethod, @@ -634,12 +645,18 @@ export const projectServiceFactory = ({ { $in: { caId: cas.map((ca) => ca.id) - } + }, + ...(friendlyName && { friendlyName }), + ...(commonName && { commonName }) }, { offset, limit, sort: [["updatedAt", "desc"]] } ); - const count = await certificateDAL.countCertificatesInProject(project.id); + const count = await certificateDAL.countCertificatesInProject({ + projectId: project.id, + friendlyName, + commonName + }); return { certificates, diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index eb08fba98..c49e51143 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -89,6 +89,10 @@ export type AddUserToWsDTO = { export type TListProjectCasDTO = { status?: CaStatus; + friendlyName?: string; + offset?: number; + limit?: number; + commonName?: string; filter: Filter; } & Omit; @@ -96,4 +100,6 @@ export type TListProjectCertsDTO = { filter: Filter; offset: number; limit: number; + friendlyName?: string; + commonName?: string; } & Omit; diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index 261a2883b..4717e0784 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -24,7 +24,6 @@ import ( "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/srp" "github.com/Infisical/infisical-merge/packages/util" - "github.com/chzyer/readline" "github.com/fatih/color" "github.com/go-resty/resty/v2" "github.com/manifoldco/promptui" @@ -205,6 +204,7 @@ var loginCmd = &cobra.Command{ if !overrideDomain { domainQuery = false config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL_MANUAL_OVERRIDE) + config.INFISICAL_LOGIN_URL = fmt.Sprintf("%s/login", strings.TrimSuffix(config.INFISICAL_URL, "/api")) } } @@ -713,7 +713,7 @@ func askForMFACode() string { return mfaVerifyCode } -func askToPasteJwtToken(stdin *readline.CancelableStdin, success chan models.UserCredentials, failure chan error) { +func askToPasteJwtToken(success chan models.UserCredentials, failure chan error) { time.Sleep(time.Second * 5) fmt.Println("\n\nOnce login is completed via browser, the CLI should be authenticated automatically.") fmt.Println("However, if browser fails to communicate with the CLI, please paste the token from the browser below.") @@ -807,26 +807,22 @@ func browserCliLogin() (models.UserCredentials, error) { log.Debug().Msgf("Callback server listening on port %d", callbackPort) - stdin := readline.NewCancelableStdin(os.Stdin) go http.Serve(listener, corsHandler) - go askToPasteJwtToken(stdin, success, failure) + go askToPasteJwtToken(success, failure) for { select { case loginResponse := <-success: _ = closeListener(&listener) - _ = stdin.Close() fmt.Println("Browser login successful") return loginResponse, nil case err := <-failure: serverErr := closeListener(&listener) - stdErr := stdin.Close() - return models.UserCredentials{}, errors.Join(err, serverErr, stdErr) + return models.UserCredentials{}, errors.Join(err, serverErr) case <-timeout: _ = closeListener(&listener) - _ = stdin.Close() return models.UserCredentials{}, errors.New("server timeout") } } diff --git a/docs/api-reference/endpoints/certificate-authorities/list.mdx b/docs/api-reference/endpoints/certificate-authorities/list.mdx new file mode 100644 index 000000000..ba4a43348 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authorities/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v2/workspace/{slug}/cas" +--- diff --git a/docs/api-reference/endpoints/certificates/list.mdx b/docs/api-reference/endpoints/certificates/list.mdx new file mode 100644 index 000000000..67a4623a4 --- /dev/null +++ b/docs/api-reference/endpoints/certificates/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v2/workspace/{slug}/certificates" +--- diff --git a/docs/mint.json b/docs/mint.json index 2898d949b..6628316f8 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -654,6 +654,7 @@ { "group": "Certificate Authorities", "pages": [ + "api-reference/endpoints/certificate-authorities/list", "api-reference/endpoints/certificate-authorities/create", "api-reference/endpoints/certificate-authorities/read", "api-reference/endpoints/certificate-authorities/update", @@ -669,6 +670,7 @@ { "group": "Certificates", "pages": [ + "api-reference/endpoints/certificates/list", "api-reference/endpoints/certificates/read", "api-reference/endpoints/certificates/revoke", "api-reference/endpoints/certificates/delete", diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 24ec3a372..238415204 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -25,6 +25,10 @@ Used to configure platform-specific security and operational settings https://app.infisical.com). + + Specifies the internal port on which the application listens. + + Telemetry helps us improve Infisical but if you want to dsiable it you may set this to `false`. diff --git a/frontend/src/hooks/api/roles/mutation.tsx b/frontend/src/hooks/api/roles/mutation.tsx index ef330e053..7f831cd4c 100644 --- a/frontend/src/hooks/api/roles/mutation.tsx +++ b/frontend/src/hooks/api/roles/mutation.tsx @@ -79,7 +79,7 @@ export const useUpdateOrgRole = () => { data: { role } } = await apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, { ...dto, - permissions: permissions?.length ? packRules(permissions) : [] + permissions: permissions?.length ? packRules(permissions) : undefined }); return role; diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index 70f3eee25..a6235e9d5 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -168,16 +168,27 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { const { token: newJwtToken } = await selectOrganization({ organizationId }); const instance = axios.create(); - await instance.post(cliUrl, { + const payload = { ...isCliLoginSuccessful.loginResponse, JTWToken: newJwtToken + }; + await instance.post(cliUrl, payload).catch(() => { + // if error happens to communicate we set the token with an expiry in sessino storage + // the cli-redirect page has logic to show this to user and ask them to paste it in terminal + sessionStorage.setItem( + SessionStorageKeys.CLI_TERMINAL_TOKEN, + JSON.stringify({ + expiry: formatISO(addSeconds(new Date(), 30)), + data: window.btoa(JSON.stringify(payload)) + }) + ); }); - - await navigateUserToOrg(router, organizationId); + router.push("/cli-redirect"); + return; } // case: no organization ID is present -- navigate to the select org page IF the user has any orgs // if the user has no orgs, navigate to the create org page - else { + const userOrgs = await fetchOrganizations(); // case: user has orgs, so we navigate the user to select an org @@ -189,7 +200,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { else { await navigateUserToOrg(router); } - } + } } else { const isLoginSuccessful = await attemptLoginMfa({ diff --git a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx index 10be08f4c..16a06f9b7 100644 --- a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import HCaptcha from "@hcaptcha/react-hcaptcha"; import axios from "axios"; +import { addSeconds, formatISO } from "date-fns"; import jwt_decode from "jwt-decode"; import { createNotification } from "@app/components/notifications"; @@ -12,6 +13,7 @@ import attemptLogin from "@app/components/utilities/attemptLogin"; import { CAPTCHA_SITE_KEY } from "@app/components/utilities/config"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, Input, Spinner } from "@app/components/v2"; +import { SessionStorageKeys } from "@app/const"; import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchMyPrivateKey } from "@app/hooks/api/users/queries"; @@ -79,11 +81,24 @@ export const PasswordStep = ({ if (callbackPort) { console.log("organization id was present. new JWT token to be used in CLI:", newJwtToken); const instance = axios.create(); - await instance.post(cliUrl, { + const payload = { privateKey, email, JTWToken: newJwtToken + }; + await instance.post(cliUrl, payload).catch(() => { + // if error happens to communicate we set the token with an expiry in sessino storage + // the cli-redirect page has logic to show this to user and ask them to paste it in terminal + sessionStorage.setItem( + SessionStorageKeys.CLI_TERMINAL_TOKEN, + JSON.stringify({ + expiry: formatISO(addSeconds(new Date(), 30)), + data: window.btoa(JSON.stringify(payload)) + }) + ); }); + router.push("/cli-redirect"); + return; } await navigateUserToOrg(router, organizationId); @@ -165,26 +180,35 @@ export const PasswordStep = ({ ); const instance = axios.create(); - await instance.post(cliUrl, { + const payload = { ...isCliLoginSuccessful.loginResponse, JTWToken: newJwtToken + }; + await instance.post(cliUrl, payload).catch(() => { + // if error happens to communicate we set the token with an expiry in sessino storage + // the cli-redirect page has logic to show this to user and ask them to paste it in terminal + sessionStorage.setItem( + SessionStorageKeys.CLI_TERMINAL_TOKEN, + JSON.stringify({ + expiry: formatISO(addSeconds(new Date(), 30)), + data: window.btoa(JSON.stringify(payload)) + }) + ); }); - - await navigateUserToOrg(router, organizationId); + router.push("/cli-redirect"); + return; } // case: no organization ID is present -- navigate to the select org page IF the user has any orgs // if the user has no orgs, navigate to the create org page - else { - const userOrgs = await fetchOrganizations(); + const userOrgs = await fetchOrganizations(); - // case: user has orgs, so we navigate the user to select an org - if (userOrgs.length > 0) { - navigateToSelectOrganization(callbackPort); - } - // case: no orgs found, so we navigate the user to create an org - else { - await navigateUserToOrg(router); - } + // case: user has orgs, so we navigate the user to select an org + if (userOrgs.length > 0) { + navigateToSelectOrganization(callbackPort); + } + // case: no orgs found, so we navigate the user to create an org + else { + await navigateUserToOrg(router); } } } else { diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx deleted file mode 100644 index 9c8bb4da2..000000000 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import { useForm } from "react-hook-form"; -import { - faArrowLeft, - faCog, - faContactCard, - faMagnifyingGlass, - faMoneyBill, - faServer, - faSignIn, - faUser, - faUserCog, - faUsers -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { zodResolver } from "@hookform/resolvers/zod"; - -import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input } from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { useCreateOrgRole, useUpdateOrgRole } from "@app/hooks/api"; -import { TOrgRole } from "@app/hooks/api/roles/types"; - -import { - formRolePermission2API, - formSchema, - rolePermission2Form, - TFormSchema -} from "./OrgRoleModifySection.utils"; -import { SimpleLevelPermissionOption } from "./SimpleLevelPermissionOptions"; -import { WorkspacePermission } from "./WorkspacePermission"; - -type Props = { - role?: TOrgRole; - onGoBack: VoidFunction; -}; - -const SIMPLE_PERMISSION_OPTIONS = [ - { - title: "User management", - subtitle: "Invite, view and remove users from the organization", - icon: faUser, - formName: "member" - }, - { - title: "Group management", - subtitle: "Invite, view and remove user groups from the organization", - icon: faUsers, - formName: "groups" - }, - { - title: "Machine identity management", - subtitle: "Create, view, update and remove (machine) identities from the organization", - icon: faServer, - formName: "identity" - }, - { - title: "Billing & usage", - subtitle: "Modify organization subscription plan", - icon: faMoneyBill, - formName: "billing" - }, - { - title: "Role management", - subtitle: "Create, modify and remove organization roles", - icon: faUserCog, - formName: "role" - }, - { - title: "Incident Contacts", - subtitle: "Incident contacts management control", - icon: faContactCard, - formName: "incident-contact" - }, - { - title: "Organization profile", - subtitle: "View & update organization metadata such as name", - icon: faCog, - formName: "settings" - }, - { - title: "Secret Scanning", - subtitle: "Secret scanning management control", - icon: faMagnifyingGlass, - formName: "secret-scanning" - }, - { - title: "SSO", - subtitle: "Define organization level SSO requirements", - icon: faSignIn, - formName: "sso" - }, - { - title: "LDAP", - subtitle: "Define organization level LDAP requirements", - icon: faSignIn, - formName: "ldap" - }, - { - title: "SCIM", - subtitle: "Define organization level SCIM requirements", - icon: faUsers, - formName: "scim" - } -] as const; - -export const OrgRoleModifySection = ({ role, onGoBack }: Props) => { - const isNonEditable = ["owner", "admin", "member", "no-access"].includes(role?.slug || ""); - const isNewRole = !role?.slug; - const { currentOrg } = useOrganization(); - const orgId = currentOrg?.id || ""; - const { - handleSubmit, - register, - formState: { isSubmitting, isDirty, errors }, - setValue, - control - } = useForm({ - defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {}, - resolver: zodResolver(formSchema) - }); - - const { mutateAsync: createRole } = useCreateOrgRole(); - const { mutateAsync: updateRole } = useUpdateOrgRole(); - - const handleRoleUpdate = async (el: TFormSchema) => { - if (!role?.id) return; - - try { - await updateRole({ - orgId, - id: role?.id, - ...el, - permissions: formRolePermission2API(el.permissions) - }); - createNotification({ type: "success", text: "Successfully updated role" }); - onGoBack(); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update role" }); - } - }; - - const handleFormSubmit = async (el: TFormSchema) => { - if (!isNewRole) { - await handleRoleUpdate(el); - return; - } - - try { - await createRole({ - orgId, - ...el, - permissions: formRolePermission2API(el.permissions) - }); - createNotification({ type: "success", text: "Created new role" }); - onGoBack(); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to create role" }); - } - }; - - return ( -
-
-
-

- {isNewRole ? "New" : "Edit"} Role -

- -
-

- Organization-level roles allow you to define permissions for resources at a high level - across the organization -

-
- - - - - - - - - -
-
-

Add Permission

-
-
-
- -
- {SIMPLE_PERMISSION_OPTIONS.map(({ title, subtitle, icon, formName }) => ( -
- -
- ))} -
-
- - -
-
-
- ); -}; diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SimpleLevelPermissionOptions.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SimpleLevelPermissionOptions.tsx deleted file mode 100644 index 8fe6a1953..000000000 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SimpleLevelPermissionOptions.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { useEffect, useMemo } from "react"; -import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form"; -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { motion } from "framer-motion"; -import { twMerge } from "tailwind-merge"; - -import { Checkbox, Select, SelectItem } from "@app/components/v2"; -import { useToggle } from "@app/hooks"; - -import { TFormSchema } from "./OrgRoleModifySection.utils"; - -type Props = { - formName: keyof Omit, "workspace">; - isNonEditable?: boolean; - setValue: UseFormSetValue; - control: Control; - title: string; - subtitle: string; - icon: IconProp; -}; - -enum Permission { - NoAccess = "no-access", - ReadOnly = "read-only", - FullAccess = "full-acess", - Custom = "custom" -} - -const PERMISSIONS = [ - { action: "read", label: "View" }, - { action: "create", label: "Create" }, - { action: "edit", label: "Modify" }, - { action: "delete", label: "Remove" } -] as const; - -const SECRET_SCANNING_PERMISSIONS = [ - { action: "read", label: "View risks" }, - { action: "create", label: "Add integrations" }, - { action: "edit", label: "Edit risk status" }, - { action: "delete", label: "Remove integrations" } -] as const; - -const INCIDENT_CONTACTS_PERMISSIONS = [ - { action: "read", label: "View contacts" }, - { action: "create", label: "Add new contacts" }, - { action: "edit", label: "Edit contacts" }, - { action: "delete", label: "Remove contacts" } -] as const; - -const MEMBERS_PERMISSIONS = [ - { action: "read", label: "View all members" }, - { action: "create", label: "Invite members" }, - { action: "edit", label: "Edit members" }, - { 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 getPermissionList = (option: Props["formName"]) => { - switch (option) { - case "secret-scanning": - return SECRET_SCANNING_PERMISSIONS; - case "billing": - return BILLING_PERMISSIONS; - case "incident-contact": - return INCIDENT_CONTACTS_PERMISSIONS; - case "member": - return MEMBERS_PERMISSIONS; - default: - return PERMISSIONS; - } -}; - -export const SimpleLevelPermissionOption = ({ - isNonEditable, - setValue, - control, - formName, - subtitle, - title, - icon -}: Props) => { - const rule = useWatch({ - control, - name: `permissions.${formName}` - }); - const [isCustom, setIsCustom] = useToggle(); - - const selectedPermissionCategory = useMemo(() => { - const actions = Object.keys(rule || {}) as Array; - const totalActions = PERMISSIONS.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?.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) setIsCustom.on(); - else 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; - } - }; - - return ( -
-
-
- -
-
-
{title}
-
{subtitle}
-
-
- -
-
- - {isCustom && - getPermissionList(formName).map(({ action, label }) => ( - ( - - {label} - - )} - /> - ))} - -
- ); -}; diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx index 7c98f1759..465029f8b 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx @@ -8,7 +8,7 @@ import { twMerge } from "tailwind-merge"; import { Checkbox, Select, SelectItem } from "@app/components/v2"; import { useToggle } from "@app/hooks"; -import { TFormSchema } from "./OrgRoleModifySection.utils"; +import { TFormSchema } from "../../../../RolePage/components/OrgRoleModifySection.utils"; type Props = { isNonEditable?: boolean; diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/index.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/index.tsx deleted file mode 100644 index 86de1647a..000000000 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { OrgRoleModifySection } from "./OrgRoleModifySection"; diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx index ef83bca9c..8e6c5d591 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx @@ -1,27 +1,9 @@ import { motion } from "framer-motion"; -import { usePopUp } from "@app/hooks"; -import { TOrgRole } from "@app/hooks/api/roles/types"; - -import { OrgRoleModifySection } from "./OrgRoleModifySection"; import { OrgRoleTable } from "./OrgRoleTable"; export const OrgRoleTabSection = () => { - const { popUp, handlePopUpClose } = usePopUp(["editRole"] as const); - return popUp.editRole.isOpen ? ( - - handlePopUpClose("editRole")} - /> - - ) : ( + return (