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
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_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_SECRET=
# heroku app connection
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID=
INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_SECRET=
# datadog
SHOULD_USE_DATADOG_TRACER=
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_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
SHOULD_USE_DATADOG_TRACER: 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."
}
]
},
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 = () => {
const { CLIENT_ID_HEROKU } = getConfig();
const { INF_APP_CONNECTION_HEROKU_OAUTH_CLIENT_ID } = getConfig();
return {
name: "Heroku" as const,
app: AppConnection.Heroku as const,
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 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>
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:
- `CLIENT_ID_HEROKU`: 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_ID`: The **Client ID** 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.
</Step>

View File

@@ -672,6 +672,16 @@ You can configure third-party app connections for re-use across Infisical Projec
</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
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:
return <OnePassConnectionForm onSubmit={onSubmit} />;
case AppConnection.Heroku:
return <HerokuConnectionForm onSubmit={onSubmit} />;
return <HerokuConnectionForm onSubmit={onSubmit} projectId={projectId} />;
case AppConnection.Render:
return <RenderConnectionForm onSubmit={onSubmit} />;
case AppConnection.Flyio:
@@ -285,7 +285,13 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
case AppConnection.OnePass:
return <OnePassConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Heroku:
return <HerokuConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
return (
<HerokuConnectionForm
onSubmit={onSubmit}
appConnection={appConnection}
projectId={appConnection.projectId}
/>
);
case AppConnection.Render:
return <RenderConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Flyio:

View File

@@ -39,7 +39,7 @@ import {
} from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { GithubFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
import { GitHubFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
@@ -118,7 +118,7 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
connectionId: appConnection?.id,
projectId,
returnUrl
} as GithubFormData)
} as GitHubFormData)
);
const githubHost =

View File

@@ -19,7 +19,7 @@ import {
} from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { GithubRadarFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
import { GitHubRadarFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
@@ -76,7 +76,7 @@ export const GitHubRadarConnectionForm = ({ appConnection, projectId }: Props) =
connectionId: appConnection?.id,
projectId,
returnUrl
} as GithubRadarFormData)
} as GitHubRadarFormData)
);
switch (formData.method) {

View File

@@ -15,7 +15,11 @@ import {
Select,
SelectItem
} 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 { useGetAppConnectionOption } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -32,6 +36,7 @@ import {
type Props = {
appConnection?: THerokuConnection;
onSubmit: (formData: FormData) => Promise<void>;
projectId: string | undefined | null;
};
const formSchema = z.discriminatedUnion("method", [
@@ -53,10 +58,12 @@ const formSchema = z.discriminatedUnion("method", [
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 [isRedirecting, setIsRedirecting] = useState(false);
const returnUrl = useGetAppConnectionOauthReturnUrl();
const {
option: { oauthClientId },
isLoading
@@ -110,7 +117,8 @@ export const HerokuConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
JSON.stringify({
...formData,
connectionId: appConnection?.id,
isUpdate
returnUrl,
projectId
})
);

View File

@@ -12,6 +12,7 @@ import {
AzureKeyVaultConnectionMethod,
GitHubConnectionMethod,
GitLabConnectionMethod,
HerokuConnectionMethod,
TAppConnection,
useCreateAppConnection,
useUpdateAppConnection
@@ -28,7 +29,8 @@ const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData",
[AppConnection.AzureClientSecrets]: "azureClientSecretsConnectionFormData",
[AppConnection.AzureDevOps]: "azureDevOpsConnectionFormData"
[AppConnection.AzureDevOps]: "azureDevOpsConnectionFormData",
[AppConnection.Heroku]: "herokuConnectionFormData"
};
export const OAuthCallbackPage = () => {
@@ -83,7 +85,7 @@ export const OAuthCallbackPage = () => {
}
};
const handleGitlab = useCallback(async () => {
const handleGitLab = useCallback(async () => {
const formData = getFormData(AppConnection.GitLab);
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);
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);
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
useEffect(() => {
if (!isReady) {
@@ -540,11 +597,11 @@ export const OAuthCallbackPage = () => {
} | null = null;
if (appConnection === AppConnection.GitHub) {
data = await handleGithub();
data = await handleGitHub();
} else if (appConnection === AppConnection.GitHubRadar) {
data = await handleGithubRadar();
data = await handleGitHubRadar();
} else if (appConnection === AppConnection.GitLab) {
data = await handleGitlab();
data = await handleGitLab();
} else if (appConnection === AppConnection.AzureKeyVault) {
data = await handleAzureKeyVault();
} else if (appConnection === AppConnection.AzureAppConfiguration) {
@@ -553,6 +610,8 @@ export const OAuthCallbackPage = () => {
data = await handleAzureClientSecrets();
} else if (appConnection === AppConnection.AzureDevOps) {
data = await handleAzureDevOps();
} else if (appConnection === AppConnection.Heroku) {
data = await handleHeroku();
}
if (data) {

View File

@@ -6,7 +6,8 @@ import {
TAzureKeyVaultConnection,
TGitHubConnection,
TGitHubRadarConnection,
TGitLabConnection
TGitLabConnection,
THerokuConnection
} from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -17,10 +18,10 @@ type BaseFormData = {
projectId: string;
};
export type GithubFormData = BaseFormData &
export type GitHubFormData = BaseFormData &
Pick<TGitHubConnection, "name" | "method" | "description" | "gatewayId" | "credentials">;
export type GithubRadarFormData = BaseFormData &
export type GitHubRadarFormData = BaseFormData &
Pick<TGitHubRadarConnection, "name" | "method" | "description">;
export type GitLabFormData = BaseFormData &
@@ -51,9 +52,12 @@ export type AzureDevOpsFormData = BaseFormData &
Pick<TAzureDevOpsConnection, "name" | "method" | "description"> &
(Pick<OAuthCredentials, "tenantId" | "orgName"> | Pick<AccessTokenCredentials, "orgName">);
export type HerokuFormData = BaseFormData &
Pick<THerokuConnection, "name" | "method" | "description">;
export type FormDataMap = {
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
[AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar };
[AppConnection.GitHub]: GitHubFormData & { app: AppConnection.GitHub };
[AppConnection.GitHubRadar]: GitHubRadarFormData & { app: AppConnection.GitHubRadar };
[AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab };
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
@@ -65,4 +69,7 @@ export type FormDataMap = {
[AppConnection.AzureDevOps]: AzureDevOpsFormData & {
app: AppConnection.AzureDevOps;
};
[AppConnection.Heroku]: HerokuFormData & {
app: AppConnection.Heroku;
};
};