Merge branch 'main' into fix/teamCityParametersHiddenConfig

This commit is contained in:
carlosmonastyrski
2025-03-31 14:14:17 -03:00
7 changed files with 179 additions and 80 deletions
@@ -183,7 +183,7 @@ export const dynamicSecretLeaseServiceFactory = ({
}); });
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); 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` }); 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); 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` }); throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
@@ -8,7 +8,7 @@ import { getDbConnectionHost } from "@app/lib/knex";
export const verifyHostInputValidity = async (host: string, isGateway = false) => { export const verifyHostInputValidity = async (host: string, isGateway = false) => {
const appCfg = getConfig(); 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( const reservedHosts = [appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI)].concat(
(appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)), (appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)),
@@ -42,6 +42,31 @@ type TIdentityAwsAuthServiceFactoryDep = {
export type TIdentityAwsAuthServiceFactory = ReturnType<typeof identityAwsAuthServiceFactory>; export type TIdentityAwsAuthServiceFactory = ReturnType<typeof identityAwsAuthServiceFactory>;
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 "<your-access-key-id>/<date>/<aws-region>/<aws-service>/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 = ({ export const identityAwsAuthServiceFactory = ({
identityAccessTokenDAL, identityAccessTokenDAL,
identityAwsAuthDAL, identityAwsAuthDAL,
@@ -60,6 +85,9 @@ export const identityAwsAuthServiceFactory = ({
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString()); 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 { const {
data: { data: {
GetCallerIdentityResponse: { GetCallerIdentityResponse: {
@@ -68,7 +96,7 @@ export const identityAwsAuthServiceFactory = ({
} }
}: { data: TGetCallerIdentityResponse } = await axios({ }: { data: TGetCallerIdentityResponse } = await axios({
method: iamHttpRequestMethod, method: iamHttpRequestMethod,
url: headers?.Host ? `https://${headers.Host}` : identityAwsAuth.stsEndpoint, url,
headers, headers,
data: body data: body
}); });
@@ -471,6 +471,7 @@ export const identityUaServiceFactory = ({
const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS); const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS);
const identityUaAuth = await identityUaDAL.findOne({ identityId: identityMembershipOrg.identityId }); 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({ const identityUaClientSecret = await identityUaClientSecretDAL.create({
identityUAId: identityUaAuth.id, 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( const { permission, membership } = await permissionService.getOrgPermission(
actor, actor,
actorId, actorId,
@@ -601,7 +608,6 @@ export const identityUaServiceFactory = ({
details: { missingPermissions: permissionBoundary.missingPermissions } details: { missingPermissions: permissionBoundary.missingPermissions }
}); });
const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId);
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId }; 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( const { permission, membership } = await permissionService.getOrgPermission(
actor, actor,
actorId, actorId,
@@ -658,11 +670,11 @@ export const identityUaServiceFactory = ({
}); });
} }
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, { const updatedClientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
isClientSecretRevoked: true isClientSecretRevoked: true
}); });
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId }; return { ...updatedClientSecret, identityId, orgId: identityMembershipOrg.orgId };
}; };
return { return {
@@ -1,81 +1,118 @@
import Select, { Props } from "react-select"; import Select, { Props } from "react-select";
import { twMerge } from "tailwind-merge"; 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 = <T,>({ export const FilterableSelect = <T,>({
isMulti, isMulti,
closeMenuOnSelect, closeMenuOnSelect,
tabSelectsValue = false, tabSelectsValue = false,
groupBy = null,
getGroupHeaderLabel = null,
options = [],
...props ...props
}: Props<T>) => ( }: Props<T> & {
<Select groupBy?: string | null;
isMulti={isMulti} getGroupHeaderLabel?: ((groupValue: any) => string) | null;
closeMenuOnSelect={closeMenuOnSelect ?? !isMulti} }) => {
hideSelectedOptions={false} let processedOptions = options;
unstyled
styles={{ if (groupBy && Array.isArray(options)) {
input: (base) => ({ const groupedOptions = options.reduce((acc, option) => {
...base, const groupValue = option[groupBy];
"input:focus": { const groupKey = groupValue?.toString() || "undefined";
boxShadow: "none"
} if (!acc[groupKey]) {
}), acc[groupKey] = {
multiValueLabel: (base) => ({ label: getGroupHeaderLabel ? getGroupHeaderLabel(groupValue) : groupValue,
...base, options: []
whiteSpace: "normal", };
overflow: "visible" }
}),
control: (base) => ({ acc[groupKey].options.push(option);
...base, return acc;
transition: "none" }, {});
})
}} processedOptions = Object.values(groupedOptions);
tabSelectsValue={tabSelectsValue} }
components={{
DropdownIndicator, return (
ClearIndicator, <Select
MultiValueRemove, isMulti={isMulti}
Option, closeMenuOnSelect={closeMenuOnSelect ?? !isMulti}
...props.components hideSelectedOptions={false}
}} unstyled
classNames={{ options={processedOptions}
container: ({ isDisabled }) => styles={{
twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"), input: (base) => ({
control: ({ isFocused, isDisabled }) => ...base,
twMerge( "input:focus": {
isFocused ? "border-primary-400/50" : "border-mineshaft-600", boxShadow: "none"
`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" }),
} ` multiValueLabel: (base) => ({
), ...base,
placeholder: () => whiteSpace: "normal",
`${isMulti ? "py-[0.22rem]" : "leading-7"} text-mineshaft-400 text-sm pl-1`, overflow: "visible"
input: () => "pl-1", }),
valueContainer: () => control: (base) => ({
`px-1 max-h-[8.2rem] ${ ...base,
isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]" transition: "none"
} gap-1`, })
singleValue: () => "leading-7 ml-1", }}
multiValue: () => "bg-mineshaft-600 text-sm rounded items-center py-0.5 px-2 gap-1.5", tabSelectsValue={tabSelectsValue}
multiValueLabel: () => "leading-6 text-sm", components={{
multiValueRemove: () => "hover:text-red text-bunker-400", DropdownIndicator,
indicatorsContainer: () => "p-1 gap-1", ClearIndicator,
clearIndicator: () => "p-1 hover:text-red text-bunker-400", MultiValueRemove,
indicatorSeparator: () => "bg-bunker-400", Option,
dropdownIndicator: () => "text-bunker-200 p-1", Group,
menuList: () => "flex flex-col gap-1", ...props.components
menu: () => }}
"my-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md", classNames={{
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm", container: ({ isDisabled }) =>
option: ({ isFocused, isSelected }) => twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"),
twMerge( control: ({ isFocused, isDisabled }) =>
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600", twMerge(
isSelected && "text-mineshaft-200", isFocused ? "border-primary-400/50" : "border-mineshaft-600",
"rounded px-3 py-2 text-xs hover:cursor-pointer" `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"
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md" } `
}} ),
{...props} 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}
/>
);
};
@@ -2,6 +2,7 @@ import {
ClearIndicatorProps, ClearIndicatorProps,
components, components,
DropdownIndicatorProps, DropdownIndicatorProps,
GroupProps,
MultiValueRemoveProps, MultiValueRemoveProps,
OptionProps OptionProps
} from "react-select"; } from "react-select";
@@ -45,3 +46,7 @@ export const Option = <T,>({ isSelected, children, ...props }: OptionProps<T>) =
</components.Option> </components.Option>
); );
}; };
export const Group = <T,>(props: GroupProps<T>) => {
return <components.Group {...props} />;
};
@@ -23,7 +23,7 @@ import {
useGetUserWorkspaces useGetUserWorkspaces
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; 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 { UsePopUpState } from "@app/hooks/usePopUp";
import { OrgInviteLink } from "./OrgInviteLink"; import { OrgInviteLink } from "./OrgInviteLink";
@@ -168,6 +168,21 @@ export const AddOrgMemberModal = ({
reset(); 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 "Other";
}
};
return ( return (
<Modal <Modal
isOpen={popUp?.addMember?.isOpen} isOpen={popUp?.addMember?.isOpen}
@@ -247,6 +262,8 @@ export const AddOrgMemberModal = ({
getOptionLabel={(project) => project.name} getOptionLabel={(project) => project.name}
getOptionValue={(project) => project.id} getOptionValue={(project) => project.id}
options={projects} options={projects}
groupBy="type"
getGroupHeaderLabel={getGroupHeaderLabel}
placeholder="Select projects..." placeholder="Select projects..."
/> />
</FormControl> </FormControl>