fix: migrate heroku oauth connection to new oauth connection format

This commit is contained in:
Scott Wilson
2025-09-19 09:51:56 -07:00
parent 3c9ad328a9
commit 2786d49cad
13 changed files with 138 additions and 27 deletions

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=

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"),
@@ -736,6 +740,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."
}
]
} }
}; };

View File

@@ -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
}; };
}; };

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

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>

View File

@@ -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 GitLab 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.

View File

@@ -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:

View File

@@ -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 =

View File

@@ -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) {

View File

@@ -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
}) })
); );

View File

@@ -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) {

View File

@@ -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;
};
}; };