From e639f5ee49477ad1757d2fdca3b9647a62b7e31a Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 28 Mar 2025 16:17:14 -0300 Subject: [PATCH 1/4] Improve FilterableSelect to support optional grouping by option field --- .../v2/FilterableSelect/FilterableSelect.tsx | 181 +++++++++++------- .../components/v2/Select/components/index.tsx | 5 + .../OrgMembersSection/AddOrgMemberModal.tsx | 19 +- 3 files changed, 132 insertions(+), 73 deletions(-) diff --git a/frontend/src/components/v2/FilterableSelect/FilterableSelect.tsx b/frontend/src/components/v2/FilterableSelect/FilterableSelect.tsx index 7bf546b13..8f51c2867 100644 --- a/frontend/src/components/v2/FilterableSelect/FilterableSelect.tsx +++ b/frontend/src/components/v2/FilterableSelect/FilterableSelect.tsx @@ -1,81 +1,118 @@ import Select, { Props } from "react-select"; import { twMerge } from "tailwind-merge"; -import { ClearIndicator, DropdownIndicator, MultiValueRemove, Option } from "../Select/components"; +import { + ClearIndicator, + DropdownIndicator, + Group, + MultiValueRemove, + Option +} from "../Select/components"; export const FilterableSelect = ({ isMulti, closeMenuOnSelect, tabSelectsValue = false, + groupBy = null, + getGroupHeaderLabel = null, + options = [], ...props -}: Props) => ( - ({ + ...base, + "input:focus": { + boxShadow: "none" + } + }), + multiValueLabel: (base) => ({ + ...base, + whiteSpace: "normal", + overflow: "visible" + }), + control: (base) => ({ + ...base, + transition: "none" + }) + }} + tabSelectsValue={tabSelectsValue} + components={{ + DropdownIndicator, + ClearIndicator, + MultiValueRemove, + Option, + Group, + ...props.components + }} + classNames={{ + container: ({ isDisabled }) => + twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"), + control: ({ isFocused, isDisabled }) => + twMerge( + isFocused ? "border-primary-400/50" : "border-mineshaft-600", + `w-full rounded-md border bg-mineshaft-900 p-0.5 font-inter text-mineshaft-200 ${ + isDisabled ? "!cursor-not-allowed" : "hover:cursor-pointer hover:border-gray-400" + } ` + ), + placeholder: () => + `${isMulti ? "py-[0.22rem]" : "leading-7"} text-mineshaft-400 text-sm pl-1`, + input: () => "pl-1", + valueContainer: () => + `px-1 max-h-[8.2rem] ${ + isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]" + } gap-1`, + singleValue: () => "leading-7 ml-1", + multiValue: () => "bg-mineshaft-600 text-sm rounded items-center py-0.5 px-2 gap-1.5", + multiValueLabel: () => "leading-6 text-sm", + multiValueRemove: () => "hover:text-red text-bunker-400", + indicatorsContainer: () => "p-1 gap-1", + clearIndicator: () => "p-1 hover:text-red text-bunker-400", + indicatorSeparator: () => "bg-bunker-400", + dropdownIndicator: () => "text-bunker-200 p-1", + menuList: () => "flex flex-col gap-1", + menu: () => + "my-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md", + groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm", + option: ({ isFocused, isSelected }) => + twMerge( + isFocused && "bg-mineshaft-700 active:bg-mineshaft-600", + isSelected && "text-mineshaft-200", + "rounded px-3 py-2 text-xs hover:cursor-pointer" + ), + noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md" + }} + {...props} + /> + ); +}; diff --git a/frontend/src/components/v2/Select/components/index.tsx b/frontend/src/components/v2/Select/components/index.tsx index 8d0e57d87..7e91c792d 100644 --- a/frontend/src/components/v2/Select/components/index.tsx +++ b/frontend/src/components/v2/Select/components/index.tsx @@ -2,6 +2,7 @@ import { ClearIndicatorProps, components, DropdownIndicatorProps, + GroupProps, MultiValueRemoveProps, OptionProps } from "react-select"; @@ -45,3 +46,7 @@ export const Option = ({ isSelected, children, ...props }: OptionProps) = ); }; + +export const Group = (props: GroupProps) => { + return ; +}; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx index f516d43fe..afde75e22 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx @@ -23,7 +23,7 @@ import { useGetUserWorkspaces } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { ProjectVersion } from "@app/hooks/api/workspace/types"; +import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { OrgInviteLink } from "./OrgInviteLink"; @@ -168,6 +168,21 @@ export const AddOrgMemberModal = ({ reset(); }; + const getGroupHeaderLabel = (type: ProjectType) => { + switch (type) { + case ProjectType.SecretManager: + return "Secrets"; + case ProjectType.CertificateManager: + return "PKI"; + case ProjectType.KMS: + return "KMS"; + case ProjectType.SSH: + return "SSH"; + default: + return "All"; + } + }; + return ( project.name} getOptionValue={(project) => project.id} options={projects} + groupBy="type" + getGroupHeaderLabel={getGroupHeaderLabel} placeholder="Select projects..." /> From db5a85d3ca0bf67adde180f6984dbcd491ec287e Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 28 Mar 2025 17:38:07 -0300 Subject: [PATCH 2/4] Renamed default value of getGroupHeaderLabel --- .../components/OrgMembersSection/AddOrgMemberModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx index afde75e22..f9c454985 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx @@ -179,7 +179,7 @@ export const AddOrgMemberModal = ({ case ProjectType.SSH: return "SSH"; default: - return "All"; + return "Other"; } }; From 0a28ac4a7d4f38ddeea4bd7221250810b0aacc3a Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 30 Mar 2025 16:13:41 -0400 Subject: [PATCH 3/4] extract region only --- .../identity-aws-auth-service.ts | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index 4622bc509..f430b4149 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -42,6 +42,31 @@ type TIdentityAwsAuthServiceFactoryDep = { export type TIdentityAwsAuthServiceFactory = ReturnType; +const awsRegionFromHeader = (authorizationHeader: string): string | null => { + // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html + // The Authorization header takes the following form. + // Authorization: AWS4-HMAC-SHA256 + // Credential=AKIAIOSFODNN7EXAMPLE/20230719/us-east-1/sts/aws4_request, + // SignedHeaders=content-length;content-type;host;x-amz-date, + // Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 + // + // The credential is in the form of "////aws4_request" + try { + const fields = authorizationHeader.split(" "); + for (const field of fields) { + if (field.startsWith("Credential=")) { + const parts = field.split("/"); + if (parts.length >= 3) { + return parts[2]; + } + } + } + } catch { + return null; + } + return null; +}; + export const identityAwsAuthServiceFactory = ({ identityAccessTokenDAL, identityAwsAuthDAL, @@ -58,7 +83,10 @@ export const identityAwsAuthServiceFactory = ({ const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsAuth.identityId }); const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString()); - const body: string = Buffer.from(iamRequestBody, "base64").toString(); + const body: string = Buffer.from(iamRequestBody, "base64").toString(); + + const region = headers.Authorization ? awsRegionFromHeader(headers.Authorization) : null; + const url = region ? `https://sts.${region}.amazonaws.com` : identityAwsAuth.stsEndpoint; const { data: { @@ -68,7 +96,7 @@ export const identityAwsAuthServiceFactory = ({ } }: { data: TGetCallerIdentityResponse } = await axios({ method: iamHttpRequestMethod, - url: headers?.Host ? `https://${headers.Host}` : identityAwsAuth.stsEndpoint, + url, headers, data: body }); From dd79d0385abb94e04fd2447dfd6032ecc67ff680 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 31 Mar 2025 21:33:12 +0530 Subject: [PATCH 4/4] feat: minor bug fixes and patch --- .../dynamic-secret-lease-service.ts | 4 ++-- .../dynamic-secret/dynamic-secret-fns.ts | 2 +- .../identity-aws-auth-service.ts | 16 ++++++++-------- .../identity-ua/identity-ua-service.ts | 18 +++++++++++++++--- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index 43f6197bf..8feee1830 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -183,7 +183,7 @@ export const dynamicSecretLeaseServiceFactory = ({ }); const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); - if (!dynamicSecretLease) { + if (!dynamicSecretLease || dynamicSecretLease.dynamicSecret.folderId !== folder.id) { throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` }); } @@ -256,7 +256,7 @@ export const dynamicSecretLeaseServiceFactory = ({ }); const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); - if (!dynamicSecretLease) + if (!dynamicSecretLease || dynamicSecretLease.dynamicSecret.folderId !== folder.id) throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` }); const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-fns.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-fns.ts index 57c05b6fb..02b83c0f9 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-fns.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-fns.ts @@ -8,7 +8,7 @@ import { getDbConnectionHost } from "@app/lib/knex"; export const verifyHostInputValidity = async (host: string, isGateway = false) => { const appCfg = getConfig(); - // if (appCfg.NODE_ENV === "development") return; // incase you want to remove this check in dev + // if (appCfg.NODE_ENV === "development") return ["host.docker.internal"]; // incase you want to remove this check in dev const reservedHosts = [appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI)].concat( (appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)), diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index f430b4149..7f0aadfca 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -44,13 +44,13 @@ export type TIdentityAwsAuthServiceFactory = ReturnType { // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html - // The Authorization header takes the following form. - // Authorization: AWS4-HMAC-SHA256 - // Credential=AKIAIOSFODNN7EXAMPLE/20230719/us-east-1/sts/aws4_request, - // SignedHeaders=content-length;content-type;host;x-amz-date, - // Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 - // - // The credential is in the form of "////aws4_request" + // The Authorization header takes the following form. + // Authorization: AWS4-HMAC-SHA256 + // Credential=AKIAIOSFODNN7EXAMPLE/20230719/us-east-1/sts/aws4_request, + // SignedHeaders=content-length;content-type;host;x-amz-date, + // Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 + // + // The credential is in the form of "////aws4_request" try { const fields = authorizationHeader.split(" "); for (const field of fields) { @@ -83,7 +83,7 @@ export const identityAwsAuthServiceFactory = ({ const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsAuth.identityId }); const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString()); - const body: string = Buffer.from(iamRequestBody, "base64").toString(); + const body: string = Buffer.from(iamRequestBody, "base64").toString(); const region = headers.Authorization ? awsRegionFromHeader(headers.Authorization) : null; const url = region ? `https://sts.${region}.amazonaws.com` : identityAwsAuth.stsEndpoint; diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index 15bbd6561..8ab499e65 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -471,6 +471,7 @@ export const identityUaServiceFactory = ({ const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS); const identityUaAuth = await identityUaDAL.findOne({ identityId: identityMembershipOrg.identityId }); + if (!identityUaAuth) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); const identityUaClientSecret = await identityUaClientSecretDAL.create({ identityUAId: identityUaAuth.id, @@ -567,6 +568,12 @@ export const identityUaServiceFactory = ({ }); } + const identityUa = await identityUaDAL.findOne({ identityId }); + if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + + const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id }); + if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + const { permission, membership } = await permissionService.getOrgPermission( actor, actorId, @@ -601,7 +608,6 @@ export const identityUaServiceFactory = ({ details: { missingPermissions: permissionBoundary.missingPermissions } }); - const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId); return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId }; }; @@ -622,6 +628,12 @@ export const identityUaServiceFactory = ({ }); } + const identityUa = await identityUaDAL.findOne({ identityId }); + if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + + const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id }); + if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + const { permission, membership } = await permissionService.getOrgPermission( actor, actorId, @@ -658,11 +670,11 @@ export const identityUaServiceFactory = ({ }); } - const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, { + const updatedClientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, { isClientSecretRevoked: true }); - return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId }; + return { ...updatedClientSecret, identityId, orgId: identityMembershipOrg.orgId }; }; return {