- Add a max iteration to loop - Hide gateways on frontend if license

does not allow them - Fix capitalization issue with GitHub secret sync
This commit is contained in:
x032205
2025-07-24 22:25:23 -04:00
parent e34deb7bd0
commit 0f06c4c27a
7 changed files with 296 additions and 266 deletions
@@ -116,6 +116,18 @@ export const getGitHubAppAuthToken = async (appConnection: TGitHubConnection) =>
return token; return token;
}; };
function extractNextPageUrl(linkHeader: string | undefined): string | null {
if (!linkHeader) return null;
const links = linkHeader.split(",");
const nextLink = links.find((link) => link.includes('rel="next"'));
if (!nextLink) return null;
const match = new RE2(/<([^>]+)>/).exec(nextLink);
return match ? match[1] : null;
}
export const makePaginatedGitHubRequest = async <T, R = T[]>( export const makePaginatedGitHubRequest = async <T, R = T[]>(
appConnection: TGitHubConnection, appConnection: TGitHubConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">, gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
@@ -128,8 +140,9 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection); method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection);
let url: string | null = `https://api.${credentials.host || "github.com"}${path}`; let url: string | null = `https://api.${credentials.host || "github.com"}${path}`;
let results: T[] = []; let results: T[] = [];
let i = 0;
while (url) { while (url && i < 1000) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const response: AxiosResponse<R> = await requestWithGitHubGateway<R>(appConnection, gatewayService, { const response: AxiosResponse<R> = await requestWithGitHubGateway<R>(appConnection, gatewayService, {
url, url,
@@ -144,14 +157,8 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
const items = dataMapper ? dataMapper(response.data) : (response.data as unknown as T[]); const items = dataMapper ? dataMapper(response.data) : (response.data as unknown as T[]);
results = results.concat(items); results = results.concat(items);
const linkHeader = response.headers.link as string | undefined; url = extractNextPageUrl(response.headers.link as string | undefined);
const nextLink = i += 1;
typeof linkHeader === "string" ? linkHeader.split(",").find((s) => s.includes('rel="next"')) : undefined;
if (nextLink) {
url = new RE2(/<(.+)>/).exec(nextLink)?.[1] || null;
} else {
url = null;
}
} }
return results; return results;
@@ -171,9 +171,11 @@ const putSecret = async (
export const GithubSyncFns = { export const GithubSyncFns = {
syncSecrets: async ( syncSecrets: async (
secretSync: TGitHubSyncWithCredentials, secretSync: TGitHubSyncWithCredentials,
secretMap: TSecretMap, ogSecretMap: TSecretMap,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId"> gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => { ) => {
const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v]));
switch (secretSync.destinationConfig.scope) { switch (secretSync.destinationConfig.scope) {
case GitHubSyncScope.Organization: case GitHubSyncScope.Organization:
if (Object.values(secretMap).length > 1000) { if (Object.values(secretMap).length > 1000) {
@@ -252,9 +254,11 @@ export const GithubSyncFns = {
}, },
removeSecrets: async ( removeSecrets: async (
secretSync: TGitHubSyncWithCredentials, secretSync: TGitHubSyncWithCredentials,
secretMap: TSecretMap, ogSecretMap: TSecretMap,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId"> gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => { ) => {
const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v]));
const { connection } = secretSync; const { connection } = secretSync;
const token = const token =
connection.method === GitHubConnectionMethod.OAuth connection.method === GitHubConnectionMethod.OAuth
@@ -20,6 +20,7 @@ import {
SelectItem, SelectItem,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { import {
OrgGatewayPermissionActions, OrgGatewayPermissionActions,
OrgPermissionSubjects OrgPermissionSubjects
@@ -80,6 +81,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
formState: { isSubmitting, isDirty } formState: { isSubmitting, isDirty }
} = form; } = form;
const { subscription } = useSubscription();
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
const selectedMethod = watch("method"); const selectedMethod = watch("method");
@@ -173,80 +175,81 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<Accordion type="single" collapsible className="w-full"> {subscription.gateway && (
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none"> <Accordion type="single" collapsible className="w-full">
<AccordionTrigger className="h-fit flex-none pl-1 text-sm"> <AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
<div className="order-1 ml-3">GitHub Enterprise Options</div> <AccordionTrigger className="h-fit flex-none pl-1 text-sm">
</AccordionTrigger> <div className="order-1 ml-3">GitHub Enterprise Options</div>
<AccordionContent childrenClassName="px-0"> </AccordionTrigger>
<OrgPermissionCan <AccordionContent childrenClassName="px-0">
I={OrgGatewayPermissionActions.AttachGateways} <OrgPermissionCan
a={OrgPermissionSubjects.Gateway} I={OrgGatewayPermissionActions.AttachGateways}
> a={OrgPermissionSubjects.Gateway}
{(isAllowed) => ( >
<Controller {(isAllowed) => (
control={control} <Controller
name="gatewayId" control={control}
defaultValue="" name="gatewayId"
render={({ field: { value, onChange }, fieldState: { error } }) => ( defaultValue=""
<FormControl render={({ field: { value, onChange }, fieldState: { error } }) => (
isError={Boolean(error?.message)} <FormControl
errorText={error?.message} isError={Boolean(error?.message)}
label="Gateway" errorText={error?.message}
> label="Gateway"
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
> >
<div> <Tooltip
<Select isDisabled={isAllowed}
isDisabled={!isAllowed} content="Restricted access. You don't have permission to attach gateways to resources."
value={value as string} >
onValueChange={onChange} <div>
className="w-full border border-mineshaft-500" <Select
dropdownContainerClassName="max-w-none" isDisabled={!isAllowed}
isLoading={isGatewaysLoading} value={value as string}
placeholder="Default: Internet Gateway" onValueChange={onChange}
position="popper" className="w-full border border-mineshaft-500"
> dropdownContainerClassName="max-w-none"
<SelectItem isLoading={isGatewaysLoading}
value={null as unknown as string} placeholder="Default: Internet Gateway"
onClick={() => onChange(undefined)} position="popper"
> >
Internet Gateway <SelectItem
</SelectItem> value={null as unknown as string}
{gateways?.map((el) => ( onClick={() => onChange(undefined)}
<SelectItem value={el.id} key={el.id}> >
{el.name} Internet Gateway
</SelectItem> </SelectItem>
))} {gateways?.map((el) => (
</Select> <SelectItem value={el.id} key={el.id}>
</div> {el.name}
</Tooltip> </SelectItem>
</FormControl> ))}
)} </Select>
/> </div>
)} </Tooltip>
</OrgPermissionCan> </FormControl>
<Controller )}
name="credentials.host" />
control={control} )}
shouldUnregister </OrgPermissionCan>
render={({ field, fieldState: { error } }) => ( <Controller
<FormControl name="credentials.host"
errorText={error?.message} control={control}
isError={Boolean(error?.message)} shouldUnregister
label="Hostname" render={({ field, fieldState: { error } }) => (
isOptional <FormControl
> errorText={error?.message}
<Input {...field} placeholder="github.com" /> isError={Boolean(error?.message)}
</FormControl> label="Hostname"
)} isOptional
/> >
</AccordionContent> <Input {...field} placeholder="github.com" />
</AccordionItem> </FormControl>
</Accordion> )}
/>
</AccordionContent>
</AccordionItem>
</Accordion>
)}
<div className="mt-8 flex items-center"> <div className="mt-8 flex items-center">
<Button <Button
className="mr-4" className="mr-4"
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2"; import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { import {
OrgGatewayPermissionActions, OrgGatewayPermissionActions,
OrgPermissionSubjects OrgPermissionSubjects
@@ -78,6 +79,7 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty } formState: { isSubmitting, isDirty }
} = form; } = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false; const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -99,55 +101,57 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
}} }}
> >
{!isUpdate && <GenericAppConnectionsFields />} {!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan {subscription.gateway && (
I={OrgGatewayPermissionActions.AttachGateways} <OrgPermissionCan
a={OrgPermissionSubjects.Gateway} I={OrgGatewayPermissionActions.AttachGateways}
> a={OrgPermissionSubjects.Gateway}
{(isAllowed) => ( >
<Controller {(isAllowed) => (
control={control} <Controller
name="gatewayId" control={control}
defaultValue="" name="gatewayId"
render={({ field: { value, onChange }, fieldState: { error } }) => ( defaultValue=""
<FormControl render={({ field: { value, onChange }, fieldState: { error } }) => (
isError={Boolean(error?.message)} <FormControl
errorText={error?.message} isError={Boolean(error?.message)}
label="Gateway" errorText={error?.message}
> label="Gateway"
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
> >
<div> <Tooltip
<Select isDisabled={isAllowed}
isDisabled={!isAllowed} content="Restricted access. You don't have permission to attach gateways to resources."
value={value as string} >
onValueChange={onChange} <div>
className="w-full border border-mineshaft-500" <Select
dropdownContainerClassName="max-w-none" isDisabled={!isAllowed}
isLoading={isGatewaysLoading} value={value as string}
placeholder="Default: Internet Gateway" onValueChange={onChange}
position="popper" className="w-full border border-mineshaft-500"
> dropdownContainerClassName="max-w-none"
<SelectItem isLoading={isGatewaysLoading}
value={null as unknown as string} placeholder="Default: Internet Gateway"
onClick={() => onChange(undefined)} position="popper"
> >
Internet Gateway <SelectItem
</SelectItem> value={null as unknown as string}
{gateways?.map((el) => ( onClick={() => onChange(undefined)}
<SelectItem value={el.id} key={el.id}> >
{el.name} Internet Gateway
</SelectItem> </SelectItem>
))} {gateways?.map((el) => (
</Select> <SelectItem value={el.id} key={el.id}>
</div> {el.name}
</Tooltip> </SelectItem>
</FormControl> ))}
)} </Select>
/> </div>
)} </Tooltip>
</OrgPermissionCan> </FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller <Controller
name="method" name="method"
control={control} control={control}
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2"; import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { import {
OrgGatewayPermissionActions, OrgGatewayPermissionActions,
OrgPermissionSubjects OrgPermissionSubjects
@@ -75,6 +76,7 @@ export const MySqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty } formState: { isSubmitting, isDirty }
} = form; } = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false; const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -96,55 +98,57 @@ export const MySqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
}} }}
> >
{!isUpdate && <GenericAppConnectionsFields />} {!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan {subscription.gateway && (
I={OrgGatewayPermissionActions.AttachGateways} <OrgPermissionCan
a={OrgPermissionSubjects.Gateway} I={OrgGatewayPermissionActions.AttachGateways}
> a={OrgPermissionSubjects.Gateway}
{(isAllowed) => ( >
<Controller {(isAllowed) => (
control={control} <Controller
name="gatewayId" control={control}
defaultValue="" name="gatewayId"
render={({ field: { value, onChange }, fieldState: { error } }) => ( defaultValue=""
<FormControl render={({ field: { value, onChange }, fieldState: { error } }) => (
isError={Boolean(error?.message)} <FormControl
errorText={error?.message} isError={Boolean(error?.message)}
label="Gateway" errorText={error?.message}
> label="Gateway"
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
> >
<div> <Tooltip
<Select isDisabled={isAllowed}
isDisabled={!isAllowed} content="Restricted access. You don't have permission to attach gateways to resources."
value={value as string} >
onValueChange={onChange} <div>
className="w-full border border-mineshaft-500" <Select
dropdownContainerClassName="max-w-none" isDisabled={!isAllowed}
isLoading={isGatewaysLoading} value={value as string}
placeholder="Default: Internet Gateway" onValueChange={onChange}
position="popper" className="w-full border border-mineshaft-500"
> dropdownContainerClassName="max-w-none"
<SelectItem isLoading={isGatewaysLoading}
value={null as unknown as string} placeholder="Default: Internet Gateway"
onClick={() => onChange(undefined)} position="popper"
> >
Internet Gateway <SelectItem
</SelectItem> value={null as unknown as string}
{gateways?.map((el) => ( onClick={() => onChange(undefined)}
<SelectItem value={el.id} key={el.id}> >
{el.name} Internet Gateway
</SelectItem> </SelectItem>
))} {gateways?.map((el) => (
</Select> <SelectItem value={el.id} key={el.id}>
</div> {el.name}
</Tooltip> </SelectItem>
</FormControl> ))}
)} </Select>
/> </div>
)} </Tooltip>
</OrgPermissionCan> </FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller <Controller
name="method" name="method"
control={control} control={control}
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2"; import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { import {
OrgGatewayPermissionActions, OrgGatewayPermissionActions,
OrgPermissionSubjects OrgPermissionSubjects
@@ -75,6 +76,7 @@ export const OracleDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty } formState: { isSubmitting, isDirty }
} = form; } = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false; const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -96,55 +98,57 @@ export const OracleDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
}} }}
> >
{!isUpdate && <GenericAppConnectionsFields />} {!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan {subscription.gateway && (
I={OrgGatewayPermissionActions.AttachGateways} <OrgPermissionCan
a={OrgPermissionSubjects.Gateway} I={OrgGatewayPermissionActions.AttachGateways}
> a={OrgPermissionSubjects.Gateway}
{(isAllowed) => ( >
<Controller {(isAllowed) => (
control={control} <Controller
name="gatewayId" control={control}
defaultValue="" name="gatewayId"
render={({ field: { value, onChange }, fieldState: { error } }) => ( defaultValue=""
<FormControl render={({ field: { value, onChange }, fieldState: { error } }) => (
isError={Boolean(error?.message)} <FormControl
errorText={error?.message} isError={Boolean(error?.message)}
label="Gateway" errorText={error?.message}
> label="Gateway"
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
> >
<div> <Tooltip
<Select isDisabled={isAllowed}
isDisabled={!isAllowed} content="Restricted access. You don't have permission to attach gateways to resources."
value={value as string} >
onValueChange={onChange} <div>
className="w-full border border-mineshaft-500" <Select
dropdownContainerClassName="max-w-none" isDisabled={!isAllowed}
isLoading={isGatewaysLoading} value={value as string}
placeholder="Default: Internet Gateway" onValueChange={onChange}
position="popper" className="w-full border border-mineshaft-500"
> dropdownContainerClassName="max-w-none"
<SelectItem isLoading={isGatewaysLoading}
value={null as unknown as string} placeholder="Default: Internet Gateway"
onClick={() => onChange(undefined)} position="popper"
> >
Internet Gateway <SelectItem
</SelectItem> value={null as unknown as string}
{gateways?.map((el) => ( onClick={() => onChange(undefined)}
<SelectItem value={el.id} key={el.id}> >
{el.name} Internet Gateway
</SelectItem> </SelectItem>
))} {gateways?.map((el) => (
</Select> <SelectItem value={el.id} key={el.id}>
</div> {el.name}
</Tooltip> </SelectItem>
</FormControl> ))}
)} </Select>
/> </div>
)} </Tooltip>
</OrgPermissionCan> </FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller <Controller
name="method" name="method"
control={control} control={control}
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2"; import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { import {
OrgGatewayPermissionActions, OrgGatewayPermissionActions,
OrgPermissionSubjects OrgPermissionSubjects
@@ -75,6 +76,7 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty } formState: { isSubmitting, isDirty }
} = form; } = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false; const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -96,55 +98,57 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
}} }}
> >
{!isUpdate && <GenericAppConnectionsFields />} {!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan {subscription.gateway && (
I={OrgGatewayPermissionActions.AttachGateways} <OrgPermissionCan
a={OrgPermissionSubjects.Gateway} I={OrgGatewayPermissionActions.AttachGateways}
> a={OrgPermissionSubjects.Gateway}
{(isAllowed) => ( >
<Controller {(isAllowed) => (
control={control} <Controller
name="gatewayId" control={control}
defaultValue="" name="gatewayId"
render={({ field: { value, onChange }, fieldState: { error } }) => ( defaultValue=""
<FormControl render={({ field: { value, onChange }, fieldState: { error } }) => (
isError={Boolean(error?.message)} <FormControl
errorText={error?.message} isError={Boolean(error?.message)}
label="Gateway" errorText={error?.message}
> label="Gateway"
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
> >
<div> <Tooltip
<Select isDisabled={isAllowed}
isDisabled={!isAllowed} content="Restricted access. You don't have permission to attach gateways to resources."
value={value as string} >
onValueChange={onChange} <div>
className="w-full border border-mineshaft-500" <Select
dropdownContainerClassName="max-w-none" isDisabled={!isAllowed}
isLoading={isGatewaysLoading} value={value as string}
placeholder="Default: Internet Gateway" onValueChange={onChange}
position="popper" className="w-full border border-mineshaft-500"
> dropdownContainerClassName="max-w-none"
<SelectItem isLoading={isGatewaysLoading}
value={null as unknown as string} placeholder="Default: Internet Gateway"
onClick={() => onChange(undefined)} position="popper"
> >
Internet Gateway <SelectItem
</SelectItem> value={null as unknown as string}
{gateways?.map((el) => ( onClick={() => onChange(undefined)}
<SelectItem value={el.id} key={el.id}> >
{el.name} Internet Gateway
</SelectItem> </SelectItem>
))} {gateways?.map((el) => (
</Select> <SelectItem value={el.id} key={el.id}>
</div> {el.name}
</Tooltip> </SelectItem>
</FormControl> ))}
)} </Select>
/> </div>
)} </Tooltip>
</OrgPermissionCan> </FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller <Controller
name="method" name="method"
control={control} control={control}