deconflict merge

This commit is contained in:
Scott Wilson
2025-06-20 12:33:37 -07:00
137 changed files with 2735 additions and 183 deletions

View File

@@ -50,6 +50,8 @@ export const initDbConnection = ({
}
: false
},
// https://knexjs.org/guide/#pool
pool: { min: 0, max: 10 },
migrations: {
tableName: "infisical_migrations"
}
@@ -70,7 +72,8 @@ export const initDbConnection = ({
},
migrations: {
tableName: "infisical_migrations"
}
},
pool: { min: 0, max: 10 }
});
});

View File

@@ -128,11 +128,21 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
const username = generateUsername(usernameTemplate, identity);
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }];
if (providerInputs.tags && Array.isArray(providerInputs.tags)) {
const additionalTags = providerInputs.tags.map((tag) => ({
Key: tag.key,
Value: tag.value
}));
awsTags.push(...additionalTags);
}
const createUserRes = await client.send(
new CreateUserCommand({
Path: awsPath,
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }],
Tags: awsTags,
UserName: username
})
);

View File

@@ -0,0 +1,133 @@
import axios from "axios";
import * as jwt from "jsonwebtoken";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { DynamicSecretGithubSchema, TDynamicProviderFns } from "./models";
interface GitHubInstallationTokenResponse {
token: string;
expires_at: string; // ISO 8601 timestamp e.g., "2024-01-15T12:00:00Z"
permissions?: Record<string, string>;
repository_selection?: string;
}
interface TGithubProviderInputs {
appId: number;
installationId: number;
privateKey: string;
}
export const GithubProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretGithubSchema.parseAsync(inputs);
return providerInputs;
};
const $generateGitHubInstallationAccessToken = async (
credentials: TGithubProviderInputs
): Promise<GitHubInstallationTokenResponse> => {
const { appId, installationId, privateKey } = credentials;
const nowInSeconds = Math.floor(Date.now() / 1000);
const jwtPayload = {
iat: nowInSeconds - 5,
exp: nowInSeconds + 60,
iss: String(appId)
};
let appJwt: string;
try {
appJwt = jwt.sign(jwtPayload, privateKey, { algorithm: "RS256" });
} catch (error) {
let message = "Failed to sign JWT.";
if (error instanceof jwt.JsonWebTokenError) {
message += ` JsonWebTokenError: ${error.message}`;
}
throw new InternalServerError({
message
});
}
const tokenUrl = `${IntegrationUrls.GITHUB_API_URL}/app/installations/${String(installationId)}/access_tokens`;
try {
const response = await axios.post<GitHubInstallationTokenResponse>(tokenUrl, undefined, {
headers: {
Authorization: `Bearer ${appJwt}`,
Accept: "application/vnd.github.v3+json",
"X-GitHub-Api-Version": "2022-11-28"
}
});
if (response.status === 201 && response.data.token) {
return response.data; // Includes token, expires_at, permissions, repository_selection
}
throw new InternalServerError({
message: `GitHub API responded with unexpected status ${response.status}: ${JSON.stringify(response.data)}`
});
} catch (error) {
let message = "Failed to fetch GitHub installation access token.";
if (axios.isAxiosError(error) && error.response) {
const githubErrorMsg =
(error.response.data as { message?: string })?.message || JSON.stringify(error.response.data);
message += ` GitHub API Error: ${error.response.status} - ${githubErrorMsg}`;
// Classify as BadRequestError for auth-related issues (401, 403, 404) which might be due to user input
if ([401, 403, 404].includes(error.response.status)) {
throw new BadRequestError({ message });
}
}
throw new InternalServerError({ message });
}
};
const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs);
await $generateGitHubInstallationAccessToken(providerInputs);
return true;
};
const create = async (data: { inputs: unknown }) => {
const { inputs } = data;
const providerInputs = await validateProviderInputs(inputs);
const ghTokenData = await $generateGitHubInstallationAccessToken(providerInputs);
const entityId = alphaNumericNanoId(32);
return {
entityId,
data: {
TOKEN: ghTokenData.token,
EXPIRES_AT: ghTokenData.expires_at,
PERMISSIONS: ghTokenData.permissions,
REPOSITORY_SELECTION: ghTokenData.repository_selection
}
};
};
const revoke = async () => {
// GitHub installation tokens cannot be revoked.
throw new BadRequestError({
message:
"Github dynamic secret does not support revocation because GitHub itself cannot revoke installation tokens"
});
};
const renew = async () => {
// No renewal
throw new BadRequestError({ message: "Github dynamic secret does not support renewal" });
};
return {
validateProviderInputs,
validateConnection,
create,
revoke,
renew
};
};

View File

@@ -7,6 +7,7 @@ import { AzureEntraIDProvider } from "./azure-entra-id";
import { CassandraProvider } from "./cassandra";
import { ElasticSearchProvider } from "./elastic-search";
import { GcpIamProvider } from "./gcp-iam";
import { GithubProvider } from "./github";
import { KubernetesProvider } from "./kubernetes";
import { LdapProvider } from "./ldap";
import { DynamicSecretProviders, TDynamicProviderFns } from "./models";
@@ -44,5 +45,6 @@ export const buildDynamicSecretProviders = ({
[DynamicSecretProviders.SapAse]: SapAseProvider(),
[DynamicSecretProviders.Kubernetes]: KubernetesProvider({ gatewayService }),
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService }),
[DynamicSecretProviders.GcpIam]: GcpIamProvider()
[DynamicSecretProviders.GcpIam]: GcpIamProvider(),
[DynamicSecretProviders.Github]: GithubProvider()
});

View File

@@ -2,6 +2,7 @@ import RE2 from "re2";
import { z } from "zod";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
@@ -207,7 +208,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: z.string().trim().optional(),
policyArns: z.string().trim().optional()
policyArns: z.string().trim().optional(),
tags: ResourceMetadataSchema.optional()
}),
z.object({
method: z.literal(AwsIamAuthType.AssumeRole),
@@ -217,7 +219,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: z.string().trim().optional(),
policyArns: z.string().trim().optional()
policyArns: z.string().trim().optional(),
tags: ResourceMetadataSchema.optional()
})
])
);
@@ -474,6 +477,23 @@ export const DynamicSecretGcpIamSchema = z.object({
serviceAccountEmail: z.string().email().trim().min(1, "Service account email required").max(128)
});
export const DynamicSecretGithubSchema = z.object({
appId: z.number().min(1).describe("The ID of your GitHub App."),
installationId: z.number().min(1).describe("The ID of the GitHub App installation."),
privateKey: z
.string()
.trim()
.min(1)
.refine(
(val) =>
new RE2(
/^-----BEGIN(?:(?: RSA| PGP| ENCRYPTED)? PRIVATE KEY)-----\s*[\s\S]*?-----END(?:(?: RSA| PGP| ENCRYPTED)? PRIVATE KEY)-----$/
).test(val),
"Invalid PEM format for private key"
)
.describe("The private key generated for your GitHub App.")
});
export enum DynamicSecretProviders {
SqlDatabase = "sql-database",
Cassandra = "cassandra",
@@ -492,7 +512,8 @@ export enum DynamicSecretProviders {
SapAse = "sap-ase",
Kubernetes = "kubernetes",
Vertica = "vertica",
GcpIam = "gcp-iam"
GcpIam = "gcp-iam",
Github = "github"
}
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
@@ -513,7 +534,8 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Kubernetes), inputs: DynamicSecretKubernetesSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Vertica), inputs: DynamicSecretVerticaSchema }),
z.object({ type: z.literal(DynamicSecretProviders.GcpIam), inputs: DynamicSecretGcpIamSchema })
z.object({ type: z.literal(DynamicSecretProviders.GcpIam), inputs: DynamicSecretGcpIamSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Github), inputs: DynamicSecretGithubSchema })
]);
export type TDynamicProviderFns = {

View File

@@ -2390,6 +2390,10 @@ export const SecretSyncs = {
ONEPASS: {
vaultId: "The ID of the 1Password vault to sync secrets to."
},
HEROKU: {
app: "The ID of the Heroku app to sync secrets to.",
appName: "The name of the Heroku app to sync secrets to."
},
RENDER: {
serviceId: "The ID of the Render service to sync secrets to.",
scope: "The Render scope that secrets should be synced to.",

View File

@@ -101,9 +101,9 @@ const envSchema = z
LOOPS_API_KEY: zpStr(z.string().optional()),
// jwt options
AUTH_SECRET: zpStr(z.string()).default(process.env.JWT_AUTH_SECRET), // for those still using old JWT_AUTH_SECRET
JWT_AUTH_LIFETIME: zpStr(z.string().default("1d")),
JWT_AUTH_LIFETIME: zpStr(z.string().default("10d")),
JWT_SIGNUP_LIFETIME: zpStr(z.string().default("15m")),
JWT_REFRESH_LIFETIME: zpStr(z.string().default("14d")),
JWT_REFRESH_LIFETIME: zpStr(z.string().default("90d")),
JWT_INVITE_LIFETIME: zpStr(z.string().default("1d")),
JWT_MFA_LIFETIME: zpStr(z.string().default("5m")),
JWT_PROVIDER_AUTH_LIFETIME: zpStr(z.string().default("15m")),

View File

@@ -107,7 +107,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
server.addHook("onRequest", async (req) => {
const appCfg = getConfig();
if (req.url.includes(".well-known/est") || req.url.includes("/api/v3/auth/") || req.url === "/api/v1/auth/token") {
if (req.url.includes(".well-known/est") || req.url.includes("/api/v3/auth/")) {
return;
}

View File

@@ -50,6 +50,7 @@ import {
HCVaultConnectionListItemSchema,
SanitizedHCVaultConnectionSchema
} from "@app/services/app-connection/hc-vault";
import { HerokuConnectionListItemSchema, SanitizedHerokuConnectionSchema } from "@app/services/app-connection/heroku";
import {
HumanitecConnectionListItemSchema,
SanitizedHumanitecConnectionSchema
@@ -106,6 +107,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedOCIConnectionSchema.options,
...SanitizedOracleDBConnectionSchema.options,
...SanitizedOnePassConnectionSchema.options,
...SanitizedHerokuConnectionSchema.options,
...SanitizedRenderConnectionSchema.options,
...SanitizedFlyioConnectionSchema.options
]);
@@ -135,6 +137,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
OCIConnectionListItemSchema,
OracleDBConnectionListItemSchema,
OnePassConnectionListItemSchema,
HerokuConnectionListItemSchema,
RenderConnectionListItemSchema,
FlyioConnectionListItemSchema
]);

View File

@@ -0,0 +1,54 @@
import z from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateHerokuConnectionSchema,
SanitizedHerokuConnectionSchema,
THerokuApp,
UpdateHerokuConnectionSchema
} from "@app/services/app-connection/heroku";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerHerokuConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Heroku,
server,
sanitizedResponseSchema: SanitizedHerokuConnectionSchema,
createSchema: CreateHerokuConnectionSchema,
updateSchema: UpdateHerokuConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/apps`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const apps: THerokuApp[] = await server.services.appConnection.heroku.listApps(connectionId, req.permission);
return apps;
}
});
};

View File

@@ -16,6 +16,7 @@ import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router";
import { registerGitHubRadarConnectionRouter } from "./github-radar-connection-router";
import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
import { registerHerokuConnectionRouter } from "./heroku-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
@@ -55,6 +56,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.OCI]: registerOCIConnectionRouter,
[AppConnection.OracleDB]: registerOracleDBConnectionRouter,
[AppConnection.OnePass]: registerOnePassConnectionRouter,
[AppConnection.Heroku]: registerHerokuConnectionRouter,
[AppConnection.Render]: registerRenderConnectionRouter,
[AppConnection.Flyio]: registerFlyioConnectionRouter
};

View File

@@ -0,0 +1,13 @@
import { CreateHerokuSyncSchema, HerokuSyncSchema, UpdateHerokuSyncSchema } from "@app/services/secret-sync/heroku";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerHerokuSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Heroku,
server,
responseSchema: HerokuSyncSchema,
createSchema: CreateHerokuSyncSchema,
updateSchema: UpdateHerokuSyncSchema
});

View File

@@ -13,6 +13,7 @@ import { registerFlyioSyncRouter } from "./flyio-sync-router";
import { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router";
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
import { registerHerokuSyncRouter } from "./heroku-sync-router";
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
import { registerRenderSyncRouter } from "./render-sync-router";
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
@@ -40,6 +41,7 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.TeamCity]: registerTeamCitySyncRouter,
[SecretSync.OCIVault]: registerOCIVaultSyncRouter,
[SecretSync.OnePass]: registerOnePassSyncRouter,
[SecretSync.Heroku]: registerHerokuSyncRouter,
[SecretSync.Render]: registerRenderSyncRouter,
[SecretSync.Flyio]: registerFlyioSyncRouter
};

View File

@@ -27,6 +27,7 @@ import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-s
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
import { HerokuSyncListItemSchema, HerokuSyncSchema } from "@app/services/secret-sync/heroku";
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
import { RenderSyncListItemSchema, RenderSyncSchema } from "@app/services/secret-sync/render/render-sync-schemas";
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
@@ -52,6 +53,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
TeamCitySyncSchema,
OCIVaultSyncSchema,
OnePassSyncSchema,
HerokuSyncSchema,
RenderSyncSchema,
FlyioSyncSchema
]);
@@ -74,6 +76,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
TeamCitySyncListItemSchema,
OCIVaultSyncListItemSchema,
OnePassSyncListItemSchema,
HerokuSyncListItemSchema,
RenderSyncListItemSchema,
FlyioSyncListItemSchema
]);

View File

@@ -50,8 +50,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
200: z.object({
token: z.string(),
isMfaEnabled: z.boolean(),
mfaMethod: z.string().optional(),
refreshToken: z.string().optional()
mfaMethod: z.string().optional()
})
}
},
@@ -102,7 +101,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
maxAge: 0
});
return { token: tokens.access, isMfaEnabled: false, refreshToken: tokens.refresh };
return { token: tokens.access, isMfaEnabled: false };
}
});
@@ -130,8 +129,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
encryptedPrivateKey: z.string(),
iv: z.string(),
tag: z.string(),
token: z.string(),
refreshToken: z.string().optional()
token: z.string()
})
}
},
@@ -174,8 +172,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
tag: data.user.tag,
protectedKey: data.user.protectedKey || null,
protectedKeyIV: data.user.protectedKeyIV || null,
protectedKeyTag: data.user.protectedKeyTag || null,
refreshToken: data.token.refresh
protectedKeyTag: data.user.protectedKeyTag || null
} as const;
}
});

View File

@@ -23,6 +23,7 @@ export enum AppConnection {
OCI = "oci",
OracleDB = "oracledb",
OnePass = "1password",
Heroku = "heroku",
Render = "render",
Flyio = "flyio"
}

View File

@@ -69,6 +69,7 @@ import {
HCVaultConnectionMethod,
validateHCVaultConnectionCredentials
} from "./hc-vault";
import { getHerokuConnectionListItem, HerokuConnectionMethod, validateHerokuConnectionCredentials } from "./heroku";
import {
getHumanitecConnectionListItem,
HumanitecConnectionMethod,
@@ -125,6 +126,7 @@ export const listAppConnectionOptions = () => {
getOCIConnectionListItem(),
getOracleDBConnectionListItem(),
getOnePassConnectionListItem(),
getHerokuConnectionListItem(),
getRenderConnectionListItem(),
getFlyioConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
@@ -202,6 +204,7 @@ export const validateAppConnectionCredentials = async (
[AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OracleDB]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Heroku]: validateHerokuConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator
};
@@ -219,7 +222,10 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case AzureClientSecretsConnectionMethod.OAuth:
case GitHubConnectionMethod.OAuth:
case AzureDevOpsConnectionMethod.OAuth:
case HerokuConnectionMethod.OAuth:
return "OAuth";
case HerokuConnectionMethod.AuthToken:
return "Auth Token";
case AwsConnectionMethod.AccessKey:
case OCIConnectionMethod.AccessKey:
return "Access Key";
@@ -310,6 +316,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.OCI]: platformManagedCredentialsNotSupported,
[AppConnection.OracleDB]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.OnePass]: platformManagedCredentialsNotSupported,
[AppConnection.Heroku]: platformManagedCredentialsNotSupported,
[AppConnection.Render]: platformManagedCredentialsNotSupported,
[AppConnection.Flyio]: platformManagedCredentialsNotSupported
};

View File

@@ -25,6 +25,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.OCI]: "OCI",
[AppConnection.OracleDB]: "OracleDB",
[AppConnection.OnePass]: "1Password",
[AppConnection.Heroku]: "Heroku",
[AppConnection.Render]: "Render",
[AppConnection.Flyio]: "Fly.io"
};
@@ -54,6 +55,7 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.OracleDB]: AppConnectionPlanType.Enterprise,
[AppConnection.OnePass]: AppConnectionPlanType.Regular,
[AppConnection.MySql]: AppConnectionPlanType.Regular,
[AppConnection.Heroku]: AppConnectionPlanType.Regular,
[AppConnection.Render]: AppConnectionPlanType.Regular,
[AppConnection.Flyio]: AppConnectionPlanType.Regular
};

View File

@@ -58,6 +58,8 @@ import { githubConnectionService } from "./github/github-connection-service";
import { ValidateGitHubRadarConnectionCredentialsSchema } from "./github-radar";
import { ValidateHCVaultConnectionCredentialsSchema } from "./hc-vault";
import { hcVaultConnectionService } from "./hc-vault/hc-vault-connection-service";
import { ValidateHerokuConnectionCredentialsSchema } from "./heroku";
import { herokuConnectionService } from "./heroku/heroku-connection-service";
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
@@ -109,6 +111,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.OCI]: ValidateOCIConnectionCredentialsSchema,
[AppConnection.OracleDB]: ValidateOracleDBConnectionCredentialsSchema,
[AppConnection.OnePass]: ValidateOnePassConnectionCredentialsSchema,
[AppConnection.Heroku]: ValidateHerokuConnectionCredentialsSchema,
[AppConnection.Render]: ValidateRenderConnectionCredentialsSchema,
[AppConnection.Flyio]: ValidateFlyioConnectionCredentialsSchema
};
@@ -516,6 +519,7 @@ export const appConnectionServiceFactory = ({
teamcity: teamcityConnectionService(connectAppConnectionById),
oci: ociConnectionService(connectAppConnectionById, licenseService),
onepass: onePassConnectionService(connectAppConnectionById),
heroku: herokuConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
render: renderConnectionService(connectAppConnectionById),
flyio: flyioConnectionService(connectAppConnectionById)
};

View File

@@ -98,6 +98,12 @@ import {
THCVaultConnectionInput,
TValidateHCVaultConnectionCredentialsSchema
} from "./hc-vault";
import {
THerokuConnection,
THerokuConnectionConfig,
THerokuConnectionInput,
TValidateHerokuConnectionCredentialsSchema
} from "./heroku";
import {
THumanitecConnection,
THumanitecConnectionConfig,
@@ -173,6 +179,7 @@ export type TAppConnection = { id: string } & (
| TOCIConnection
| TOracleDBConnection
| TOnePassConnection
| THerokuConnection
| TRenderConnection
| TFlyioConnection
);
@@ -206,6 +213,7 @@ export type TAppConnectionInput = { id: string } & (
| TOCIConnectionInput
| TOracleDBConnectionInput
| TOnePassConnectionInput
| THerokuConnectionInput
| TRenderConnectionInput
| TFlyioConnectionInput
);
@@ -247,6 +255,7 @@ export type TAppConnectionConfig =
| TTeamCityConnectionConfig
| TOCIConnectionConfig
| TOnePassConnectionConfig
| THerokuConnectionConfig
| TRenderConnectionConfig
| TFlyioConnectionConfig;
@@ -275,6 +284,7 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateOCIConnectionCredentialsSchema
| TValidateOracleDBConnectionCredentialsSchema
| TValidateOnePassConnectionCredentialsSchema
| TValidateHerokuConnectionCredentialsSchema
| TValidateRenderConnectionCredentialsSchema
| TValidateFlyioConnectionCredentialsSchema;

View File

@@ -0,0 +1,4 @@
export enum HerokuConnectionMethod {
AuthToken = "auth-token",
OAuth = "oauth"
}

View File

@@ -0,0 +1,208 @@
import { AxiosError, AxiosResponse } from "axios";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TAppConnectionDALFactory } from "../app-connection-dal";
import { HerokuConnectionMethod } from "./heroku-connection-enums";
import { THerokuApp, THerokuConnection, THerokuConnectionConfig } from "./heroku-connection-types";
interface HerokuOAuthTokenResponse {
access_token: string;
expires_in: number;
refresh_token: string;
token_type: string;
user_id: string;
session_nonce: string;
}
export const getHerokuConnectionListItem = () => {
const { CLIENT_ID_HEROKU } = getConfig();
return {
name: "Heroku" as const,
app: AppConnection.Heroku as const,
methods: Object.values(HerokuConnectionMethod) as [HerokuConnectionMethod.AuthToken, HerokuConnectionMethod.OAuth],
oauthClientId: CLIENT_ID_HEROKU
};
};
export const refreshHerokuToken = async (
refreshToken: string,
appId: string,
orgId: string,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
): Promise<string> => {
const { CLIENT_SECRET_HEROKU } = getConfig();
const payload = {
grant_type: "refresh_token",
refresh_token: refreshToken,
client_secret: CLIENT_SECRET_HEROKU
};
const { data } = await request.post<{ access_token: string; expires_in: number }>(
IntegrationUrls.HEROKU_TOKEN_URL,
payload,
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
const encryptedCredentials = await encryptAppConnectionCredentials({
credentials: {
refreshToken,
authToken: data.access_token,
expiresAt: new Date(Date.now() + data.expires_in * 1000 - 60000)
},
orgId,
kmsService
});
await appConnectionDAL.updateById(appId, { encryptedCredentials });
return data.access_token;
};
export const exchangeHerokuOAuthCode = async (code: string): Promise<HerokuOAuthTokenResponse> => {
const { CLIENT_SECRET_HEROKU } = getConfig();
try {
const response = await request.post<HerokuOAuthTokenResponse>(
IntegrationUrls.HEROKU_TOKEN_URL,
{
grant_type: "authorization_code",
code,
client_secret: CLIENT_SECRET_HEROKU
},
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
if (!response.data) {
throw new InternalServerError({
message: "Failed to exchange OAuth code: Empty response"
});
}
return response.data;
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
message: `Failed to exchange OAuth code: ${error.response?.data?.message || error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to exchange OAuth code"
});
}
};
export const validateHerokuConnectionCredentials = async (config: THerokuConnectionConfig) => {
const { credentials: inputCredentials, method } = config;
let authToken: string;
let oauthData: HerokuOAuthTokenResponse | null = null;
if (method === HerokuConnectionMethod.OAuth && "code" in inputCredentials) {
oauthData = await exchangeHerokuOAuthCode(inputCredentials.code);
authToken = oauthData.access_token;
} else if (method === HerokuConnectionMethod.AuthToken && "authToken" in inputCredentials) {
authToken = inputCredentials.authToken;
} else {
throw new BadRequestError({
message: "Invalid credentials for the selected connection method"
});
}
let response: AxiosResponse<THerokuApp[]> | null = null;
try {
response = await request.get<THerokuApp[]>(`${IntegrationUrls.HEROKU_API_URL}/apps`, {
headers: {
Authorization: `Bearer ${authToken}`,
Accept: "application/vnd.heroku+json; version=3"
}
});
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
});
}
if (!response?.data) {
throw new InternalServerError({
message: "Failed to get apps: Response was empty"
});
}
if (method === HerokuConnectionMethod.OAuth && oauthData) {
return {
authToken,
refreshToken: oauthData.refresh_token,
expiresIn: oauthData.expires_in,
tokenType: oauthData.token_type,
userId: oauthData.user_id,
sessionNonce: oauthData.session_nonce
};
}
return inputCredentials;
};
export const listHerokuApps = async ({
appConnection,
appConnectionDAL,
kmsService
}: {
appConnection: THerokuConnection;
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
}): Promise<THerokuApp[]> => {
let authCredential = appConnection.credentials.authToken;
if (
appConnection.method === HerokuConnectionMethod.OAuth &&
appConnection.credentials.refreshToken &&
appConnection.credentials.expiresAt < new Date()
) {
authCredential = await refreshHerokuToken(
appConnection.credentials.refreshToken,
appConnection.id,
appConnection.orgId,
appConnectionDAL,
kmsService
);
}
const { data } = await request.get<THerokuApp[]>(`${IntegrationUrls.HEROKU_API_URL}/apps`, {
headers: {
Authorization: `Bearer ${authCredential}`,
Accept: "application/vnd.heroku+json; version=3"
}
});
if (!data) {
throw new InternalServerError({
message: "Failed to get apps: Response was empty"
});
}
return data.map((res) => ({ name: res.name, id: res.id }));
};

View File

@@ -0,0 +1,103 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { HerokuConnectionMethod } from "./heroku-connection-enums";
export const HerokuConnectionAuthTokenCredentialsSchema = z.object({
authToken: z.string().trim().min(1, "Auth Token required").startsWith("HRKU-", "Token must start with 'HRKU-")
});
export const HerokuConnectionOAuthCredentialsSchema = z.object({
code: z.string().trim().min(1, "OAuth code required")
});
export const HerokuConnectionOAuthOutputCredentialsSchema = z.object({
authToken: z.string().trim(),
refreshToken: z.string().trim(),
expiresAt: z.date()
});
// Schema for refresh token input during initial setup
export const HerokuConnectionRefreshTokenCredentialsSchema = z.object({
refreshToken: z.string().trim().min(1, "Refresh token required")
});
const BaseHerokuConnectionSchema = BaseAppConnectionSchema.extend({
app: z.literal(AppConnection.Heroku)
});
export const HerokuConnectionSchema = z.intersection(
BaseHerokuConnectionSchema,
z.discriminatedUnion("method", [
z.object({
method: z.literal(HerokuConnectionMethod.AuthToken),
credentials: HerokuConnectionAuthTokenCredentialsSchema
}),
z.object({
method: z.literal(HerokuConnectionMethod.OAuth),
credentials: HerokuConnectionOAuthOutputCredentialsSchema
})
])
);
export const SanitizedHerokuConnectionSchema = z.discriminatedUnion("method", [
BaseHerokuConnectionSchema.extend({
method: z.literal(HerokuConnectionMethod.AuthToken),
credentials: HerokuConnectionAuthTokenCredentialsSchema.pick({})
}),
BaseHerokuConnectionSchema.extend({
method: z.literal(HerokuConnectionMethod.OAuth),
credentials: HerokuConnectionOAuthOutputCredentialsSchema.pick({})
})
]);
export const ValidateHerokuConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z.literal(HerokuConnectionMethod.AuthToken).describe(AppConnections.CREATE(AppConnection.Heroku).method),
credentials: HerokuConnectionAuthTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Heroku).credentials
)
}),
z.object({
method: z.literal(HerokuConnectionMethod.OAuth).describe(AppConnections.CREATE(AppConnection.Heroku).method),
credentials: z
.union([
HerokuConnectionOAuthCredentialsSchema,
HerokuConnectionRefreshTokenCredentialsSchema,
HerokuConnectionOAuthOutputCredentialsSchema
])
.describe(AppConnections.CREATE(AppConnection.Heroku).credentials)
})
]);
export const CreateHerokuConnectionSchema = ValidateHerokuConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Heroku)
);
export const UpdateHerokuConnectionSchema = z
.object({
credentials: z
.union([
HerokuConnectionAuthTokenCredentialsSchema,
HerokuConnectionOAuthOutputCredentialsSchema,
HerokuConnectionRefreshTokenCredentialsSchema,
HerokuConnectionOAuthCredentialsSchema
])
.optional()
.describe(AppConnections.UPDATE(AppConnection.Heroku).credentials)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Heroku));
export const HerokuConnectionListItemSchema = z.object({
name: z.literal("Heroku"),
app: z.literal(AppConnection.Heroku),
methods: z.nativeEnum(HerokuConnectionMethod).array(),
oauthClientId: z.string().optional()
});

View File

@@ -0,0 +1,35 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TAppConnectionDALFactory } from "../app-connection-dal";
import { AppConnection } from "../app-connection-enums";
import { listHerokuApps as getHerokuApps } from "./heroku-connection-fns";
import { THerokuConnection } from "./heroku-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<THerokuConnection>;
export const herokuConnectionService = (
getAppConnection: TGetAppConnectionFunc,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Heroku, connectionId, actor);
try {
const apps = await getHerokuApps({ appConnection, appConnectionDAL, kmsService });
return apps;
} catch (error) {
logger.error(error, `Failed to establish connection with Heroku for app ${connectionId}`);
return [];
}
};
return {
listApps
};
};

View File

@@ -0,0 +1,27 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateHerokuConnectionSchema,
HerokuConnectionSchema,
ValidateHerokuConnectionCredentialsSchema
} from "./heroku-connection-schemas";
export type THerokuConnection = z.infer<typeof HerokuConnectionSchema>;
export type THerokuConnectionInput = z.infer<typeof CreateHerokuConnectionSchema> & {
app: AppConnection.Heroku;
};
export type TValidateHerokuConnectionCredentialsSchema = typeof ValidateHerokuConnectionCredentialsSchema;
export type THerokuConnectionConfig = DiscriminativePick<THerokuConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type THerokuApp = {
name: string;
id: string;
};

View File

@@ -0,0 +1,4 @@
export * from "./heroku-connection-enums";
export * from "./heroku-connection-fns";
export * from "./heroku-connection-schemas";
export * from "./heroku-connection-types";

View File

@@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const HEROKU_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Heroku",
destination: SecretSync.Heroku,
connection: AppConnection.Heroku,
canImportSecrets: true
};

View File

@@ -0,0 +1,170 @@
import { request } from "@app/lib/config/request";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { HerokuConnectionMethod, refreshHerokuToken, THerokuConnection } from "@app/services/app-connection/heroku";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import {
THerokuConfigVars,
THerokuListVariables,
THerokuSyncWithCredentials,
THerokuUpdateVariables
} from "@app/services/secret-sync/heroku/heroku-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
type THerokuSyncFactoryDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
const getValidAuthToken = async (
connection: THerokuConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
): Promise<string> => {
if (
connection.method === HerokuConnectionMethod.OAuth &&
connection.credentials.refreshToken &&
connection.credentials.expiresAt < new Date()
) {
const authToken = await refreshHerokuToken(
connection.credentials.refreshToken,
connection.id,
connection.orgId,
appConnectionDAL,
kmsService
);
return authToken;
}
return connection.credentials.authToken;
};
const getHerokuConfigVars = async ({ authToken, app }: THerokuListVariables): Promise<THerokuConfigVars> => {
const { data } = await request.get<THerokuConfigVars>(
`${IntegrationUrls.HEROKU_API_URL}/apps/${encodeURIComponent(app)}/config-vars`,
{
headers: {
Authorization: `Bearer ${authToken}`,
Accept: "application/vnd.heroku+json; version=3"
}
}
);
return data;
};
const updateHerokuConfigVars = async ({ authToken, app, configVars }: THerokuUpdateVariables) => {
return request.patch(`${IntegrationUrls.HEROKU_API_URL}/apps/${encodeURIComponent(app)}/config-vars`, configVars, {
headers: {
Authorization: `Bearer ${authToken}`,
Accept: "application/vnd.heroku+json; version=3",
"Content-Type": "application/json"
}
});
};
export const HerokuSyncFns = {
syncSecrets: async (
secretSync: THerokuSyncWithCredentials,
secretMap: TSecretMap,
{ appConnectionDAL, kmsService }: THerokuSyncFactoryDeps
) => {
const {
connection,
environment,
destinationConfig: { app }
} = secretSync;
const authToken = await getValidAuthToken(connection, appConnectionDAL, kmsService);
try {
const updatedConfigVars: THerokuConfigVars = {};
for (const [key, { value }] of Object.entries(secretMap)) {
updatedConfigVars[key] = value;
}
if (!secretSync.syncOptions.disableSecretDeletion) {
const currentConfigVars = await getHerokuConfigVars({ authToken, app });
for (const key of Object.keys(currentConfigVars)) {
if (matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema) && !(key in secretMap)) {
updatedConfigVars[key] = null;
}
}
}
await updateHerokuConfigVars({
authToken,
app,
configVars: updatedConfigVars
});
} catch (error) {
throw new SecretSyncError({
error,
secretKey: "batch_update"
});
}
},
removeSecrets: async (
secretSync: THerokuSyncWithCredentials,
secretMap: TSecretMap,
{ appConnectionDAL, kmsService }: THerokuSyncFactoryDeps
) => {
const {
connection,
destinationConfig: { app }
} = secretSync;
const authToken = await getValidAuthToken(connection, appConnectionDAL, kmsService);
try {
const currentConfigVars = await getHerokuConfigVars({ authToken, app });
const configVarsToUpdate: Record<string, null> = {};
for (const key of Object.keys(secretMap)) {
if (key in currentConfigVars) {
configVarsToUpdate[key] = null;
}
}
if (Object.keys(configVarsToUpdate).length > 0) {
await updateHerokuConfigVars({
authToken,
app,
configVars: configVarsToUpdate
});
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: "batch_remove"
});
}
},
getSecrets: async (
secretSync: THerokuSyncWithCredentials,
{ appConnectionDAL, kmsService }: THerokuSyncFactoryDeps
): Promise<TSecretMap> => {
const {
connection,
destinationConfig: { app }
} = secretSync;
const authToken = await getValidAuthToken(connection, appConnectionDAL, kmsService);
const data = await getHerokuConfigVars({ authToken, app });
const transformed = Object.entries(data).reduce((acc, [key, value]) => {
if (!value) {
return acc;
}
acc[key] = { value };
return acc;
}, {} as TSecretMap);
return transformed;
}
};

View File

@@ -0,0 +1,44 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const HerokuSyncDestinationConfigSchema = z.object({
app: z.string().trim().min(1, "App required").describe(SecretSyncs.DESTINATION_CONFIG.HEROKU.app),
appName: z.string().trim().min(1, "App name required").describe(SecretSyncs.DESTINATION_CONFIG.HEROKU.appName)
});
const HerokuSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
export const HerokuSyncSchema = BaseSecretSyncSchema(SecretSync.Heroku, HerokuSyncOptionsConfig).extend({
destination: z.literal(SecretSync.Heroku),
destinationConfig: HerokuSyncDestinationConfigSchema
});
export const CreateHerokuSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Heroku,
HerokuSyncOptionsConfig
).extend({
destinationConfig: HerokuSyncDestinationConfigSchema
});
export const UpdateHerokuSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Heroku,
HerokuSyncOptionsConfig
).extend({
destinationConfig: HerokuSyncDestinationConfigSchema.optional()
});
export const HerokuSyncListItemSchema = z.object({
name: z.literal("Heroku"),
connection: z.literal(AppConnection.Heroku),
destination: z.literal(SecretSync.Heroku),
canImportSecrets: z.literal(true)
});

View File

@@ -0,0 +1,24 @@
import { z } from "zod";
import { THerokuConnection } from "@app/services/app-connection/heroku";
import { CreateHerokuSyncSchema, HerokuSyncListItemSchema, HerokuSyncSchema } from "./heroku-sync-schemas";
export type THerokuSync = z.infer<typeof HerokuSyncSchema>;
export type THerokuSyncInput = z.infer<typeof CreateHerokuSyncSchema>;
export type THerokuSyncListItem = z.infer<typeof HerokuSyncListItemSchema>;
export type THerokuSyncWithCredentials = THerokuSync & {
connection: THerokuConnection;
};
export type THerokuConfigVars = Record<string, string | null>;
export type THerokuListVariables = {
authToken: string;
app: string;
};
export type THerokuUpdateVariables = THerokuListVariables & {
configVars: THerokuConfigVars;
};

View File

@@ -0,0 +1,4 @@
export * from "./heroku-sync-constants";
export * from "./heroku-sync-fns";
export * from "./heroku-sync-schemas";
export * from "./heroku-sync-types";

View File

@@ -16,6 +16,7 @@ export enum SecretSync {
TeamCity = "teamcity",
OCIVault = "oci-vault",
OnePass = "1password",
Heroku = "heroku",
Render = "render",
Flyio = "flyio"
}

View File

@@ -33,6 +33,7 @@ import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio";
import { GCP_SYNC_LIST_OPTION } from "./gcp";
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault";
import { HEROKU_SYNC_LIST_OPTION, HerokuSyncFns } from "./heroku";
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
import { RENDER_SYNC_LIST_OPTION, RenderSyncFns } from "./render";
@@ -60,6 +61,7 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION,
[SecretSync.OCIVault]: OCI_VAULT_SYNC_LIST_OPTION,
[SecretSync.OnePass]: ONEPASS_SYNC_LIST_OPTION,
[SecretSync.Heroku]: HEROKU_SYNC_LIST_OPTION,
[SecretSync.Render]: RENDER_SYNC_LIST_OPTION,
[SecretSync.Flyio]: FLYIO_SYNC_LIST_OPTION
};
@@ -207,6 +209,8 @@ export const SecretSyncFns = {
appConnectionDAL,
kmsService
}).syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Heroku:
return HerokuSyncFns.syncSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService });
case SecretSync.Vercel:
return VercelSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Windmill:
@@ -300,6 +304,9 @@ export const SecretSyncFns = {
case SecretSync.OnePass:
secretMap = await OnePassSyncFns.getSecrets(secretSync);
break;
case SecretSync.Heroku:
secretMap = await HerokuSyncFns.getSecrets(secretSync, { appConnectionDAL, kmsService });
break;
case SecretSync.Render:
secretMap = await RenderSyncFns.getSecrets(secretSync);
break;
@@ -373,6 +380,8 @@ export const SecretSyncFns = {
return OCIVaultSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.OnePass:
return OnePassSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Heroku:
return HerokuSyncFns.removeSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService });
case SecretSync.Render:
return RenderSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Flyio:

View File

@@ -19,6 +19,7 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.TeamCity]: "TeamCity",
[SecretSync.OCIVault]: "OCI Vault",
[SecretSync.OnePass]: "1Password",
[SecretSync.Heroku]: "Heroku",
[SecretSync.Render]: "Render",
[SecretSync.Flyio]: "Fly.io"
};
@@ -41,6 +42,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.TeamCity]: AppConnection.TeamCity,
[SecretSync.OCIVault]: AppConnection.OCI,
[SecretSync.OnePass]: AppConnection.OnePass,
[SecretSync.Heroku]: AppConnection.Heroku,
[SecretSync.Render]: AppConnection.Render,
[SecretSync.Flyio]: AppConnection.Flyio
};
@@ -63,6 +65,7 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.TeamCity]: SecretSyncPlanType.Regular,
[SecretSync.OCIVault]: SecretSyncPlanType.Enterprise,
[SecretSync.OnePass]: SecretSyncPlanType.Regular,
[SecretSync.Heroku]: SecretSyncPlanType.Regular,
[SecretSync.Render]: SecretSyncPlanType.Regular,
[SecretSync.Flyio]: SecretSyncPlanType.Regular
};

View File

@@ -80,6 +80,7 @@ import {
THCVaultSyncListItem,
THCVaultSyncWithCredentials
} from "./hc-vault/hc-vault-sync-types";
import { THerokuSync, THerokuSyncInput, THerokuSyncListItem, THerokuSyncWithCredentials } from "./heroku";
import {
THumanitecSync,
THumanitecSyncInput,
@@ -124,6 +125,7 @@ export type TSecretSync =
| TTeamCitySync
| TOCIVaultSync
| TOnePassSync
| THerokuSync
| TRenderSync
| TFlyioSync;
@@ -145,6 +147,7 @@ export type TSecretSyncWithCredentials =
| TTeamCitySyncWithCredentials
| TOCIVaultSyncWithCredentials
| TOnePassSyncWithCredentials
| THerokuSyncWithCredentials
| TRenderSyncWithCredentials
| TFlyioSyncWithCredentials;
@@ -166,6 +169,7 @@ export type TSecretSyncInput =
| TTeamCitySyncInput
| TOCIVaultSyncInput
| TOnePassSyncInput
| THerokuSyncInput
| TRenderSyncInput
| TFlyioSyncInput;
@@ -187,6 +191,7 @@ export type TSecretSyncListItem =
| TTeamCitySyncListItem
| TOCIVaultSyncListItem
| TOnePassSyncListItem
| THerokuSyncListItem
| TRenderSyncListItem
| TFlyioSyncListItem;

View File

@@ -21,7 +21,7 @@ type LoginTwoRequest struct {
}
type LoginTwoResponse struct {
JWTToken string `json:"token"`
JTWToken string `json:"token"`
RefreshToken string `json:"refreshToken"`
PublicKey string `json:"publicKey"`
EncryptedPrivateKey string `json:"encryptedPrivateKey"`
@@ -267,7 +267,7 @@ type GetLoginTwoV2Response struct {
ProtectedKey string `json:"protectedKey"`
ProtectedKeyIV string `json:"protectedKeyIV"`
ProtectedKeyTag string `json:"protectedKeyTag"`
RefreshToken string `json:"refreshToken"`
RefreshToken string `json:"RefreshToken"`
}
type VerifyMfaTokenRequest struct {

View File

@@ -87,7 +87,7 @@ func getDynamicSecretList(cmd *cobra.Command, args []string) {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
httpClient.SetAuthToken(infisicalToken)
@@ -211,7 +211,7 @@ func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
httpClient.SetAuthToken(infisicalToken)
@@ -363,7 +363,7 @@ func renewDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
httpClient.SetAuthToken(infisicalToken)
@@ -478,7 +478,7 @@ func revokeDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
httpClient.SetAuthToken(infisicalToken)
@@ -592,7 +592,7 @@ func listDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
httpClient.SetAuthToken(infisicalToken)

View File

@@ -115,7 +115,7 @@ var exportCmd = &cobra.Command{
if err != nil {
util.HandleError(err)
}
accessToken = loggedInUserDetails.UserCredentials.JWTToken
accessToken = loggedInUserDetails.UserCredentials.JTWToken
}
processedTemplate, err := ProcessTemplate(1, templatePath, nil, accessToken, "", &newEtag, dynamicSecretLeases)

View File

@@ -53,7 +53,7 @@ var initCmd = &cobra.Command{
if err != nil {
util.HandleError(err, "Unable to get resty client with custom headers")
}
httpClient.SetAuthToken(userCreds.UserCredentials.JWTToken)
httpClient.SetAuthToken(userCreds.UserCredentials.JTWToken)
organizationResponse, err := api.CallGetAllOrganizations(httpClient)
if err != nil {
@@ -124,7 +124,7 @@ var initCmd = &cobra.Command{
}
// set the config jwt token to the new token
userCreds.UserCredentials.JWTToken = tokenResponse.Token
userCreds.UserCredentials.JTWToken = tokenResponse.Token
err = util.StoreUserCredsInKeyRing(&userCreds.UserCredentials)
httpClient.SetAuthToken(tokenResponse.Token)

View File

@@ -111,7 +111,7 @@ var loginCmd = &cobra.Command{
infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{
SiteUrl: config.INFISICAL_URL,
UserAgent: api.USER_AGENT,
AutoTokenRefresh: true,
AutoTokenRefresh: false,
CustomHeaders: customHeaders,
})
@@ -437,8 +437,7 @@ func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) {
//updating usercredentials
userCredentialsToBeStored.Email = email
userCredentialsToBeStored.PrivateKey = string(decryptedPrivateKey)
userCredentialsToBeStored.JWTToken = newJwtToken
userCredentialsToBeStored.RefreshToken = loginTwoResponse.RefreshToken
userCredentialsToBeStored.JTWToken = newJwtToken
}
func init() {
@@ -863,7 +862,7 @@ func askToPasteJwtToken(success chan models.UserCredentials, failure chan error)
os.Exit(1)
}
// verify JWT
// verify JTW
httpClient, err := util.GetRestyClientWithCustomHeaders()
if err != nil {
failure <- err
@@ -872,7 +871,7 @@ func askToPasteJwtToken(success chan models.UserCredentials, failure chan error)
}
httpClient.
SetAuthToken(userCredentials.JWTToken).
SetAuthToken(userCredentials.JTWToken).
SetHeader("Accept", "application/json")
isAuthenticated := api.CallIsAuthenticated(httpClient)

View File

@@ -245,7 +245,7 @@ var secretsSetCmd = &cobra.Command{
secretOperations, err = util.SetRawSecrets(processedArgs, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{
Type: "",
Token: loggedInUserDetails.UserCredentials.JWTToken,
Token: loggedInUserDetails.UserCredentials.JTWToken,
}, file)
}
@@ -330,7 +330,7 @@ var secretsDeleteCmd = &cobra.Command{
loggedInUserDetails = util.EstablishUserLoginSession()
}
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JWTToken)
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken)
}
for _, secretName := range args {

View File

@@ -186,7 +186,7 @@ func issueCredentials(cmd *cobra.Command, args []string) {
if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
certificateTemplateId, err := cmd.Flags().GetString("certificateTemplateId")
@@ -419,7 +419,7 @@ func signKey(cmd *cobra.Command, args []string) {
if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
certificateTemplateId, err := cmd.Flags().GetString("certificateTemplateId")
@@ -628,7 +628,7 @@ func sshConnect(cmd *cobra.Command, args []string) {
if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
writeHostCaToFile, err := cmd.Flags().GetBool("write-host-ca-to-file")
@@ -881,7 +881,7 @@ func sshAddHost(cmd *cobra.Command, args []string) {
if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession()
}
infisicalToken = loggedInUserDetails.UserCredentials.JWTToken
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
projectId, err := cmd.Flags().GetString("projectId")

View File

@@ -115,7 +115,7 @@ var tokensCreateCmd = &cobra.Command{
}
}
workspaceKey, err := util.GetPlainTextWorkspaceKey(loggedInUserDetails.UserCredentials.JWTToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceId)
workspaceKey, err := util.GetPlainTextWorkspaceKey(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceId)
if err != nil {
util.HandleError(err, "Unable to get workspace key needed to create service token")
}
@@ -140,7 +140,7 @@ var tokensCreateCmd = &cobra.Command{
util.HandleError(err, "Unable to get resty client with custom headers")
}
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JWTToken).
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
SetHeader("Accept", "application/json")
createServiceTokenResponse, err := api.CallCreateServiceToken(httpClient, api.CreateServiceTokenRequest{

View File

@@ -118,7 +118,7 @@ var userGetTokenCmd = &cobra.Command{
util.HandleError(err, "[infisical user get token]: Unable to get logged in user token")
}
tokenParts := strings.Split(loggedInUserDetails.UserCredentials.JWTToken, ".")
tokenParts := strings.Split(loggedInUserDetails.UserCredentials.JTWToken, ".")
if len(tokenParts) != 3 {
util.HandleError(errors.New("invalid token format"), "[infisical user get token]: Invalid token format")
}
@@ -136,7 +136,7 @@ var userGetTokenCmd = &cobra.Command{
}
fmt.Println("Session ID:", tokenPayload.TokenVersionId)
fmt.Println("Token:", loggedInUserDetails.UserCredentials.JWTToken)
fmt.Println("Token:", loggedInUserDetails.UserCredentials.JTWToken)
},
}

View File

@@ -5,8 +5,8 @@ import "time"
type UserCredentials struct {
Email string `json:"email"`
PrivateKey string `json:"privateKey"`
JWTToken string `json:"JWTToken"`
RefreshToken string `json:"refreshToken"`
JTWToken string `json:"JTWToken"`
RefreshToken string `json:"RefreshToken"`
}
// The file struct for Infisical config file

View File

@@ -9,7 +9,6 @@ import (
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/rs/zerolog/log"
"github.com/zalando/go-keyring"
)
@@ -91,22 +90,23 @@ func GetCurrentLoggedInUserDetails(setConfigVariables bool) (LoggedInUserDetails
}
httpClient.
SetAuthToken(userCreds.JWTToken).
SetAuthToken(userCreds.JTWToken).
SetHeader("Accept", "application/json")
isAuthenticated := api.CallIsAuthenticated(httpClient)
if !isAuthenticated {
accessTokenResponse, refreshErr := api.CallGetNewAccessTokenWithRefreshToken(httpClient, userCreds.RefreshToken)
if refreshErr == nil && accessTokenResponse.Token != "" {
isAuthenticated = true
userCreds.JWTToken = accessTokenResponse.Token
}
}
// TODO: add refresh token
// if !isAuthenticated {
// accessTokenResponse, err := api.CallGetNewAccessTokenWithRefreshToken(httpClient, userCreds.RefreshToken)
// if err == nil && accessTokenResponse.Token != "" {
// isAuthenticated = true
// userCreds.JTWToken = accessTokenResponse.Token
// }
// }
err = StoreUserCredsInKeyRing(&userCreds)
if err != nil {
log.Debug().Msg("unable to store your user credentials with new access token")
}
// err = StoreUserCredsInKeyRing(&userCreds)
// if err != nil {
// log.Debug().Msg("unable to store your user credentials with new access token")
// }
if !isAuthenticated {
return LoggedInUserDetails{

View File

@@ -35,7 +35,7 @@ func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder
params.WorkspaceId = workspaceFile.WorkspaceId
}
folders, err := GetFoldersViaJWT(loggedInUserDetails.UserCredentials.JWTToken, params.WorkspaceId, params.Environment, params.FoldersPath)
folders, err := GetFoldersViaJTW(loggedInUserDetails.UserCredentials.JTWToken, params.WorkspaceId, params.Environment, params.FoldersPath)
folderErr = err
foldersToReturn = folders
} else if params.InfisicalToken != "" {
@@ -60,14 +60,14 @@ func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder
return foldersToReturn, folderErr
}
func GetFoldersViaJWT(JWTToken string, workspaceId string, environmentName string, foldersPath string) ([]models.SingleFolder, error) {
func GetFoldersViaJTW(JTWToken string, workspaceId string, environmentName string, foldersPath string) ([]models.SingleFolder, error) {
// set up resty client
httpClient, err := GetRestyClientWithCustomHeaders()
if err != nil {
return nil, err
}
httpClient.SetAuthToken(JWTToken).
httpClient.SetAuthToken(JTWToken).
SetHeader("Accept", "application/json")
getFoldersRequest := api.GetFoldersV1Request{
@@ -194,7 +194,7 @@ func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, er
loggedInUserDetails = EstablishUserLoginSession()
}
params.InfisicalToken = loggedInUserDetails.UserCredentials.JWTToken
params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
// set up resty client
@@ -243,7 +243,7 @@ func DeleteFolder(params models.DeleteFolderParameters) ([]models.SingleFolder,
loggedInUserDetails = EstablishUserLoginSession()
}
params.InfisicalToken = loggedInUserDetails.UserCredentials.JWTToken
params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken
}
// set up resty client

View File

@@ -302,9 +302,9 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
params.WorkspaceId = infisicalDotJson.WorkspaceId
}
res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JWTToken, params.WorkspaceId,
res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, params.WorkspaceId,
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs, true)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JWT token [err=%s]", err)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err)
if err == nil {
backupEncryptionKey, err := GetBackupEncryptionKey()

View File

@@ -0,0 +1,4 @@
---
title: "Available"
openapi: "GET /api/v1/app-connections/heroku/available"
---

View File

@@ -0,0 +1,10 @@
---
title: "Create"
openapi: "POST /api/v1/app-connections/heroku"
---
<Note>
Heroku OAuth Connections must be created through the Infisical UI.
Check out the configuration docs for [Heroku OAuth Connections](/integrations/app-connections/heroku) for a step-by-step
guide.
</Note>

View File

@@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/app-connections/heroku/{connectionId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/app-connections/heroku/{connectionId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by Name"
openapi: "GET /api/v1/app-connections/heroku/connection-name/{connectionName}"
---

View File

@@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/app-connections/heroku"
---

View File

@@ -0,0 +1,10 @@
---
title: "Update"
openapi: "PATCH /api/v1/app-connections/heroku/{connectionId}"
---
<Note>
Heroku OAuth Connections must be updated through the Infisical UI.
Check out the configuration docs for [Heroku OAuth Connections](/integrations/app-connections/heroku) for a step-by-step
guide.
</Note>

View File

@@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/secret-syncs/heroku"
---

View File

@@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/secret-syncs/heroku/{syncId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/secret-syncs/heroku/{syncId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by Name"
openapi: "GET /api/v1/secret-syncs/heroku/sync-name/{syncName}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Import Secrets"
openapi: "POST /api/v1/secret-syncs/heroku/{syncId}/import-secrets"
---

View File

@@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/secret-syncs/heroku"
---

View File

@@ -0,0 +1,4 @@
---
title: "Remove Secrets"
openapi: "POST /api/v1/secret-syncs/heroku/{syncId}/remove-secrets"
---

View File

@@ -0,0 +1,4 @@
---
title: "Sync Secrets"
openapi: "POST /api/v1/secret-syncs/heroku/{syncId}/sync-secrets"
---

View File

@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/secret-syncs/heroku/{syncId}"
---

View File

@@ -127,8 +127,8 @@ Follow the instructions for your operating system to install the Infisical CLI.
</Tab>
<Tab title="Debian/Ubuntu">
Add Infisical repository
Add Infisical repository
```console
$ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' \
@@ -143,7 +143,7 @@ Follow the instructions for your operating system to install the Infisical CLI.
</Tab>
<Tab title="Arch Linux">
Use the `yay` package manager to install from the [Arch User Repository](https://aur.archlinux.org/packages/infisical-bin)
```console
$ yay -S infisical-bin
```
@@ -187,7 +187,7 @@ We'll now use the Infisical-Vercel integration send secrets from Infisical to Ve
### Infisical-Vercel integration
To begin we have to import the Next.js app into Vercel as a project. [Follow these instructions](https://nextjs.org/learn/basics/deploying-nextjs-app/deploy) to deploy the Next.js app to Vercel.
To begin we have to import the Next.js app into Vercel as a project. [Follow these instructions](https://vercel.com/docs/frameworks/nextjs) to deploy the Next.js app to Vercel.
Next, navigate to your project's integrations tab in Infisical and press on the Vercel tile to grant Infisical access to your Vercel account.
@@ -237,7 +237,7 @@ At this stage, you know how to use the Infisical-Vercel integration to sync prod
</Accordion>
<Accordion title="Is opting out of end-to-end encryption for the Infisical-Vercel integration safe?">
Yes. Your secrets are still encrypted at rest. To note, most secret managers actually don't support end-to-end encryption.
Check out the [security guide](/security/overview).
</Accordion>
</AccordionGroup>

View File

@@ -161,6 +161,11 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField>
<ParamField path="Tags" type="map<string, string>[]">
Tags to be added to the created IAM User resource.
</ParamField>
<ParamField path="Method" type="string" required>
Select *Assume Role* method.
</ParamField>
@@ -304,6 +309,10 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Tags" type="map[string]string">
Tags to be added to the created IAM User resource.
</ParamField>
</Step>
<Step title="Click 'Submit'">

View File

@@ -0,0 +1,112 @@
---
title: "GitHub"
description: "Learn how to dynamically generate GitHub App tokens."
---
The Infisical GitHub dynamic secret allows you to generate short-lived tokens for a GitHub App on demand based on service account permissions.
## Setup GitHub App
<Steps>
<Step title="Create an application on GitHub">
Navigate to [GitHub App settings](https://github.com/settings/apps) and click **New GitHub App**.
![integrations github app create](/images/integrations/github/app/self-hosted-github-app-create.png)
Give the application a name and a homepage URL. These values do not need to be anything specific.
Disable webhook by unchecking the Active checkbox.
![integrations github app webhook](/images/integrations/github/app/self-hosted-github-app-webhook.png)
Configure the app's permissions to grant the necessary access for the dynamic secret's short-lived tokens based on your use case.
Create the GitHub Application.
![integrations github app create confirm](/images/integrations/github/app/self-hosted-github-app-create-confirm.png)
<Note>
If you have a GitHub organization, you can create an application under it
in your organization Settings > Developer settings > GitHub Apps > New GitHub App.
</Note>
</Step>
<Step title="Save app credentials">
Copy the **App ID** and generate a new **Private Key** for your GitHub Application.
![integrations github app create private key](/images/integrations/github/app/self-hosted-github-app-private-key.png)
Save these for later steps.
</Step>
<Step title="Install app">
Install your application to whichever repositories and organizations that you want the dynamic secret to access.
![Install App](/images/platform/dynamic-secrets/github/install-app.png)
![Install App](/images/platform/dynamic-secrets/github/install-app-modal.png)
Once you've installed the app, **copy the installation ID** from the URL and save it for later steps.
![Install App](/images/platform/dynamic-secrets/github/installation.png)
</Step>
</Steps>
## Set up Dynamic Secrets with GitHub
<Steps>
<Step title="Open Secret Overview Dashboard">
Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret.
</Step>
<Step title="Click on the 'Add Dynamic Secret' button">
![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png)
</Step>
<Step title="Select 'GitHub'">
![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/github/modal.png)
</Step>
<Step title="Provide the inputs for dynamic secret parameters">
<ParamField path="Secret Name" type="string" required>
Name by which you want the secret to be referenced
</ParamField>
<ParamField path="App ID" type="string" required>
The ID of the app created in earlier steps.
</ParamField>
<ParamField path="App Private Key PEM" type="string" required>
The Private Key of the app created in earlier steps.
</ParamField>
<ParamField path="Installation ID" type="string" required>
The ID of the installation from earlier steps.
</ParamField>
</Step>
<Step title="Click `Submit`">
After submitting the form, you will see a dynamic secret created in the dashboard.
</Step>
<Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item.
Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section.
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.png)
When generating these secrets, the TTL will be fixed to 1 hour.
![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png)
Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.
![Dynamic Secret Lease](/images/platform/dynamic-secrets/github/lease.png)
</Step>
</Steps>
## Audit or Revoke Leases
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before its set time to live.
![Lease Data](/images/platform/dynamic-secrets/lease-data.png)
<Warning>
GitHub App tokens cannot be revoked. As such, revoking a token on Infisical does not invalidate the GitHub token; it remains active until it expires.
</Warning>
## Renew Leases
<Note>
GitHub App tokens cannot be renewed because they are fixed to a lifetime of 1 hour.
</Note>

View File

@@ -49,11 +49,21 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
```
</Step>
<Step title="Install the Issuer Controller">
Install the Infisical PKI Issuer controller into your Kubernetes cluster by running the following command:
Install the Infisical PKI Issuer controller into your Kubernetes cluster using one of the following methods:
```bash
kubectl apply -f https://raw.githubusercontent.com/Infisical/infisical-issuer/main/build/install.yaml
```
<Tabs>
<Tab title="Helm">
```bash
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
helm install infisical-pki-issuer infisical-helm-charts/infisical-pki-issuer
```
</Tab>
<Tab title="kubectl">
```bash
kubectl apply -f https://raw.githubusercontent.com/Infisical/infisical-issuer/main/build/install.yaml
```
</Tab>
</Tabs>
</Step>
<Step title="Create Kubernetes Secret for Infisical PKI Issuer">
Start by creating a Kubernetes `Secret` containing the **Client Secret** from step 1. As mentioned previously, this will be used by the Infisical PKI issuer to authenticate with Infisical.

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 KiB

View File

@@ -0,0 +1,121 @@
---
title: "Heroku App Connection"
description: "Learn how to configure a Heroku App Connection for Infisical using OAuth or Auth Token methods."
---
Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth Token**. Choose the method that best fits your setup and security requirements.
<Tabs>
<Tab title="OAuth Method">
The OAuth method provides secure authentication through Heroku's OAuth flow.
<Accordion title="Self-Hosted Instance Setup">
Using the Heroku App Connection with OAuth on a self-hosted instance of Infisical requires configuring an API client in Heroku and registering your instance with it.
**Prerequisites:**
- A Heroku account with existing applications
- Self-hosted Infisical instance
<Steps>
<Step title="Create an API client in Heroku">
Navigate to your user Account settings > Applications to create a new API client.
![Heroku config settings](/images/integrations/heroku/integrations-heroku-config-settings.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)
Create the API client. As part of the form, set the **OAuth callback URL** to `https://your-domain.com/integrations/heroku/oauth2/callback`.
<Tip>
The domain you defined in the OAuth callback URL should be equivalent to the `SITE_URL` configured in your Infisical instance.
</Tip>
![Heroku config new app form](/images/integrations/heroku/integrations-heroku-config-new-app-form.png)
</Step>
<Step title="Add your Heroku API client credentials to Infisical">
Obtain the **Client ID** and **Client Secret** for your Heroku API client.
![Heroku config credentials](/images/integrations/heroku/integrations-heroku-config-credentials.png)
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.
Once added, restart your Infisical instance and use the Heroku App Connection.
</Step>
</Steps>
</Accordion>
## Setup Heroku OAuth Connection in Infisical
<Steps>
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings** page.
![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step>
<Step title="Add Connection">
Select the **Heroku App Connection** option from the connection options modal.
![Select Heroku Connection](/images/app-connections/heroku/heroku-select-connection.png)
</Step>
<Step title="Choose OAuth Method">
Select the **OAuth** method and click **Connect to Heroku**.
![Connect via Heroku OAuth](/images/app-connections/heroku/heroku-create-oauth-method.png)
</Step>
<Step title="Grant Access">
You will be redirected to Heroku to grant Infisical access to your Heroku account. Once granted, you will be redirected back to Infisical's App Connections page.
![Heroku Authorization](/images/integrations/heroku/integrations-heroku-auth.png)
</Step>
<Step title="Connection Created">
Your **Heroku App Connection** is now available for use.
![Heroku OAuth Connection](/images/app-connections/heroku/heroku-connection.png)
</Step>
</Steps>
</Tab>
<Tab title="Auth Token Method">
The Auth Token method uses a Heroku API token for authentication, providing a straightforward setup process.
## Setup Heroku Auth Token Connection in Infisical
<Steps>
<Step title="Generate Heroku API Token">
Log in to your Heroku account and navigate to Account Settings.
Under the **Authorizations** section on the **Applications** tab, reveal and copy your Authorization token. If you don't have one, click **Create Authorization** to create a new token.
<Warning>
Keep your Authorization token secure and do not share it. Anyone with access to this token can manage your Heroku applications.
</Warning>
![Heroku API Token](/images/app-connections/heroku/heroku-api-token.png)
</Step>
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings** page.
![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step>
<Step title="Add Connection">
Select the **Heroku App Connection** option from the connection options modal.
![Select Heroku Connection](/images/app-connections/heroku/heroku-select-connection.png)
</Step>
<Step title="Configure Auth Token">
Select the **Auth Token** method and paste your Heroku Authorization token in the provided field.
![Configure Auth Token](/images/app-connections/heroku/heroku-create-token-method.png)
Click **Connect** to establish the connection.
</Step>
<Step title="Connection Created">
Your **Heroku App Connection** is now available for use.
![Heroku Auth Token Connection](/images/app-connections/heroku/heroku-connection.png)
</Step>
</Steps>
<Info>
Auth Token connections require manual token rotation when your Heroku Authorization expires or is regenerated. Monitor your connection status and update the token as needed.
</Info>
</Tab>
</Tabs>

View File

@@ -0,0 +1,144 @@
---
title: "Heroku Sync"
description: "Learn how to configure a Heroku Sync for Infisical."
---
**Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- Create a [Heroku App Connection](/integrations/app-connections/heroku)
<Tabs>
<Tab title="Infisical UI">
1. Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
![Secret Syncs Tab](/images/secret-syncs/general/secret-sync-tab.png)
2. Select the **Heroku** option.
![Select Heroku](/images/secret-syncs/heroku/select-heroku-option.png)
3. Configure the **Source** from where secrets should be retrieved, then click **Next**.
![Configure Source](/images/secret-syncs/heroku/heroku-source.png)
- **Environment**: The project environment to retrieve secrets from.
- **Secret Path**: The folder path to retrieve secrets from.
<Tip>
If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports).
</Tip>
4. Configure the **Destination** to where secrets should be deployed, then click **Next**.
![Configure Destination](/images/secret-syncs/heroku/heroku-destination.png)
- **Heroku App Connection**: The Heroku App Connection to authenticate with.
- **Heroku App**: The Heroku application to sync secrets to.
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
![Configure Options](/images/secret-syncs/heroku/heroku-options.png)
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
- **Import - Prefer values from Infisical**: Import secrets from Heroku to Infisical; if a secret with the same name already exists in Infisical, do nothing. Afterwards, sync secrets to Heroku.
- **Import - Prefer values from Heroku**: Import secrets from Heroku to Infisical; if a secret with the same name already exists in Infisical, replace its value with the one from Heroku. Afterwards, sync secrets to Heroku.
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment.
<Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
</Note>
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Heroku Sync, then click **Next**.
![Configure Details](/images/secret-syncs/heroku/heroku-details.png)
- **Name**: The name of your sync. Must be slug-friendly.
- **Description**: An optional description for your sync.
7. Review your Heroku Sync configuration, then click **Create Sync**.
![Confirm Configuration](/images/secret-syncs/heroku/heroku-review.png)
8. If enabled, your Heroku Sync will begin syncing your secrets to the destination endpoint.
![Sync Secrets](/images/secret-syncs/heroku/heroku-created.png)
</Tab>
<Tab title="API">
To create a **Heroku Sync**, make an API request to the [Create Heroku Sync](/api-reference/endpoints/secret-syncs/heroku/create) API endpoint.
### Sample request
```bash Request
curl --request POST \
--url https://app.infisical.com/api/v1/secret-syncs/heroku \
--header 'Content-Type: application/json' \
--data '{
"name": "my-heroku-sync",
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"description": "an example sync",
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"environment": "dev",
"secretPath": "/my-secrets",
"isEnabled": true,
"syncOptions": {
"initialSyncBehavior": "overwrite-destination",
"disableSecretDeletion": true
},
"destinationConfig": {
"app": "8dd25736052a4b50",
"appName": "my-app"
}
}'
```
### Sample response
```bash Response
{
"secretSync": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "my-heroku-sync",
"description": "an example sync",
"isEnabled": true,
"version": 1,
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"syncStatus": "succeeded",
"lastSyncJobId": "123",
"lastSyncMessage": null,
"lastSyncedAt": "2023-11-07T05:31:56Z",
"importStatus": null,
"lastImportJobId": null,
"lastImportMessage": null,
"lastImportedAt": null,
"removeStatus": null,
"lastRemoveJobId": null,
"lastRemoveMessage": null,
"lastRemovedAt": null,
"syncOptions": {
"initialSyncBehavior": "overwrite-destination",
"disableSecretDeletion": true
},
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"connection": {
"app": "heroku",
"name": "my-heroku-connection",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
},
"environment": {
"slug": "dev",
"name": "Development",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
},
"folder": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"path": "/my-secrets"
},
"destination": "heroku",
"destinationConfig": {
"app": "8dd25736052a4b50",
"appName": "my-app"
}
}
}
```
</Tab>
</Tabs>

View File

@@ -214,6 +214,7 @@
"documentation/platform/dynamic-secrets/cassandra",
"documentation/platform/dynamic-secrets/elastic-search",
"documentation/platform/dynamic-secrets/gcp-iam",
"documentation/platform/dynamic-secrets/github",
"documentation/platform/dynamic-secrets/ldap",
"documentation/platform/dynamic-secrets/mongo-atlas",
"documentation/platform/dynamic-secrets/mongo-db",
@@ -509,6 +510,7 @@
"integrations/app-connections/github",
"integrations/app-connections/github-radar",
"integrations/app-connections/hashicorp-vault",
"integrations/app-connections/heroku",
"integrations/app-connections/humanitec",
"integrations/app-connections/ldap",
"integrations/app-connections/mssql",
@@ -544,6 +546,7 @@
"integrations/secret-syncs/gcp-secret-manager",
"integrations/secret-syncs/github",
"integrations/secret-syncs/hashicorp-vault",
"integrations/secret-syncs/heroku",
"integrations/secret-syncs/humanitec",
"integrations/secret-syncs/oci-vault",
"integrations/secret-syncs/render",
@@ -1327,6 +1330,18 @@
"api-reference/endpoints/app-connections/hashicorp-vault/delete"
]
},
{
"group": "Heroku",
"pages": [
"api-reference/endpoints/app-connections/heroku/list",
"api-reference/endpoints/app-connections/heroku/available",
"api-reference/endpoints/app-connections/heroku/get-by-id",
"api-reference/endpoints/app-connections/heroku/get-by-name",
"api-reference/endpoints/app-connections/heroku/create",
"api-reference/endpoints/app-connections/heroku/update",
"api-reference/endpoints/app-connections/heroku/delete"
]
},
{
"group": "Humanitec",
"pages": [
@@ -1642,6 +1657,19 @@
"api-reference/endpoints/secret-syncs/hashicorp-vault/remove-secrets"
]
},
{
"group": "Heroku",
"pages": [
"api-reference/endpoints/secret-syncs/heroku/list",
"api-reference/endpoints/secret-syncs/heroku/get-by-id",
"api-reference/endpoints/secret-syncs/heroku/get-by-name",
"api-reference/endpoints/secret-syncs/heroku/create",
"api-reference/endpoints/secret-syncs/heroku/update",
"api-reference/endpoints/secret-syncs/heroku/delete",
"api-reference/endpoints/secret-syncs/heroku/sync-secrets",
"api-reference/endpoints/secret-syncs/heroku/remove-secrets"
]
},
{
"group": "Humanitec",
"pages": [

View File

@@ -26,7 +26,7 @@ export const TtlFormLabel = ({ label }: { label: string }) => (
<FontAwesomeIcon
icon={faQuestionCircle}
size="sm"
className="relative bottom-1 right-1"
className="relative bottom-px right-1"
/>
</Tooltip>
}

View File

@@ -0,0 +1,76 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
import { THerokuApp } from "@app/hooks/api/appConnections/heroku";
import { useHerokuConnectionListApps } from "@app/hooks/api/appConnections/heroku/queries";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const HerokuSyncFields = () => {
const { control, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.Heroku }
>();
const connectionId = useWatch({ name: "connection.id", control });
const { data: apps, isLoading: isAppsLoading } = useHerokuConnectionListApps(connectionId, {
enabled: Boolean(connectionId)
});
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.app", "");
setValue("destinationConfig.appName", "");
}}
/>
<Controller
name="destinationConfig.app"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="App"
helperText={
<Tooltip
className="max-w-md"
content="Ensure the app exists in the connection's Heroku instance URL."
>
<div>
<span>Don&#39;t see the app you&#39;re looking for?</span>{" "}
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
</div>
</Tooltip>
}
>
<FilterableSelect
menuPlacement="top"
isLoading={isAppsLoading && Boolean(connectionId)}
isDisabled={!connectionId}
value={apps?.find((app) => app.id === value) ?? null}
onChange={(option) => {
onChange((option as SingleValue<THerokuApp>)?.id ?? "");
setValue(
"destinationConfig.appName",
(option as SingleValue<THerokuApp>)?.name ?? ""
);
}}
options={apps}
placeholder="Select an app..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
</>
);
};

View File

@@ -15,6 +15,7 @@ import { FlyioSyncFields } from "./FlyioSyncFields";
import { GcpSyncFields } from "./GcpSyncFields";
import { GitHubSyncFields } from "./GitHubSyncFields";
import { HCVaultSyncFields } from "./HCVaultSyncFields";
import { HerokuSyncFields } from "./HerokuSyncFields";
import { HumanitecSyncFields } from "./HumanitecSyncFields";
import { OCIVaultSyncFields } from "./OCIVaultSyncFields";
import { RenderSyncFields } from "./RenderSyncFields";
@@ -63,6 +64,8 @@ export const SecretSyncDestinationFields = () => {
return <OCIVaultSyncFields />;
case SecretSync.OnePass:
return <OnePassSyncFields />;
case SecretSync.Heroku:
return <HerokuSyncFields />;
case SecretSync.Render:
return <RenderSyncFields />;
case SecretSync.Flyio:

View File

@@ -52,6 +52,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.TeamCity:
case SecretSync.OnePass:
case SecretSync.OCIVault:
case SecretSync.Heroku:
case SecretSync.Render:
case SecretSync.Flyio:
AdditionalSyncOptionsFieldsComponent = null;

View File

@@ -0,0 +1,18 @@
import { useFormContext } from "react-hook-form";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const HerokuSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Heroku }>();
const appName = watch("destinationConfig.appName");
const appId = watch("destinationConfig.app");
return (
<>
<GenericFieldLabel label="App">{appName}</GenericFieldLabel>
<GenericFieldLabel label="App ID">{appId}</GenericFieldLabel>
</>
);
};

View File

@@ -24,6 +24,7 @@ import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields";
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields";
import { HerokuSyncReviewFields } from "./HerokuSyncReviewFields";
import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
@@ -106,6 +107,9 @@ export const SecretSyncReviewFields = () => {
case SecretSync.OnePass:
DestinationFieldsComponent = <OnePassSyncReviewFields />;
break;
case SecretSync.Heroku:
DestinationFieldsComponent = <HerokuSyncReviewFields />;
break;
case SecretSync.Render:
DestinationFieldsComponent = <RenderSyncReviewFields />;
break;

View File

@@ -0,0 +1,14 @@
import { z } from "zod";
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const HerokuSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.Heroku),
destinationConfig: z.object({
app: z.string().trim().min(1, "App ID required"),
appName: z.string().trim().min(1, "App name required")
})
})
);

View File

@@ -12,6 +12,7 @@ import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema";
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
import { HCVaultSyncDestinationSchema } from "./hc-vault-sync-destination-schema";
import { HerokuSyncDestinationSchema } from "./heroku-sync-destination-schema";
import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-schema";
import { OCIVaultSyncDestinationSchema } from "./oci-vault-sync-destination-schema";
import { RenderSyncDestinationSchema } from "./render-sync-destination-schema";
@@ -38,6 +39,7 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
TeamCitySyncDestinationSchema,
OCIVaultSyncDestinationSchema,
OnePassSyncDestinationSchema,
HerokuSyncDestinationSchema,
RenderSyncDestinationSchema,
FlyioSyncDestinationSchema
]);

View File

@@ -16,7 +16,7 @@ export interface IsCliLoginSuccessful {
loginResponse?: {
email: string;
privateKey: string;
JWTToken: string;
JTWToken: string;
};
success: boolean;
}
@@ -131,7 +131,7 @@ const attemptLogin = async ({
loginResponse: {
email,
privateKey,
JWTToken: token
JTWToken: token
},
success: true
});

View File

@@ -14,7 +14,7 @@ interface IsMfaLoginSuccessful {
success: boolean;
loginResponse: {
privateKey: string;
JWTToken: string;
JTWToken: string;
};
}
@@ -95,7 +95,7 @@ const attemptLoginMfa = async ({
success: true,
loginResponse: {
privateKey,
JWTToken: token
JTWToken: token
}
});
} catch (err) {

View File

@@ -37,6 +37,7 @@ import {
VercelConnectionMethod,
WindmillConnectionMethod
} from "@app/hooks/api/appConnections/types";
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
@@ -81,6 +82,7 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" },
[AppConnection.OCI]: { name: "OCI", image: "Oracle.png", enterprise: true },
[AppConnection.OnePass]: { name: "1Password", image: "1Password.png" },
[AppConnection.Heroku]: { name: "Heroku", image: "Heroku.png" },
[AppConnection.Render]: { name: "Render", image: "Render.png" },
[AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" }
};
@@ -95,6 +97,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
case AzureClientSecretsConnectionMethod.OAuth:
case AzureDevOpsConnectionMethod.OAuth:
case GitHubConnectionMethod.OAuth:
case HerokuConnectionMethod.OAuth:
return { name: "OAuth", icon: faPassport };
case AwsConnectionMethod.AccessKey:
case OCIConnectionMethod.AccessKey:
@@ -129,6 +132,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
return { name: "App Role", icon: faUser };
case LdapConnectionMethod.SimpleBind:
return { name: "Simple Bind", icon: faLink };
case HerokuConnectionMethod.AuthToken:
return { name: "Auth Token", icon: faKey };
case RenderConnectionMethod.ApiKey:
return { name: "API Key", icon: faKey };
default:

View File

@@ -62,6 +62,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
name: "1Password",
image: "1Password.png"
},
[SecretSync.Heroku]: {
name: "Heroku",
image: "Heroku.png"
},
[SecretSync.Render]: {
name: "Render",
image: "Render.png"
@@ -90,6 +94,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.TeamCity]: AppConnection.TeamCity,
[SecretSync.OCIVault]: AppConnection.OCI,
[SecretSync.OnePass]: AppConnection.OnePass,
[SecretSync.Heroku]: AppConnection.Heroku,
[SecretSync.Render]: AppConnection.Render,
[SecretSync.Flyio]: AppConnection.Flyio
};

Some files were not shown because too many files have changed in this diff Show More