mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Iron out naming / text, update docs for Heroku integration
This commit is contained in:
@@ -422,10 +422,11 @@ export const registerRoutes = async (
|
||||
projectMembershipDAL,
|
||||
smtpService,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
secretVersionTagDAL
|
||||
});
|
||||
const secretBlindIndexService = secretBlindIndexServiceFactory({
|
||||
permissionService,
|
||||
|
||||
@@ -32,7 +32,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||
.object({
|
||||
secretPrefix: z.string().optional(),
|
||||
secretSuffix: z.string().optional(),
|
||||
syncBehavior: z.string().optional(),
|
||||
initialSyncBehavior: z.string().optional(),
|
||||
secretGCPLabel: z
|
||||
.object({
|
||||
labelName: z.string(),
|
||||
|
||||
@@ -43,7 +43,6 @@ import {
|
||||
import { getIntegrationOptions, Integrations, IntegrationUrls } from "./integration-list";
|
||||
import { getTeams } from "./integration-team";
|
||||
import { exchangeCode, exchangeRefresh } from "./integration-token";
|
||||
import { access } from "node:fs";
|
||||
|
||||
type TIntegrationAuthServiceFactoryDep = {
|
||||
integrationAuthDAL: TIntegrationAuthDALFactory;
|
||||
@@ -603,7 +602,7 @@ export const integrationAuthServiceFactory = ({
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return data.map(({ app: { id: appId }, stage, pipeline: { id: pipelineId, name } }) => ({
|
||||
app: { appId },
|
||||
stage,
|
||||
|
||||
@@ -37,7 +37,7 @@ export enum IntegrationType {
|
||||
OAUTH2 = "oauth2"
|
||||
}
|
||||
|
||||
export enum IntegrationSyncBehavior {
|
||||
export enum IntegrationInitialSyncBehavior {
|
||||
OVERWRITE_TARGET = "overwrite-target",
|
||||
PREFER_TARGET = "prefer-target",
|
||||
PREFER_SOURCE = "prefer-source"
|
||||
|
||||
@@ -20,23 +20,13 @@ import sodium from "libsodium-wrappers";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretType, TIntegrationAuths, TIntegrations } from "@app/db/schemas";
|
||||
import { SecretType, TIntegrationAuths, TIntegrations, TSecrets } from "@app/db/schemas";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
|
||||
|
||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||
import {
|
||||
createManySecretsRawHelper
|
||||
// updateManySecretsRawHelper
|
||||
} from "../secret/secret-fns";
|
||||
import { Integrations, IntegrationSyncBehavior, IntegrationUrls } from "./integration-list";
|
||||
import { IntegrationInitialSyncBehavior, Integrations, IntegrationUrls } from "./integration-list";
|
||||
|
||||
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
|
||||
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
|
||||
@@ -594,35 +584,25 @@ const syncSecretsAWSSecretManager = async ({
|
||||
* Sync/push [secrets] to Heroku app named [integration.app]
|
||||
*/
|
||||
const syncSecretsHeroku = async ({
|
||||
projectDAL,
|
||||
createManySecretsRawFn,
|
||||
updateManySecretsRawFn,
|
||||
integrationDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL,
|
||||
botKey, // TODO: consider getting botKey inside this fn
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
projectDAL: TProjectDALFactory;
|
||||
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||
integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
|
||||
secretDAL: TSecretDALFactory;
|
||||
secretVersionDAL: TSecretVersionDALFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
secretTagDAL: TSecretTagDALFactory;
|
||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
||||
folderDAL: TSecretFolderDALFactory;
|
||||
botKey: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
integration: TIntegrations;
|
||||
integration: TIntegrations & {
|
||||
projectId: string;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
secretPath: string;
|
||||
};
|
||||
secrets: Record<string, { value: string; comment?: string } | null>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
@@ -644,13 +624,13 @@ const syncSecretsHeroku = async ({
|
||||
Object.keys(herokuSecrets).forEach((key) => {
|
||||
if (!integration.lastUsed) {
|
||||
// first time using integration
|
||||
// -> apply initial sync behavior rule
|
||||
switch (metadata.syncBehavior) {
|
||||
case IntegrationSyncBehavior.OVERWRITE_TARGET: {
|
||||
// -> apply initial sync behavior
|
||||
switch (metadata.initialSyncBehavior) {
|
||||
case IntegrationInitialSyncBehavior.OVERWRITE_TARGET: {
|
||||
if (!(key in secrets)) secrets[key] = null;
|
||||
break;
|
||||
}
|
||||
case IntegrationSyncBehavior.PREFER_TARGET: {
|
||||
case IntegrationInitialSyncBehavior.PREFER_TARGET: {
|
||||
if (!(key in secrets)) {
|
||||
secretsToAdd[key] = herokuSecrets[key];
|
||||
} else if (secrets[key]?.value !== herokuSecrets[key]) {
|
||||
@@ -661,7 +641,7 @@ const syncSecretsHeroku = async ({
|
||||
};
|
||||
break;
|
||||
}
|
||||
case IntegrationSyncBehavior.PREFER_SOURCE: {
|
||||
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
|
||||
if (!(key in secrets)) {
|
||||
secrets[key] = herokuSecrets[key];
|
||||
secretsToAdd[key] = herokuSecrets[key];
|
||||
@@ -677,18 +657,10 @@ const syncSecretsHeroku = async ({
|
||||
});
|
||||
|
||||
if (Object.keys(secretsToAdd).length) {
|
||||
await createManySecretsRawHelper({
|
||||
botKey,
|
||||
projectDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL,
|
||||
projectId,
|
||||
environment,
|
||||
path: secretPath,
|
||||
await createManySecretsRawFn({
|
||||
projectId: integration.projectId,
|
||||
environment: integration.environment.slug,
|
||||
path: integration.secretPath,
|
||||
secrets: Object.keys(secretsToAdd).map((key) => ({
|
||||
secretName: key,
|
||||
secretValue: secretsToAdd[key],
|
||||
@@ -698,27 +670,19 @@ const syncSecretsHeroku = async ({
|
||||
});
|
||||
}
|
||||
|
||||
// if (Object.keys(secretsToUpdate).length) {
|
||||
// await updateManySecretsRawHelper({
|
||||
// projectId,
|
||||
// environment,
|
||||
// path: secretPath,
|
||||
// secrets: Object.keys(secretsToUpdate).map((key) => ({
|
||||
// secretName: key,
|
||||
// secretValue: secretsToUpdate[key],
|
||||
// type: SecretType.Shared,
|
||||
// secretComment: ""
|
||||
// })),
|
||||
// botKey, // TODO: consider getting botKey inside this fn
|
||||
// projectDAL,
|
||||
// secretDAL,
|
||||
// secretVersionDAL,
|
||||
// secretBlindIndexDAL,
|
||||
// secretTagDAL,
|
||||
// secretVersionTagDAL,
|
||||
// folderDAL
|
||||
// });
|
||||
// }
|
||||
if (Object.keys(secretsToUpdate).length) {
|
||||
await updateManySecretsRawFn({
|
||||
projectId: integration.projectId,
|
||||
environment: integration.environment.slug,
|
||||
path: integration.secretPath,
|
||||
secrets: Object.keys(secretsToUpdate).map((key) => ({
|
||||
secretName: key,
|
||||
secretValue: secretsToUpdate[key],
|
||||
type: SecretType.Shared,
|
||||
secretComment: ""
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
await request.patch(
|
||||
`${IntegrationUrls.HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||
@@ -3053,18 +3017,9 @@ const syncSecretsHasuraCloud = async ({
|
||||
*
|
||||
*/
|
||||
export const syncIntegrationSecrets = async ({
|
||||
projectDAL,
|
||||
createManySecretsRawFn,
|
||||
updateManySecretsRawFn,
|
||||
integrationDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL,
|
||||
botKey,
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
@@ -3072,19 +3027,18 @@ export const syncIntegrationSecrets = async ({
|
||||
accessToken,
|
||||
appendices
|
||||
}: {
|
||||
projectDAL: TProjectDALFactory;
|
||||
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||
integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
|
||||
secretDAL: TSecretDALFactory;
|
||||
secretVersionDAL: TSecretVersionDALFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
secretTagDAL: TSecretTagDALFactory;
|
||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
||||
folderDAL: TSecretFolderDALFactory;
|
||||
botKey: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
integration: TIntegrations;
|
||||
integration: TIntegrations & {
|
||||
projectId: string;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
secretPath: string;
|
||||
};
|
||||
integrationAuth: TIntegrationAuths;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessId: string | null;
|
||||
@@ -3124,18 +3078,9 @@ export const syncIntegrationSecrets = async ({
|
||||
break;
|
||||
case Integrations.HEROKU:
|
||||
await syncSecretsHeroku({
|
||||
projectDAL,
|
||||
createManySecretsRawFn,
|
||||
updateManySecretsRawFn,
|
||||
integrationDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL,
|
||||
botKey,
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { decryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
|
||||
import { TGetPrivateKeyDTO } from "./project-bot-types";
|
||||
|
||||
export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
ciphertext: bot.encryptedPrivateKey
|
||||
});
|
||||
|
||||
export const getBotKeyFnFactory = (projectBotDAL: TProjectBotDALFactory) => {
|
||||
const getBotKeyFn = async (projectId: string) => {
|
||||
const bot = await projectBotDAL.findOne({ projectId });
|
||||
|
||||
if (!bot) throw new BadRequestError({ message: "failed to find bot key" });
|
||||
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
|
||||
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
|
||||
throw new BadRequestError({ message: "Encryption key missing" });
|
||||
|
||||
const botPrivateKey = getBotPrivateKey({ bot });
|
||||
|
||||
return decryptAsymmetric({
|
||||
ciphertext: bot.encryptedProjectKey,
|
||||
privateKey: botPrivateKey,
|
||||
nonce: bot.encryptedProjectKeyNonce,
|
||||
publicKey: bot.sender.publicKey
|
||||
});
|
||||
};
|
||||
|
||||
return getBotKeyFn;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ProjectVersion, SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { ProjectVersion } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { decryptAsymmetric, generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectBotDALFactory } from "./project-bot-dal";
|
||||
import { TFindBotByProjectIdDTO, TGetPrivateKeyDTO, TSetActiveStateDTO } from "./project-bot-types";
|
||||
import { getBotKeyFnFactory, getBotPrivateKey } from "./project-bot-fns";
|
||||
import { TFindBotByProjectIdDTO, TSetActiveStateDTO } from "./project-bot-types";
|
||||
|
||||
type TProjectBotServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
@@ -24,29 +25,10 @@ export const projectBotServiceFactory = ({
|
||||
projectDAL,
|
||||
permissionService
|
||||
}: TProjectBotServiceFactoryDep) => {
|
||||
const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
ciphertext: bot.encryptedPrivateKey
|
||||
});
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||
|
||||
const getBotKey = async (projectId: string) => {
|
||||
const bot = await projectBotDAL.findOne({ projectId });
|
||||
if (!bot) throw new BadRequestError({ message: "failed to find bot key" });
|
||||
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
|
||||
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
|
||||
throw new BadRequestError({ message: "Encryption key missing" });
|
||||
|
||||
const botPrivateKey = getBotPrivateKey({ bot });
|
||||
|
||||
return decryptAsymmetric({
|
||||
ciphertext: bot.encryptedProjectKey,
|
||||
privateKey: botPrivateKey,
|
||||
nonce: bot.encryptedProjectKeyNonce,
|
||||
publicKey: bot.sender.publicKey
|
||||
});
|
||||
return getBotKeyFn(projectId);
|
||||
};
|
||||
|
||||
const findBotByProjectId = async ({
|
||||
|
||||
@@ -18,10 +18,12 @@ import {
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { groupBy, unique } from "@app/lib/fn";
|
||||
|
||||
import { getBotKeyFnFactory } from "../project-bot/project-bot-fns";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretDALFactory } from "./secret-dal";
|
||||
import {
|
||||
TCreateManySecretsRawHelper,
|
||||
TCreateManySecretsRawFn,
|
||||
TCreateManySecretsRawFnFactory,
|
||||
TFnSecretBlindIndexCheck,
|
||||
TFnSecretBulkInsert,
|
||||
TFnSecretBulkUpdate,
|
||||
@@ -405,112 +407,114 @@ export const fnSecretBulkUpdate = async ({
|
||||
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||
};
|
||||
|
||||
export const createManySecretsRawHelper = async ({
|
||||
projectId,
|
||||
environment,
|
||||
path: secretPath,
|
||||
secrets,
|
||||
userId,
|
||||
botKey, // TODO: consider getting botKey inside this fn
|
||||
export const createManySecretsRawFnFactory = ({
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL
|
||||
}: TCreateManySecretsRawHelper) => {
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
}: TCreateManySecretsRawFnFactory) => {
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||
const createManySecretsRawFn = async ({
|
||||
projectId,
|
||||
environment,
|
||||
path: secretPath,
|
||||
secrets,
|
||||
userId
|
||||
}: TCreateManySecretsRawFn) => {
|
||||
const botKey = await getBotKeyFn(projectId);
|
||||
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
||||
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Create secret" });
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
|
||||
// insert operation
|
||||
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
|
||||
inputSecrets: secrets,
|
||||
folderId,
|
||||
isNew: true,
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
||||
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Create secret" });
|
||||
|
||||
const inputSecrets = await Promise.all(
|
||||
secrets.map(async (secret) => {
|
||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
|
||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
|
||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey);
|
||||
|
||||
if (secret.type === SecretType.Personal) {
|
||||
if (!userId) throw new BadRequestError({ message: "Missing user id for personal secret" });
|
||||
const sharedExist = await secretDAL.findOne({
|
||||
secretBlindIndex: keyName2BlindIndex[secret.secretName],
|
||||
folderId,
|
||||
type: SecretType.Shared
|
||||
});
|
||||
|
||||
if (!sharedExist)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create personal secret override for no corresponding shared secret"
|
||||
});
|
||||
}
|
||||
|
||||
const tags = secret.tags ? await secretTagDAL.findManyTagsById(projectId, secret.tags) : [];
|
||||
if ((secret.tags || []).length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
|
||||
|
||||
return {
|
||||
type: secret.type,
|
||||
userId: secret.type === SecretType.Personal ? userId : null,
|
||||
secretName: secret.secretName,
|
||||
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
|
||||
secretKeyIV: secretKeyEncrypted.iv,
|
||||
secretKeyTag: secretKeyEncrypted.tag,
|
||||
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||
secretValueIV: secretValueEncrypted.iv,
|
||||
secretValueTag: secretValueEncrypted.tag,
|
||||
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||
secretCommentIV: secretCommentEncrypted.iv,
|
||||
secretCommentTag: secretCommentEncrypted.tag,
|
||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||
tags: secret.tags
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const newSecrets = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkInsert({
|
||||
inputSecrets: inputSecrets.map(({ secretName, ...el }) => ({
|
||||
...el,
|
||||
version: 0,
|
||||
secretBlindIndex: keyName2BlindIndex[secretName],
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
keyEncoding: SecretKeyEncoding.UTF8
|
||||
})),
|
||||
// insert operation
|
||||
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
|
||||
inputSecrets: secrets,
|
||||
folderId,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
tx
|
||||
})
|
||||
);
|
||||
isNew: true,
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
return newSecrets;
|
||||
const inputSecrets = await Promise.all(
|
||||
secrets.map(async (secret) => {
|
||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
|
||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
|
||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey);
|
||||
|
||||
if (secret.type === SecretType.Personal) {
|
||||
if (!userId) throw new BadRequestError({ message: "Missing user id for personal secret" });
|
||||
const sharedExist = await secretDAL.findOne({
|
||||
secretBlindIndex: keyName2BlindIndex[secret.secretName],
|
||||
folderId,
|
||||
type: SecretType.Shared
|
||||
});
|
||||
|
||||
if (!sharedExist)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create personal secret override for no corresponding shared secret"
|
||||
});
|
||||
}
|
||||
|
||||
const tags = secret.tags ? await secretTagDAL.findManyTagsById(projectId, secret.tags) : [];
|
||||
if ((secret.tags || []).length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
|
||||
|
||||
return {
|
||||
type: secret.type,
|
||||
userId: secret.type === SecretType.Personal ? userId : null,
|
||||
secretName: secret.secretName,
|
||||
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
|
||||
secretKeyIV: secretKeyEncrypted.iv,
|
||||
secretKeyTag: secretKeyEncrypted.tag,
|
||||
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||
secretValueIV: secretValueEncrypted.iv,
|
||||
secretValueTag: secretValueEncrypted.tag,
|
||||
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||
secretCommentIV: secretCommentEncrypted.iv,
|
||||
secretCommentTag: secretCommentEncrypted.tag,
|
||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||
tags: secret.tags
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const newSecrets = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkInsert({
|
||||
inputSecrets: inputSecrets.map(({ secretName, ...el }) => ({
|
||||
...el,
|
||||
version: 0,
|
||||
secretBlindIndex: keyName2BlindIndex[secretName],
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
keyEncoding: SecretKeyEncoding.UTF8
|
||||
})),
|
||||
folderId,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
tx
|
||||
})
|
||||
);
|
||||
|
||||
return newSecrets;
|
||||
};
|
||||
|
||||
return createManySecretsRawFn;
|
||||
};
|
||||
|
||||
// TOOD: potentially convert raw stuff
|
||||
|
||||
// updateManySecretsRawFnFactory
|
||||
|
||||
// updateManySecretsRawHelper
|
||||
|
||||
export const updateManySecretsRawFnFactory = async ({
|
||||
// TODO: refactor
|
||||
botKey,
|
||||
export const updateManySecretsRawFnFactory = ({
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
@@ -518,14 +522,16 @@ export const updateManySecretsRawFnFactory = async ({
|
||||
secretVersionTagDAL,
|
||||
folderDAL
|
||||
}: TUpdateManySecretsRawFnFactory) => {
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||
const updateManySecretsRawFn = async ({
|
||||
projectId,
|
||||
environment,
|
||||
path: secretPath,
|
||||
secrets, // accept instead ciphertext secrets
|
||||
secrets, // consider accepting instead ciphertext secrets
|
||||
userId
|
||||
}: TUpdateManySecretsRawFn) => {
|
||||
// TODO: fetch botKey from here
|
||||
}: TUpdateManySecretsRawFn): Promise<Array<TSecrets & { _id: string }>> => {
|
||||
const botKey = await getBotKeyFn(projectId);
|
||||
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
import { isSamePath } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
import { createManySecretsRawFnFactory, updateManySecretsRawFnFactory } from "@app/services/secret/secret-fns";
|
||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
||||
@@ -29,8 +31,6 @@ import { TSecretDALFactory } from "./secret-dal";
|
||||
import { interpolateSecrets } from "./secret-fns";
|
||||
import { TCreateSecretReminderDTO, THandleReminderDTO, TRemoveSecretReminderDTO } from "./secret-types";
|
||||
|
||||
// import { updateManySecretsRawFnFactory } from "@app/services/secret/secret-fns";
|
||||
|
||||
export type TSecretQueueFactory = ReturnType<typeof secretQueueFactory>;
|
||||
|
||||
type TSecretQueueFactoryDep = {
|
||||
@@ -44,6 +44,7 @@ type TSecretQueueFactoryDep = {
|
||||
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
projectDAL: TProjectDALFactory;
|
||||
projectBotDAL: TProjectBotDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">;
|
||||
smtpService: TSmtpService;
|
||||
orgDAL: Pick<TOrgDALFactory, "findOrgByProjectId">;
|
||||
@@ -72,12 +73,35 @@ export const secretQueueFactory = ({
|
||||
orgDAL,
|
||||
smtpService,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
projectMembershipDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL
|
||||
}: TSecretQueueFactoryDep) => {
|
||||
const createManySecretsRawFn = createManySecretsRawFnFactory({
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL
|
||||
});
|
||||
|
||||
const updateManySecretsRawFn = updateManySecretsRawFnFactory({
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL
|
||||
});
|
||||
|
||||
const syncIntegrations = async (dto: TGetSecrets) => {
|
||||
await queueService.queue(QueueName.IntegrationSync, QueueJobs.IntegrationSync, dto, {
|
||||
attempts: 5,
|
||||
@@ -320,30 +344,10 @@ export const secretQueueFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
// const updateManySecretsRawFn = updateManySecretsRawFnFactory({
|
||||
// botKey, // can move this out
|
||||
// projectDAL,
|
||||
// secretDAL,
|
||||
// secretVersionDAL,
|
||||
// secretBlindIndexDAL,
|
||||
// secretTagDAL,
|
||||
// secretVersionTagDAL,
|
||||
// folderDAL
|
||||
// });
|
||||
|
||||
await syncIntegrationSecrets({
|
||||
projectDAL,
|
||||
createManySecretsRawFn,
|
||||
updateManySecretsRawFn,
|
||||
integrationDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretBlindIndexDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL,
|
||||
botKey,
|
||||
projectId, // service
|
||||
environment,
|
||||
secretPath,
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Knex } from "knex";
|
||||
import { SecretType, TSecretBlindIndexes, TSecrets, TSecretsInsert, TSecretsUpdate } from "@app/db/schemas";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||
@@ -248,7 +249,18 @@ export type TRemoveSecretReminderDTO = {
|
||||
|
||||
// ---
|
||||
|
||||
export type TCreateManySecretsRawHelper = {
|
||||
export type TCreateManySecretsRawFnFactory = {
|
||||
projectDAL: TProjectDALFactory;
|
||||
projectBotDAL: TProjectBotDALFactory;
|
||||
secretDAL: TSecretDALFactory;
|
||||
secretVersionDAL: TSecretVersionDALFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
secretTagDAL: TSecretTagDALFactory;
|
||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
||||
folderDAL: TSecretFolderDALFactory;
|
||||
};
|
||||
|
||||
export type TCreateManySecretsRawFn = {
|
||||
projectId: string;
|
||||
environment: string;
|
||||
path: string;
|
||||
@@ -264,19 +276,11 @@ export type TCreateManySecretsRawHelper = {
|
||||
};
|
||||
}[];
|
||||
userId?: string; // only relevant for personal secret(s)
|
||||
botKey: string;
|
||||
projectDAL: TProjectDALFactory;
|
||||
secretDAL: TSecretDALFactory;
|
||||
secretVersionDAL: TSecretVersionDALFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
secretTagDAL: TSecretTagDALFactory;
|
||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
||||
folderDAL: TSecretFolderDALFactory;
|
||||
};
|
||||
|
||||
export type TUpdateManySecretsRawFnFactory = {
|
||||
botKey: string;
|
||||
projectDAL: TProjectDALFactory;
|
||||
projectBotDAL: TProjectBotDALFactory;
|
||||
secretDAL: TSecretDALFactory;
|
||||
secretVersionDAL: TSecretVersionDALFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 533 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 700 KiB |
@@ -30,6 +30,17 @@ description: "How to sync secrets from Infisical to Heroku"
|
||||
Select which Infisical environment secrets you want to sync to which Heroku app and press create integration to start syncing secrets to Heroku.
|
||||
|
||||

|
||||
|
||||
Here's some guidance on each field:
|
||||
|
||||
- Project Environment: The environment in the current Infisical project from which you want to sync secrets from.
|
||||
- Secrets Path: The path in the current Infisical project from which you want to sync secrets from such as `/` (for secrets that do not reside in a folder) or `/foo/bar` (for secrets nested in a folder, in this case a folder called `bar` in another folder called `foo`).
|
||||
- Heroku App: The application in Heroku that you want to sync secrets to.
|
||||
- Initial Sync Behavior (default is **Import - Prefer values from Infisical**): The behavior of the first sync operation triggered after creating the integration.
|
||||
- **No Import - Overwrite all values in Heroku**: Sync secrets and overwrite any existing secrets in Heroku.
|
||||
- **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.
|
||||
|
||||

|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
2
frontend/package-lock.json
generated
2
frontend/package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "npm-proj-1709146141702-0.772936286416932EMIzNi",
|
||||
"name": "frontend",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -61,7 +61,7 @@ export const useCreateIntegration = () => {
|
||||
metadata?: {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
syncBehavior?: string;
|
||||
initialSyncBehavior?: string;
|
||||
}
|
||||
}) => {
|
||||
const { data: { integration } } = await apiRequest.post("/api/v1/integration", {
|
||||
|
||||
@@ -45,10 +45,4 @@ export enum IntegrationSyncBehavior {
|
||||
OVERWRITE_TARGET = "overwrite-target",
|
||||
PREFER_TARGET = "prefer-target",
|
||||
PREFER_SOURCE = "prefer-source"
|
||||
}
|
||||
|
||||
export const syncBehaviors = [
|
||||
{ label: "Overwrite target", value: IntegrationSyncBehavior.OVERWRITE_TARGET },
|
||||
{ label: "Prefer target", value: IntegrationSyncBehavior.PREFER_TARGET },
|
||||
{ label: "Prefer source", value: IntegrationSyncBehavior.PREFER_SOURCE }
|
||||
];
|
||||
}
|
||||
@@ -4,15 +4,22 @@ import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faBookOpen,
|
||||
faBugs,
|
||||
// faCircleInfo
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
import { RadioGroup } from "@app/components/v2/RadioGroup";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthHerokuPipelines } from "@app/hooks/api/integrationAuth/queries";
|
||||
import { App, Pipeline } from "@app/hooks/api/integrationAuth/types";
|
||||
import * as yup from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import queryString from "query-string";
|
||||
// import { useGetIntegrationAuthHerokuPipelines } from "@app/hooks/api/integrationAuth/queries";
|
||||
// import { App, Pipeline } from "@app/hooks/api/integrationAuth/types";
|
||||
import * as yup from "yup";
|
||||
|
||||
// import { RadioGroup } from "@app/components/v2/RadioGroup";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -27,16 +34,24 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "../../../hooks/api/integrationAuth";
|
||||
import { useCreateWsEnvironment, useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
import { IntegrationSyncBehavior, syncBehaviors } from "@app/hooks/api/integrations/types";
|
||||
import {
|
||||
// useCreateWsEnvironment,
|
||||
useGetWorkspaceById
|
||||
} from "../../../hooks/api/workspace";
|
||||
|
||||
const initialSyncBehaviors = [
|
||||
{ label: "No Import - Overwrite all values in Heroku", value: IntegrationSyncBehavior.OVERWRITE_TARGET },
|
||||
{ label: "Import - Prefer values from Heroku", value: IntegrationSyncBehavior.PREFER_TARGET },
|
||||
{ label: "Import - Prefer values from Infisical", value: IntegrationSyncBehavior.PREFER_SOURCE }
|
||||
];
|
||||
|
||||
const schema = yup.object({
|
||||
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
||||
secretPath: yup.string().required("Secret path is required"),
|
||||
targetApp: yup.string().required("Heroku app is required"),
|
||||
syncBehavior: yup
|
||||
initialSyncBehavior: yup
|
||||
.string()
|
||||
.oneOf(syncBehaviors.map((b) => b.value), "Invalid sync behavior")
|
||||
.oneOf(initialSyncBehaviors.map((b) => b.value), "Invalid initial sync behavior")
|
||||
.required("Initial sync behavior is required")
|
||||
});
|
||||
|
||||
@@ -49,7 +64,7 @@ export default function HerokuCreateIntegrationPage() {
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
secretPath: "/",
|
||||
syncBehavior: IntegrationSyncBehavior.PREFER_SOURCE
|
||||
initialSyncBehavior: IntegrationSyncBehavior.PREFER_SOURCE
|
||||
}
|
||||
});
|
||||
|
||||
@@ -57,7 +72,7 @@ export default function HerokuCreateIntegrationPage() {
|
||||
|
||||
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
const { mutateAsync: mutateAsyncEnv } = useCreateWsEnvironment();
|
||||
// const { mutateAsync: mutateAsyncEnv } = useCreateWsEnvironment();
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
@@ -67,14 +82,14 @@ export default function HerokuCreateIntegrationPage() {
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const { data: integrationAuthPipelineCouplings } = useGetIntegrationAuthHerokuPipelines({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
// const { data: integrationAuthPipelineCouplings } = useGetIntegrationAuthHerokuPipelines({
|
||||
// integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
// });
|
||||
|
||||
const [uniquePipelines, setUniquePipelines] = useState<Pipeline[]>();
|
||||
const [selectedPipeline, setSelectedPipeline] = useState("");
|
||||
const [selectedPipelineApps, setSelectedPipelineApps] = useState<App[]>();
|
||||
const [integrationType, setIntegrationType] = useState("App");
|
||||
// const [uniquePipelines, setUniquePipelines] = useState<Pipeline[]>();
|
||||
// const [selectedPipeline, setSelectedPipeline] = useState("");
|
||||
// const [selectedPipelineApps, setSelectedPipelineApps] = useState<App[]>();
|
||||
// const [integrationType, setIntegrationType] = useState("App");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@@ -84,37 +99,37 @@ export default function HerokuCreateIntegrationPage() {
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthPipelineCouplings) {
|
||||
const uniquePipelinesConst = Array.from(
|
||||
new Set(
|
||||
integrationAuthPipelineCouplings
|
||||
.map(({ pipeline: { pipelineId, name } }) => ({
|
||||
name,
|
||||
pipelineId
|
||||
}))
|
||||
.map((obj) => JSON.stringify(obj))
|
||||
)).map((str) => JSON.parse(str)) as { pipelineId: string; name: string }[]
|
||||
// useEffect(() => {
|
||||
// if (integrationAuthPipelineCouplings) {
|
||||
// const uniquePipelinesConst = Array.from(
|
||||
// new Set(
|
||||
// integrationAuthPipelineCouplings
|
||||
// .map(({ pipeline: { pipelineId, name } }) => ({
|
||||
// name,
|
||||
// pipelineId
|
||||
// }))
|
||||
// .map((obj) => JSON.stringify(obj))
|
||||
// )).map((str) => JSON.parse(str)) as { pipelineId: string; name: string }[]
|
||||
|
||||
[... (new Set())]
|
||||
setUniquePipelines(uniquePipelinesConst);
|
||||
if (uniquePipelinesConst) {
|
||||
if (uniquePipelinesConst!.length > 0) {
|
||||
setSelectedPipeline(uniquePipelinesConst![0].name);
|
||||
} else {
|
||||
setSelectedPipeline("none");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [integrationAuthPipelineCouplings]);
|
||||
// [... (new Set())]
|
||||
// setUniquePipelines(uniquePipelinesConst);
|
||||
// if (uniquePipelinesConst) {
|
||||
// if (uniquePipelinesConst!.length > 0) {
|
||||
// setSelectedPipeline(uniquePipelinesConst![0].name);
|
||||
// } else {
|
||||
// setSelectedPipeline("none");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }, [integrationAuthPipelineCouplings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthPipelineCouplings) {
|
||||
setSelectedPipelineApps(integrationAuthApps?.filter(app => integrationAuthPipelineCouplings
|
||||
.filter((pipelineCoupling) => pipelineCoupling.pipeline.name === selectedPipeline)
|
||||
.map(coupling => coupling.app.appId).includes(String(app.appId))))
|
||||
}
|
||||
}, [selectedPipeline]);
|
||||
// useEffect(() => {
|
||||
// if (integrationAuthPipelineCouplings) {
|
||||
// setSelectedPipelineApps(integrationAuthApps?.filter(app => integrationAuthPipelineCouplings
|
||||
// .filter((pipelineCoupling) => pipelineCoupling.pipeline.name === selectedPipeline)
|
||||
// .map(coupling => coupling.app.appId).includes(String(app.appId))))
|
||||
// }
|
||||
// }, [selectedPipeline]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
@@ -167,10 +182,9 @@ export default function HerokuCreateIntegrationPage() {
|
||||
// };
|
||||
|
||||
const onFormSubmit = async ({
|
||||
selectedSourceEnvironment: sce,
|
||||
secretPath,
|
||||
targetApp,
|
||||
syncBehavior,
|
||||
initialSyncBehavior,
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!integrationAuth?.id) return;
|
||||
@@ -184,7 +198,7 @@ export default function HerokuCreateIntegrationPage() {
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
secretPath,
|
||||
metadata: {
|
||||
syncBehavior
|
||||
initialSyncBehavior
|
||||
}
|
||||
});
|
||||
|
||||
@@ -314,7 +328,7 @@ export default function HerokuCreateIntegrationPage() {
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="syncBehavior"
|
||||
name="initialSyncBehavior"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Initial Sync Behavior"
|
||||
@@ -322,7 +336,7 @@ export default function HerokuCreateIntegrationPage() {
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select {...field} onValueChange={(e) => onChange(e)} className="w-full">
|
||||
{syncBehaviors.map((b) => {
|
||||
{initialSyncBehaviors.map((b) => {
|
||||
return (
|
||||
<SelectItem value={b.value} key={`sync-behavior-${b.value}`}>
|
||||
{b.label}
|
||||
|
||||
Reference in New Issue
Block a user