mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 02:26:40 +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,
|
projectMembershipDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretBlindIndexDAL,
|
secretBlindIndexDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL
|
||||||
});
|
});
|
||||||
const secretBlindIndexService = secretBlindIndexServiceFactory({
|
const secretBlindIndexService = secretBlindIndexServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
.object({
|
.object({
|
||||||
secretPrefix: z.string().optional(),
|
secretPrefix: z.string().optional(),
|
||||||
secretSuffix: z.string().optional(),
|
secretSuffix: z.string().optional(),
|
||||||
syncBehavior: z.string().optional(),
|
initialSyncBehavior: z.string().optional(),
|
||||||
secretGCPLabel: z
|
secretGCPLabel: z
|
||||||
.object({
|
.object({
|
||||||
labelName: z.string(),
|
labelName: z.string(),
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import {
|
|||||||
import { getIntegrationOptions, Integrations, IntegrationUrls } from "./integration-list";
|
import { getIntegrationOptions, Integrations, IntegrationUrls } from "./integration-list";
|
||||||
import { getTeams } from "./integration-team";
|
import { getTeams } from "./integration-team";
|
||||||
import { exchangeCode, exchangeRefresh } from "./integration-token";
|
import { exchangeCode, exchangeRefresh } from "./integration-token";
|
||||||
import { access } from "node:fs";
|
|
||||||
|
|
||||||
type TIntegrationAuthServiceFactoryDep = {
|
type TIntegrationAuthServiceFactoryDep = {
|
||||||
integrationAuthDAL: TIntegrationAuthDALFactory;
|
integrationAuthDAL: TIntegrationAuthDALFactory;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export enum IntegrationType {
|
|||||||
OAUTH2 = "oauth2"
|
OAUTH2 = "oauth2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum IntegrationSyncBehavior {
|
export enum IntegrationInitialSyncBehavior {
|
||||||
OVERWRITE_TARGET = "overwrite-target",
|
OVERWRITE_TARGET = "overwrite-target",
|
||||||
PREFER_TARGET = "prefer-target",
|
PREFER_TARGET = "prefer-target",
|
||||||
PREFER_SOURCE = "prefer-source"
|
PREFER_SOURCE = "prefer-source"
|
||||||
|
|||||||
@@ -20,23 +20,13 @@ import sodium from "libsodium-wrappers";
|
|||||||
import isEqual from "lodash.isequal";
|
import isEqual from "lodash.isequal";
|
||||||
import { z } from "zod";
|
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 { request } from "@app/lib/config/request";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
|
||||||
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 { TIntegrationDALFactory } from "../integration/integration-dal";
|
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||||
import {
|
import { IntegrationInitialSyncBehavior, Integrations, IntegrationUrls } from "./integration-list";
|
||||||
createManySecretsRawHelper
|
|
||||||
// updateManySecretsRawHelper
|
|
||||||
} from "../secret/secret-fns";
|
|
||||||
import { Integrations, IntegrationSyncBehavior, IntegrationUrls } from "./integration-list";
|
|
||||||
|
|
||||||
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
|
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
|
||||||
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
|
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]
|
* Sync/push [secrets] to Heroku app named [integration.app]
|
||||||
*/
|
*/
|
||||||
const syncSecretsHeroku = async ({
|
const syncSecretsHeroku = async ({
|
||||||
projectDAL,
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
integrationDAL,
|
integrationDAL,
|
||||||
secretDAL,
|
|
||||||
secretVersionDAL,
|
|
||||||
secretBlindIndexDAL,
|
|
||||||
secretTagDAL,
|
|
||||||
secretVersionTagDAL,
|
|
||||||
folderDAL,
|
|
||||||
botKey, // TODO: consider getting botKey inside this fn
|
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
projectDAL: TProjectDALFactory;
|
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
|
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
|
integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
|
||||||
secretDAL: TSecretDALFactory;
|
integration: TIntegrations & {
|
||||||
secretVersionDAL: TSecretVersionDALFactory;
|
projectId: string;
|
||||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
environment: {
|
||||||
secretTagDAL: TSecretTagDALFactory;
|
id: string;
|
||||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
name: string;
|
||||||
folderDAL: TSecretFolderDALFactory;
|
slug: string;
|
||||||
botKey: string;
|
};
|
||||||
projectId: string;
|
secretPath: string;
|
||||||
environment: string;
|
};
|
||||||
secretPath: string;
|
|
||||||
integration: TIntegrations;
|
|
||||||
secrets: Record<string, { value: string; comment?: string } | null>;
|
secrets: Record<string, { value: string; comment?: string } | null>;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -644,13 +624,13 @@ const syncSecretsHeroku = async ({
|
|||||||
Object.keys(herokuSecrets).forEach((key) => {
|
Object.keys(herokuSecrets).forEach((key) => {
|
||||||
if (!integration.lastUsed) {
|
if (!integration.lastUsed) {
|
||||||
// first time using integration
|
// first time using integration
|
||||||
// -> apply initial sync behavior rule
|
// -> apply initial sync behavior
|
||||||
switch (metadata.syncBehavior) {
|
switch (metadata.initialSyncBehavior) {
|
||||||
case IntegrationSyncBehavior.OVERWRITE_TARGET: {
|
case IntegrationInitialSyncBehavior.OVERWRITE_TARGET: {
|
||||||
if (!(key in secrets)) secrets[key] = null;
|
if (!(key in secrets)) secrets[key] = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IntegrationSyncBehavior.PREFER_TARGET: {
|
case IntegrationInitialSyncBehavior.PREFER_TARGET: {
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
secretsToAdd[key] = herokuSecrets[key];
|
secretsToAdd[key] = herokuSecrets[key];
|
||||||
} else if (secrets[key]?.value !== herokuSecrets[key]) {
|
} else if (secrets[key]?.value !== herokuSecrets[key]) {
|
||||||
@@ -661,7 +641,7 @@ const syncSecretsHeroku = async ({
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IntegrationSyncBehavior.PREFER_SOURCE: {
|
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
secrets[key] = herokuSecrets[key];
|
secrets[key] = herokuSecrets[key];
|
||||||
secretsToAdd[key] = herokuSecrets[key];
|
secretsToAdd[key] = herokuSecrets[key];
|
||||||
@@ -677,18 +657,10 @@ const syncSecretsHeroku = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (Object.keys(secretsToAdd).length) {
|
if (Object.keys(secretsToAdd).length) {
|
||||||
await createManySecretsRawHelper({
|
await createManySecretsRawFn({
|
||||||
botKey,
|
projectId: integration.projectId,
|
||||||
projectDAL,
|
environment: integration.environment.slug,
|
||||||
secretDAL,
|
path: integration.secretPath,
|
||||||
secretVersionDAL,
|
|
||||||
secretBlindIndexDAL,
|
|
||||||
secretTagDAL,
|
|
||||||
secretVersionTagDAL,
|
|
||||||
folderDAL,
|
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
path: secretPath,
|
|
||||||
secrets: Object.keys(secretsToAdd).map((key) => ({
|
secrets: Object.keys(secretsToAdd).map((key) => ({
|
||||||
secretName: key,
|
secretName: key,
|
||||||
secretValue: secretsToAdd[key],
|
secretValue: secretsToAdd[key],
|
||||||
@@ -698,27 +670,19 @@ const syncSecretsHeroku = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (Object.keys(secretsToUpdate).length) {
|
if (Object.keys(secretsToUpdate).length) {
|
||||||
// await updateManySecretsRawHelper({
|
await updateManySecretsRawFn({
|
||||||
// projectId,
|
projectId: integration.projectId,
|
||||||
// environment,
|
environment: integration.environment.slug,
|
||||||
// path: secretPath,
|
path: integration.secretPath,
|
||||||
// secrets: Object.keys(secretsToUpdate).map((key) => ({
|
secrets: Object.keys(secretsToUpdate).map((key) => ({
|
||||||
// secretName: key,
|
secretName: key,
|
||||||
// secretValue: secretsToUpdate[key],
|
secretValue: secretsToUpdate[key],
|
||||||
// type: SecretType.Shared,
|
type: SecretType.Shared,
|
||||||
// secretComment: ""
|
secretComment: ""
|
||||||
// })),
|
}))
|
||||||
// botKey, // TODO: consider getting botKey inside this fn
|
});
|
||||||
// projectDAL,
|
}
|
||||||
// secretDAL,
|
|
||||||
// secretVersionDAL,
|
|
||||||
// secretBlindIndexDAL,
|
|
||||||
// secretTagDAL,
|
|
||||||
// secretVersionTagDAL,
|
|
||||||
// folderDAL
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
await request.patch(
|
await request.patch(
|
||||||
`${IntegrationUrls.HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
`${IntegrationUrls.HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||||
@@ -3053,18 +3017,9 @@ const syncSecretsHasuraCloud = async ({
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const syncIntegrationSecrets = async ({
|
export const syncIntegrationSecrets = async ({
|
||||||
projectDAL,
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
integrationDAL,
|
integrationDAL,
|
||||||
secretDAL,
|
|
||||||
secretVersionDAL,
|
|
||||||
secretBlindIndexDAL,
|
|
||||||
secretTagDAL,
|
|
||||||
secretVersionTagDAL,
|
|
||||||
folderDAL,
|
|
||||||
botKey,
|
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
@@ -3072,19 +3027,18 @@ export const syncIntegrationSecrets = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
appendices
|
appendices
|
||||||
}: {
|
}: {
|
||||||
projectDAL: TProjectDALFactory;
|
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
|
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
|
integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
|
||||||
secretDAL: TSecretDALFactory;
|
integration: TIntegrations & {
|
||||||
secretVersionDAL: TSecretVersionDALFactory;
|
projectId: string;
|
||||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
environment: {
|
||||||
secretTagDAL: TSecretTagDALFactory;
|
id: string;
|
||||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
name: string;
|
||||||
folderDAL: TSecretFolderDALFactory;
|
slug: string;
|
||||||
botKey: string;
|
};
|
||||||
projectId: string;
|
secretPath: string;
|
||||||
environment: string;
|
};
|
||||||
secretPath: string;
|
|
||||||
integration: TIntegrations;
|
|
||||||
integrationAuth: TIntegrationAuths;
|
integrationAuth: TIntegrationAuths;
|
||||||
secrets: Record<string, { value: string; comment?: string }>;
|
secrets: Record<string, { value: string; comment?: string }>;
|
||||||
accessId: string | null;
|
accessId: string | null;
|
||||||
@@ -3124,18 +3078,9 @@ export const syncIntegrationSecrets = async ({
|
|||||||
break;
|
break;
|
||||||
case Integrations.HEROKU:
|
case Integrations.HEROKU:
|
||||||
await syncSecretsHeroku({
|
await syncSecretsHeroku({
|
||||||
projectDAL,
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
integrationDAL,
|
integrationDAL,
|
||||||
secretDAL,
|
|
||||||
secretVersionDAL,
|
|
||||||
secretBlindIndexDAL,
|
|
||||||
secretTagDAL,
|
|
||||||
secretVersionTagDAL,
|
|
||||||
folderDAL,
|
|
||||||
botKey,
|
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
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 { 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { decryptAsymmetric, generateAsymmetricKeyPair } from "@app/lib/crypto";
|
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "./project-bot-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 = {
|
type TProjectBotServiceFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
@@ -24,29 +25,10 @@ export const projectBotServiceFactory = ({
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService
|
permissionService
|
||||||
}: TProjectBotServiceFactoryDep) => {
|
}: TProjectBotServiceFactoryDep) => {
|
||||||
const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||||
infisicalSymmetricDecrypt({
|
|
||||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
|
||||||
iv: bot.iv,
|
|
||||||
tag: bot.tag,
|
|
||||||
ciphertext: bot.encryptedPrivateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const getBotKey = async (projectId: string) => {
|
const getBotKey = async (projectId: string) => {
|
||||||
const bot = await projectBotDAL.findOne({ projectId });
|
return getBotKeyFn(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
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const findBotByProjectId = async ({
|
const findBotByProjectId = async ({
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ import {
|
|||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { groupBy, unique } from "@app/lib/fn";
|
import { groupBy, unique } from "@app/lib/fn";
|
||||||
|
|
||||||
|
import { getBotKeyFnFactory } from "../project-bot/project-bot-fns";
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretDALFactory } from "./secret-dal";
|
import { TSecretDALFactory } from "./secret-dal";
|
||||||
import {
|
import {
|
||||||
TCreateManySecretsRawHelper,
|
TCreateManySecretsRawFn,
|
||||||
|
TCreateManySecretsRawFnFactory,
|
||||||
TFnSecretBlindIndexCheck,
|
TFnSecretBlindIndexCheck,
|
||||||
TFnSecretBulkInsert,
|
TFnSecretBulkInsert,
|
||||||
TFnSecretBulkUpdate,
|
TFnSecretBulkUpdate,
|
||||||
@@ -405,112 +407,114 @@ export const fnSecretBulkUpdate = async ({
|
|||||||
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createManySecretsRawHelper = async ({
|
export const createManySecretsRawFnFactory = ({
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
path: secretPath,
|
|
||||||
secrets,
|
|
||||||
userId,
|
|
||||||
botKey, // TODO: consider getting botKey inside this fn
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretBlindIndexDAL,
|
secretBlindIndexDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
folderDAL
|
folderDAL
|
||||||
}: TCreateManySecretsRawHelper) => {
|
}: TCreateManySecretsRawFnFactory) => {
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
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);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
|
||||||
const folderId = folder.id;
|
|
||||||
|
|
||||||
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Create secret" });
|
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||||
|
const folderId = folder.id;
|
||||||
|
|
||||||
// insert operation
|
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
||||||
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
|
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Create secret" });
|
||||||
inputSecrets: secrets,
|
|
||||||
folderId,
|
|
||||||
isNew: true,
|
|
||||||
blindIndexCfg,
|
|
||||||
secretDAL
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputSecrets = await Promise.all(
|
// insert operation
|
||||||
secrets.map(async (secret) => {
|
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
|
||||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
|
inputSecrets: secrets,
|
||||||
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,
|
folderId,
|
||||||
secretDAL,
|
isNew: true,
|
||||||
secretVersionDAL,
|
blindIndexCfg,
|
||||||
secretTagDAL,
|
secretDAL
|
||||||
secretVersionTagDAL,
|
});
|
||||||
tx
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
export const updateManySecretsRawFnFactory = ({
|
||||||
|
|
||||||
// updateManySecretsRawFnFactory
|
|
||||||
|
|
||||||
// updateManySecretsRawHelper
|
|
||||||
|
|
||||||
export const updateManySecretsRawFnFactory = async ({
|
|
||||||
// TODO: refactor
|
|
||||||
botKey,
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretBlindIndexDAL,
|
secretBlindIndexDAL,
|
||||||
@@ -518,14 +522,16 @@ export const updateManySecretsRawFnFactory = async ({
|
|||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
folderDAL
|
folderDAL
|
||||||
}: TUpdateManySecretsRawFnFactory) => {
|
}: TUpdateManySecretsRawFnFactory) => {
|
||||||
|
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||||
const updateManySecretsRawFn = async ({
|
const updateManySecretsRawFn = async ({
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
secrets, // accept instead ciphertext secrets
|
secrets, // consider accepting instead ciphertext secrets
|
||||||
userId
|
userId
|
||||||
}: TUpdateManySecretsRawFn) => {
|
}: TUpdateManySecretsRawFn): Promise<Array<TSecrets & { _id: string }>> => {
|
||||||
// TODO: fetch botKey from here
|
const botKey = await getBotKeyFn(projectId);
|
||||||
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { BadRequestError } from "@app/lib/errors";
|
|||||||
import { isSamePath } from "@app/lib/fn";
|
import { isSamePath } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
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 { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-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 { interpolateSecrets } from "./secret-fns";
|
||||||
import { TCreateSecretReminderDTO, THandleReminderDTO, TRemoveSecretReminderDTO } from "./secret-types";
|
import { TCreateSecretReminderDTO, THandleReminderDTO, TRemoveSecretReminderDTO } from "./secret-types";
|
||||||
|
|
||||||
// import { updateManySecretsRawFnFactory } from "@app/services/secret/secret-fns";
|
|
||||||
|
|
||||||
export type TSecretQueueFactory = ReturnType<typeof secretQueueFactory>;
|
export type TSecretQueueFactory = ReturnType<typeof secretQueueFactory>;
|
||||||
|
|
||||||
type TSecretQueueFactoryDep = {
|
type TSecretQueueFactoryDep = {
|
||||||
@@ -44,6 +44,7 @@ type TSecretQueueFactoryDep = {
|
|||||||
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
|
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
projectDAL: TProjectDALFactory;
|
projectDAL: TProjectDALFactory;
|
||||||
|
projectBotDAL: TProjectBotDALFactory;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findOrgByProjectId">;
|
orgDAL: Pick<TOrgDALFactory, "findOrgByProjectId">;
|
||||||
@@ -72,12 +73,35 @@ export const secretQueueFactory = ({
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretBlindIndexDAL,
|
secretBlindIndexDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL
|
secretVersionTagDAL
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: 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) => {
|
const syncIntegrations = async (dto: TGetSecrets) => {
|
||||||
await queueService.queue(QueueName.IntegrationSync, QueueJobs.IntegrationSync, dto, {
|
await queueService.queue(QueueName.IntegrationSync, QueueJobs.IntegrationSync, dto, {
|
||||||
attempts: 5,
|
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({
|
await syncIntegrationSecrets({
|
||||||
projectDAL,
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
integrationDAL,
|
integrationDAL,
|
||||||
secretDAL,
|
|
||||||
secretVersionDAL,
|
|
||||||
secretBlindIndexDAL,
|
|
||||||
secretTagDAL,
|
|
||||||
secretVersionTagDAL,
|
|
||||||
folderDAL,
|
|
||||||
botKey,
|
|
||||||
projectId, // service
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
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 { SecretType, TSecretBlindIndexes, TSecrets, TSecretsInsert, TSecretsUpdate } from "@app/db/schemas";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
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 { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-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;
|
projectId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
path: string;
|
path: string;
|
||||||
@@ -264,19 +276,11 @@ export type TCreateManySecretsRawHelper = {
|
|||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
userId?: string; // only relevant for personal secret(s)
|
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 = {
|
export type TUpdateManySecretsRawFnFactory = {
|
||||||
botKey: string;
|
|
||||||
projectDAL: TProjectDALFactory;
|
projectDAL: TProjectDALFactory;
|
||||||
|
projectBotDAL: TProjectBotDALFactory;
|
||||||
secretDAL: TSecretDALFactory;
|
secretDAL: TSecretDALFactory;
|
||||||
secretVersionDAL: TSecretVersionDALFactory;
|
secretVersionDAL: TSecretVersionDALFactory;
|
||||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
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.
|
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>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "npm-proj-1709146141702-0.772936286416932EMIzNi",
|
"name": "frontend",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const useCreateIntegration = () => {
|
|||||||
metadata?: {
|
metadata?: {
|
||||||
secretPrefix?: string;
|
secretPrefix?: string;
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
syncBehavior?: string;
|
initialSyncBehavior?: string;
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
const { data: { integration } } = await apiRequest.post("/api/v1/integration", {
|
const { data: { integration } } = await apiRequest.post("/api/v1/integration", {
|
||||||
|
|||||||
@@ -46,9 +46,3 @@ export enum IntegrationSyncBehavior {
|
|||||||
PREFER_TARGET = "prefer-target",
|
PREFER_TARGET = "prefer-target",
|
||||||
PREFER_SOURCE = "prefer-source"
|
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 Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
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 { 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 { 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 {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -27,16 +34,24 @@ import {
|
|||||||
useGetIntegrationAuthApps,
|
useGetIntegrationAuthApps,
|
||||||
useGetIntegrationAuthById
|
useGetIntegrationAuthById
|
||||||
} from "../../../hooks/api/integrationAuth";
|
} from "../../../hooks/api/integrationAuth";
|
||||||
import { useCreateWsEnvironment, useGetWorkspaceById } from "../../../hooks/api/workspace";
|
import {
|
||||||
import { IntegrationSyncBehavior, syncBehaviors } from "@app/hooks/api/integrations/types";
|
// 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({
|
const schema = yup.object({
|
||||||
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
||||||
secretPath: yup.string().required("Secret path is required"),
|
secretPath: yup.string().required("Secret path is required"),
|
||||||
targetApp: yup.string().required("Heroku app is required"),
|
targetApp: yup.string().required("Heroku app is required"),
|
||||||
syncBehavior: yup
|
initialSyncBehavior: yup
|
||||||
.string()
|
.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")
|
.required("Initial sync behavior is required")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,7 +64,7 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
secretPath: "/",
|
secretPath: "/",
|
||||||
syncBehavior: IntegrationSyncBehavior.PREFER_SOURCE
|
initialSyncBehavior: IntegrationSyncBehavior.PREFER_SOURCE
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,7 +72,7 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
|
|
||||||
|
|
||||||
const { mutateAsync } = useCreateIntegration();
|
const { mutateAsync } = useCreateIntegration();
|
||||||
const { mutateAsync: mutateAsyncEnv } = useCreateWsEnvironment();
|
// const { mutateAsync: mutateAsyncEnv } = useCreateWsEnvironment();
|
||||||
|
|
||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
@@ -67,14 +82,14 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: integrationAuthPipelineCouplings } = useGetIntegrationAuthHerokuPipelines({
|
// const { data: integrationAuthPipelineCouplings } = useGetIntegrationAuthHerokuPipelines({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
// integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
// });
|
||||||
|
|
||||||
const [uniquePipelines, setUniquePipelines] = useState<Pipeline[]>();
|
// const [uniquePipelines, setUniquePipelines] = useState<Pipeline[]>();
|
||||||
const [selectedPipeline, setSelectedPipeline] = useState("");
|
// const [selectedPipeline, setSelectedPipeline] = useState("");
|
||||||
const [selectedPipelineApps, setSelectedPipelineApps] = useState<App[]>();
|
// const [selectedPipelineApps, setSelectedPipelineApps] = useState<App[]>();
|
||||||
const [integrationType, setIntegrationType] = useState("App");
|
// const [integrationType, setIntegrationType] = useState("App");
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@@ -84,37 +99,37 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (integrationAuthPipelineCouplings) {
|
// if (integrationAuthPipelineCouplings) {
|
||||||
const uniquePipelinesConst = Array.from(
|
// const uniquePipelinesConst = Array.from(
|
||||||
new Set(
|
// new Set(
|
||||||
integrationAuthPipelineCouplings
|
// integrationAuthPipelineCouplings
|
||||||
.map(({ pipeline: { pipelineId, name } }) => ({
|
// .map(({ pipeline: { pipelineId, name } }) => ({
|
||||||
name,
|
// name,
|
||||||
pipelineId
|
// pipelineId
|
||||||
}))
|
// }))
|
||||||
.map((obj) => JSON.stringify(obj))
|
// .map((obj) => JSON.stringify(obj))
|
||||||
)).map((str) => JSON.parse(str)) as { pipelineId: string; name: string }[]
|
// )).map((str) => JSON.parse(str)) as { pipelineId: string; name: string }[]
|
||||||
|
|
||||||
[... (new Set())]
|
// [... (new Set())]
|
||||||
setUniquePipelines(uniquePipelinesConst);
|
// setUniquePipelines(uniquePipelinesConst);
|
||||||
if (uniquePipelinesConst) {
|
// if (uniquePipelinesConst) {
|
||||||
if (uniquePipelinesConst!.length > 0) {
|
// if (uniquePipelinesConst!.length > 0) {
|
||||||
setSelectedPipeline(uniquePipelinesConst![0].name);
|
// setSelectedPipeline(uniquePipelinesConst![0].name);
|
||||||
} else {
|
// } else {
|
||||||
setSelectedPipeline("none");
|
// setSelectedPipeline("none");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}, [integrationAuthPipelineCouplings]);
|
// }, [integrationAuthPipelineCouplings]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (integrationAuthPipelineCouplings) {
|
// if (integrationAuthPipelineCouplings) {
|
||||||
setSelectedPipelineApps(integrationAuthApps?.filter(app => integrationAuthPipelineCouplings
|
// setSelectedPipelineApps(integrationAuthApps?.filter(app => integrationAuthPipelineCouplings
|
||||||
.filter((pipelineCoupling) => pipelineCoupling.pipeline.name === selectedPipeline)
|
// .filter((pipelineCoupling) => pipelineCoupling.pipeline.name === selectedPipeline)
|
||||||
.map(coupling => coupling.app.appId).includes(String(app.appId))))
|
// .map(coupling => coupling.app.appId).includes(String(app.appId))))
|
||||||
}
|
// }
|
||||||
}, [selectedPipeline]);
|
// }, [selectedPipeline]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
@@ -167,10 +182,9 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
const onFormSubmit = async ({
|
const onFormSubmit = async ({
|
||||||
selectedSourceEnvironment: sce,
|
|
||||||
secretPath,
|
secretPath,
|
||||||
targetApp,
|
targetApp,
|
||||||
syncBehavior,
|
initialSyncBehavior,
|
||||||
}: FormData) => {
|
}: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (!integrationAuth?.id) return;
|
if (!integrationAuth?.id) return;
|
||||||
@@ -184,7 +198,7 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
sourceEnvironment: selectedSourceEnvironment,
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
secretPath,
|
secretPath,
|
||||||
metadata: {
|
metadata: {
|
||||||
syncBehavior
|
initialSyncBehavior
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -314,7 +328,7 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="syncBehavior"
|
name="initialSyncBehavior"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Initial Sync Behavior"
|
label="Initial Sync Behavior"
|
||||||
@@ -322,7 +336,7 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
>
|
||||||
<Select {...field} onValueChange={(e) => onChange(e)} className="w-full">
|
<Select {...field} onValueChange={(e) => onChange(e)} className="w-full">
|
||||||
{syncBehaviors.map((b) => {
|
{initialSyncBehaviors.map((b) => {
|
||||||
return (
|
return (
|
||||||
<SelectItem value={b.value} key={`sync-behavior-${b.value}`}>
|
<SelectItem value={b.value} key={`sync-behavior-${b.value}`}>
|
||||||
{b.label}
|
{b.label}
|
||||||
|
|||||||
Reference in New Issue
Block a user