Merge pull request #4559 from Infisical/ENG-3733

fix(app-connections): Migrate Heroku OAuth Connection to new OAuth connection format
This commit is contained in:
Scott Wilson
2025-09-23 09:27:19 -07:00
committed by GitHub
13 changed files with 146 additions and 32 deletions
+5 -1
View File
@@ -122,7 +122,7 @@ INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET=
#gcp app connection #gcp app connection
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL= INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
# azure app connection # azure app connections
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID= INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID=
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET= INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET=
@@ -135,6 +135,10 @@ INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET=
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID= INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID=
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET= INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET=
# heroku app connection
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID=
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET=
# datadog # datadog
SHOULD_USE_DATADOG_TRACER= SHOULD_USE_DATADOG_TRACER=
DATADOG_PROFILING_ENABLED= DATADOG_PROFILING_ENABLED=
+21 -1
View File
@@ -323,6 +323,10 @@ const envSchema = z
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID: zpStr(z.string().optional()),
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET: zpStr(z.string().optional()),
// Heroku App Connection
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID: zpStr(z.string().optional()),
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET: zpStr(z.string().optional()),
// datadog // datadog
SHOULD_USE_DATADOG_TRACER: zodStrBool.default("false"), SHOULD_USE_DATADOG_TRACER: zodStrBool.default("false"),
DATADOG_PROFILING_ENABLED: zodStrBool.default("false"), DATADOG_PROFILING_ENABLED: zodStrBool.default("false"),
@@ -433,7 +437,10 @@ const envSchema = z
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID:
data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID, data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID,
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET:
data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID: data.INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID || data.CLIENT_ID_HEROKU,
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET:
data.INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET || data.CLIENT_SECRET_HEROKU
})); }));
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>; export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
@@ -736,6 +743,19 @@ export const overwriteSchema: {
description: "The Client Secret of your GCP OAuth2 application." description: "The Client Secret of your GCP OAuth2 application."
} }
] ]
},
heroku: {
name: "Heroku",
fields: [
{
key: "INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID",
description: "The Client ID of your Heroku application."
},
{
key: "INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET",
description: "The Client Secret of your Heroku application."
}
]
} }
}; };
@@ -22,13 +22,13 @@ interface HerokuOAuthTokenResponse {
} }
export const getHerokuConnectionListItem = () => { export const getHerokuConnectionListItem = () => {
const { CLIENT_ID_HEROKU } = getConfig(); const { INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID } = getConfig();
return { return {
name: "Heroku" as const, name: "Heroku" as const,
app: AppConnection.Heroku as const, app: AppConnection.Heroku as const,
methods: Object.values(HerokuConnectionMethod) as [HerokuConnectionMethod.AuthToken, HerokuConnectionMethod.OAuth], methods: Object.values(HerokuConnectionMethod) as [HerokuConnectionMethod.AuthToken, HerokuConnectionMethod.OAuth],
oauthClientId: CLIENT_ID_HEROKU oauthClientId: INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID
}; };
}; };
@@ -40,12 +40,12 @@ export const refreshHerokuToken = async (
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">, appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey"> kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
): Promise<string> => { ): Promise<string> => {
const { CLIENT_SECRET_HEROKU } = getConfig(); const { INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET } = getConfig();
const payload = { const payload = {
grant_type: "refresh_token", grant_type: "refresh_token",
refresh_token: refreshToken, refresh_token: refreshToken,
client_secret: CLIENT_SECRET_HEROKU client_secret: INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET
}; };
const { data } = await request.post<{ access_token: string; expires_in: number }>( const { data } = await request.post<{ access_token: string; expires_in: number }>(
@@ -75,7 +75,7 @@ export const refreshHerokuToken = async (
}; };
export const exchangeHerokuOAuthCode = async (code: string): Promise<HerokuOAuthTokenResponse> => { export const exchangeHerokuOAuthCode = async (code: string): Promise<HerokuOAuthTokenResponse> => {
const { CLIENT_SECRET_HEROKU } = getConfig(); const { INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET } = getConfig();
try { try {
const response = await request.post<HerokuOAuthTokenResponse>( const response = await request.post<HerokuOAuthTokenResponse>(
@@ -83,7 +83,7 @@ export const exchangeHerokuOAuthCode = async (code: string): Promise<HerokuOAuth
{ {
grant_type: "authorization_code", grant_type: "authorization_code",
code, code,
client_secret: CLIENT_SECRET_HEROKU client_secret: INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET
}, },
{ {
headers: { headers: {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 425 KiB

+3 -3
View File
@@ -24,7 +24,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
![Heroku config applications](/images/integrations/heroku/integrations-heroku-config-applications.png) ![Heroku config applications](/images/integrations/heroku/integrations-heroku-config-applications.png)
![Heroku config new app](/images/integrations/heroku/integrations-heroku-config-new-app.png) ![Heroku config new app](/images/integrations/heroku/integrations-heroku-config-new-app.png)
Create the API client. As part of the form, set the **OAuth callback URL** to `https://your-domain.com/integrations/heroku/oauth2/callback`. Create the API client. As part of the form, set the **OAuth callback URL** to `https://your-domain.com/organization/app-connections/heroku/oauth/callback`.
<Tip> <Tip>
The domain you defined in the OAuth callback URL should be equivalent to the `SITE_URL` configured in your Infisical instance. The domain you defined in the OAuth callback URL should be equivalent to the `SITE_URL` configured in your Infisical instance.
@@ -39,8 +39,8 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
Back in your Infisical instance, add two new environment variables for the credentials of your Heroku API client: Back in your Infisical instance, add two new environment variables for the credentials of your Heroku API client:
- `CLIENT_ID_HEROKU`: The **Client ID** of your Heroku API client. - `INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID`: The **Client ID** of your Heroku API client.
- `CLIENT_SECRET_HEROKU`: The **Client Secret** of your Heroku API client. - `INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET`: The **Client Secret** of your Heroku API client.
Once added, restart your Infisical instance and use the Heroku Connection. Once added, restart your Infisical instance and use the Heroku Connection.
</Step> </Step>
@@ -672,6 +672,16 @@ You can configure third-party app connections for re-use across Infisical Projec
</Accordion> </Accordion>
<Accordion title="Heroku OAuth Connection">
<ParamField query="INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID" type="string" default="none" optional>
The Application ID of your Heroku OAuth application.
</ParamField>
<ParamField query="INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET" type="string" default="none" optional>
The Secret of your Heroku OAuth application.
</ParamField>
</Accordion>
## Native Secret Integrations ## Native Secret Integrations
To help you sync secrets from Infisical to services such as Github and Gitlab, Infisical provides native integrations out of the box. To help you sync secrets from Infisical to services such as Github and Gitlab, Infisical provides native integrations out of the box.
@@ -142,7 +142,7 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => {
case AppConnection.OnePass: case AppConnection.OnePass:
return <OnePassConnectionForm onSubmit={onSubmit} />; return <OnePassConnectionForm onSubmit={onSubmit} />;
case AppConnection.Heroku: case AppConnection.Heroku:
return <HerokuConnectionForm onSubmit={onSubmit} />; return <HerokuConnectionForm onSubmit={onSubmit} projectId={projectId} />;
case AppConnection.Render: case AppConnection.Render:
return <RenderConnectionForm onSubmit={onSubmit} />; return <RenderConnectionForm onSubmit={onSubmit} />;
case AppConnection.Flyio: case AppConnection.Flyio:
@@ -285,7 +285,13 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
case AppConnection.OnePass: case AppConnection.OnePass:
return <OnePassConnectionForm onSubmit={onSubmit} appConnection={appConnection} />; return <OnePassConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Heroku: case AppConnection.Heroku:
return <HerokuConnectionForm onSubmit={onSubmit} appConnection={appConnection} />; return (
<HerokuConnectionForm
onSubmit={onSubmit}
appConnection={appConnection}
projectId={appConnection.projectId}
/>
);
case AppConnection.Render: case AppConnection.Render:
return <RenderConnectionForm onSubmit={onSubmit} appConnection={appConnection} />; return <RenderConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Flyio: case AppConnection.Flyio:
@@ -39,7 +39,7 @@ import {
} from "@app/hooks/api/appConnections"; } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { GithubFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { GitHubFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
import { import {
genericAppConnectionFieldsSchema, genericAppConnectionFieldsSchema,
GenericAppConnectionsFields GenericAppConnectionsFields
@@ -118,7 +118,7 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
connectionId: appConnection?.id, connectionId: appConnection?.id,
projectId, projectId,
returnUrl returnUrl
} as GithubFormData) } as GitHubFormData)
); );
const githubHost = const githubHost =
@@ -19,7 +19,7 @@ import {
} from "@app/hooks/api/appConnections"; } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { GithubRadarFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { GitHubRadarFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
import { import {
genericAppConnectionFieldsSchema, genericAppConnectionFieldsSchema,
GenericAppConnectionsFields GenericAppConnectionsFields
@@ -76,7 +76,7 @@ export const GitHubRadarConnectionForm = ({ appConnection, projectId }: Props) =
connectionId: appConnection?.id, connectionId: appConnection?.id,
projectId, projectId,
returnUrl returnUrl
} as GithubRadarFormData) } as GitHubRadarFormData)
); );
switch (formData.method) { switch (formData.method) {
@@ -15,7 +15,11 @@ import {
Select, Select,
SelectItem SelectItem
} from "@app/components/v2"; } from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; import {
APP_CONNECTION_MAP,
getAppConnectionMethodDetails,
useGetAppConnectionOauthReturnUrl
} from "@app/helpers/appConnections";
import { isInfisicalCloud } from "@app/helpers/platform"; import { isInfisicalCloud } from "@app/helpers/platform";
import { useGetAppConnectionOption } from "@app/hooks/api/appConnections"; import { useGetAppConnectionOption } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -32,6 +36,7 @@ import {
type Props = { type Props = {
appConnection?: THerokuConnection; appConnection?: THerokuConnection;
onSubmit: (formData: FormData) => Promise<void>; onSubmit: (formData: FormData) => Promise<void>;
projectId: string | undefined | null;
}; };
const formSchema = z.discriminatedUnion("method", [ const formSchema = z.discriminatedUnion("method", [
@@ -53,10 +58,12 @@ const formSchema = z.discriminatedUnion("method", [
type FormData = z.infer<typeof formSchema>; type FormData = z.infer<typeof formSchema>;
export const HerokuConnectionForm = ({ appConnection, onSubmit: formSubmit }: Props) => { export const HerokuConnectionForm = ({ appConnection, onSubmit: formSubmit, projectId }: Props) => {
const isUpdate = Boolean(appConnection); const isUpdate = Boolean(appConnection);
const [isRedirecting, setIsRedirecting] = useState(false); const [isRedirecting, setIsRedirecting] = useState(false);
const returnUrl = useGetAppConnectionOauthReturnUrl();
const { const {
option: { oauthClientId }, option: { oauthClientId },
isLoading isLoading
@@ -110,7 +117,8 @@ export const HerokuConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
JSON.stringify({ JSON.stringify({
...formData, ...formData,
connectionId: appConnection?.id, connectionId: appConnection?.id,
isUpdate returnUrl,
projectId
}) })
); );
@@ -12,6 +12,7 @@ import {
AzureKeyVaultConnectionMethod, AzureKeyVaultConnectionMethod,
GitHubConnectionMethod, GitHubConnectionMethod,
GitLabConnectionMethod, GitLabConnectionMethod,
HerokuConnectionMethod,
TAppConnection, TAppConnection,
useCreateAppConnection, useCreateAppConnection,
useUpdateAppConnection useUpdateAppConnection
@@ -28,7 +29,8 @@ const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData", [AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData", [AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData",
[AppConnection.AzureClientSecrets]: "azureClientSecretsConnectionFormData", [AppConnection.AzureClientSecrets]: "azureClientSecretsConnectionFormData",
[AppConnection.AzureDevOps]: "azureDevOpsConnectionFormData" [AppConnection.AzureDevOps]: "azureDevOpsConnectionFormData",
[AppConnection.Heroku]: "herokuConnectionFormData"
}; };
export const OAuthCallbackPage = () => { export const OAuthCallbackPage = () => {
@@ -83,7 +85,7 @@ export const OAuthCallbackPage = () => {
} }
}; };
const handleGitlab = useCallback(async () => { const handleGitLab = useCallback(async () => {
const formData = getFormData(AppConnection.GitLab); const formData = getFormData(AppConnection.GitLab);
if (formData === null) return null; if (formData === null) return null;
@@ -375,7 +377,7 @@ export const OAuthCallbackPage = () => {
}; };
}, []); }, []);
const handleGithub = useCallback(async () => { const handleGitHub = useCallback(async () => {
const formData = getFormData(AppConnection.GitHub); const formData = getFormData(AppConnection.GitHub);
if (formData === null) return null; if (formData === null) return null;
@@ -463,7 +465,7 @@ export const OAuthCallbackPage = () => {
}; };
}, []); }, []);
const handleGithubRadar = useCallback(async () => { const handleGitHubRadar = useCallback(async () => {
const formData = getFormData(AppConnection.GitHubRadar); const formData = getFormData(AppConnection.GitHubRadar);
if (formData === null) return null; if (formData === null) return null;
@@ -520,6 +522,61 @@ export const OAuthCallbackPage = () => {
}; };
}, []); }, []);
const handleHeroku = useCallback(async () => {
const formData = getFormData(AppConnection.Heroku);
if (formData === null) return null;
clearState(AppConnection.Heroku);
const { connectionId, name, description, returnUrl, projectId } = formData;
let connection: TAppConnection;
try {
if (connectionId) {
connection = await updateAppConnection.mutateAsync({
app: AppConnection.Heroku,
connectionId,
credentials: {
code: code as string
}
});
} else {
connection = await createAppConnection.mutateAsync({
app: AppConnection.Heroku,
name,
description,
method: HerokuConnectionMethod.OAuth,
projectId,
credentials: {
code: code as string
}
});
}
} catch (e: any) {
createNotification({
title: `Failed to ${connectionId ? "update" : "add"} Heroku Connection`,
text: e.message,
type: "error"
});
navigate({
to: returnUrl,
params: {
projectId
}
});
return null;
}
return {
connectionId,
returnUrl,
appConnectionName: formData.app,
projectId,
connection
};
}, []);
// Ensure that the localstorage is ready for use, to avoid the form data being malformed // Ensure that the localstorage is ready for use, to avoid the form data being malformed
useEffect(() => { useEffect(() => {
if (!isReady) { if (!isReady) {
@@ -540,11 +597,11 @@ export const OAuthCallbackPage = () => {
} | null = null; } | null = null;
if (appConnection === AppConnection.GitHub) { if (appConnection === AppConnection.GitHub) {
data = await handleGithub(); data = await handleGitHub();
} else if (appConnection === AppConnection.GitHubRadar) { } else if (appConnection === AppConnection.GitHubRadar) {
data = await handleGithubRadar(); data = await handleGitHubRadar();
} else if (appConnection === AppConnection.GitLab) { } else if (appConnection === AppConnection.GitLab) {
data = await handleGitlab(); data = await handleGitLab();
} else if (appConnection === AppConnection.AzureKeyVault) { } else if (appConnection === AppConnection.AzureKeyVault) {
data = await handleAzureKeyVault(); data = await handleAzureKeyVault();
} else if (appConnection === AppConnection.AzureAppConfiguration) { } else if (appConnection === AppConnection.AzureAppConfiguration) {
@@ -553,6 +610,8 @@ export const OAuthCallbackPage = () => {
data = await handleAzureClientSecrets(); data = await handleAzureClientSecrets();
} else if (appConnection === AppConnection.AzureDevOps) { } else if (appConnection === AppConnection.AzureDevOps) {
data = await handleAzureDevOps(); data = await handleAzureDevOps();
} else if (appConnection === AppConnection.Heroku) {
data = await handleHeroku();
} }
if (data) { if (data) {
@@ -6,7 +6,8 @@ import {
TAzureKeyVaultConnection, TAzureKeyVaultConnection,
TGitHubConnection, TGitHubConnection,
TGitHubRadarConnection, TGitHubRadarConnection,
TGitLabConnection TGitLabConnection,
THerokuConnection
} from "@app/hooks/api/appConnections"; } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -17,10 +18,10 @@ type BaseFormData = {
projectId: string; projectId: string;
}; };
export type GithubFormData = BaseFormData & export type GitHubFormData = BaseFormData &
Pick<TGitHubConnection, "name" | "method" | "description" | "gatewayId" | "credentials">; Pick<TGitHubConnection, "name" | "method" | "description" | "gatewayId" | "credentials">;
export type GithubRadarFormData = BaseFormData & export type GitHubRadarFormData = BaseFormData &
Pick<TGitHubRadarConnection, "name" | "method" | "description">; Pick<TGitHubRadarConnection, "name" | "method" | "description">;
export type GitLabFormData = BaseFormData & export type GitLabFormData = BaseFormData &
@@ -51,9 +52,12 @@ export type AzureDevOpsFormData = BaseFormData &
Pick<TAzureDevOpsConnection, "name" | "method" | "description"> & Pick<TAzureDevOpsConnection, "name" | "method" | "description"> &
(Pick<OAuthCredentials, "tenantId" | "orgName"> | Pick<AccessTokenCredentials, "orgName">); (Pick<OAuthCredentials, "tenantId" | "orgName"> | Pick<AccessTokenCredentials, "orgName">);
export type HerokuFormData = BaseFormData &
Pick<THerokuConnection, "name" | "method" | "description">;
export type FormDataMap = { export type FormDataMap = {
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub }; [AppConnection.GitHub]: GitHubFormData & { app: AppConnection.GitHub };
[AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar }; [AppConnection.GitHubRadar]: GitHubRadarFormData & { app: AppConnection.GitHubRadar };
[AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab }; [AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab };
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault }; [AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & { [AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
@@ -65,4 +69,7 @@ export type FormDataMap = {
[AppConnection.AzureDevOps]: AzureDevOpsFormData & { [AppConnection.AzureDevOps]: AzureDevOpsFormData & {
app: AppConnection.AzureDevOps; app: AppConnection.AzureDevOps;
}; };
[AppConnection.Heroku]: HerokuFormData & {
app: AppConnection.Heroku;
};
}; };