Draft refactor core secrets fn into reusable factories

This commit is contained in:
Tuan Dang
2024-03-08 09:06:03 -08:00
parent 0b98feea50
commit 8ac7a29893
5 changed files with 177 additions and 143 deletions
@@ -32,7 +32,10 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { TIntegrationDALFactory } from "../integration/integration-dal"; import { TIntegrationDALFactory } from "../integration/integration-dal";
import { createManySecretsRawHelper, updateManySecretsRawHelper } from "../secret/secret-fns"; import {
createManySecretsRawHelper
// updateManySecretsRawHelper
} from "../secret/secret-fns";
import { Integrations, IntegrationSyncBehavior, IntegrationUrls } from "./integration-list"; 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>) =>
@@ -589,7 +592,6 @@ const syncSecretsAWSSecretManager = async ({
/** /**
* Sync/push [secrets] to Heroku app named [integration.app] * Sync/push [secrets] to Heroku app named [integration.app]
* server.services...
*/ */
const syncSecretsHeroku = async ({ const syncSecretsHeroku = async ({
projectDAL, projectDAL,
@@ -609,7 +611,7 @@ const syncSecretsHeroku = async ({
accessToken accessToken
}: { }: {
projectDAL: TProjectDALFactory; projectDAL: TProjectDALFactory;
integrationDAL: TIntegrationDALFactory; integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
secretDAL: TSecretDALFactory; secretDAL: TSecretDALFactory;
secretVersionDAL: TSecretVersionDALFactory; secretVersionDAL: TSecretVersionDALFactory;
secretBlindIndexDAL: TSecretBlindIndexDALFactory; secretBlindIndexDAL: TSecretBlindIndexDALFactory;
@@ -696,27 +698,27 @@ const syncSecretsHeroku = async ({
}); });
} }
if (Object.keys(secretsToUpdate).length) { // if (Object.keys(secretsToUpdate).length) {
await updateManySecretsRawHelper({ // await updateManySecretsRawHelper({
projectId, // projectId,
environment, // environment,
path: secretPath, // path: 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 // botKey, // TODO: consider getting botKey inside this fn
projectDAL, // projectDAL,
secretDAL, // secretDAL,
secretVersionDAL, // secretVersionDAL,
secretBlindIndexDAL, // secretBlindIndexDAL,
secretTagDAL, // secretTagDAL,
secretVersionTagDAL, // secretVersionTagDAL,
folderDAL // 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`,
@@ -3071,7 +3073,7 @@ export const syncIntegrationSecrets = async ({
appendices appendices
}: { }: {
projectDAL: TProjectDALFactory; projectDAL: TProjectDALFactory;
integrationDAL: TIntegrationDALFactory; integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
secretDAL: TSecretDALFactory; secretDAL: TSecretDALFactory;
secretVersionDAL: TSecretVersionDALFactory; secretVersionDAL: TSecretVersionDALFactory;
secretBlindIndexDAL: TSecretBlindIndexDALFactory; secretBlindIndexDAL: TSecretBlindIndexDALFactory;
+123 -107
View File
@@ -25,7 +25,8 @@ import {
TFnSecretBlindIndexCheck, TFnSecretBlindIndexCheck,
TFnSecretBulkInsert, TFnSecretBulkInsert,
TFnSecretBulkUpdate, TFnSecretBulkUpdate,
TUpdateManySecretsRawHelper TUpdateManySecretsRawFn,
TUpdateManySecretsRawFnFactory
} from "./secret-types"; } from "./secret-types";
export const generateSecretBlindIndexBySalt = async (secretName: string, secretBlindIndexDoc: TSecretBlindIndexes) => { export const generateSecretBlindIndexBySalt = async (secretName: string, secretBlindIndexDoc: TSecretBlindIndexes) => {
@@ -500,13 +501,15 @@ export const createManySecretsRawHelper = async ({
return newSecrets; return newSecrets;
}; };
export const updateManySecretsRawHelper = async ({ // TOOD: potentially convert raw stuff
projectId,
environment, // updateManySecretsRawFnFactory
path: secretPath,
secrets, // updateManySecretsRawHelper
userId,
botKey, // TODO: consider getting botKey inside this fn export const updateManySecretsRawFnFactory = async ({
// TODO: refactor
botKey,
projectDAL, projectDAL,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
@@ -514,114 +517,127 @@ export const updateManySecretsRawHelper = async ({
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
folderDAL folderDAL
}: TUpdateManySecretsRawHelper) => { }: TUpdateManySecretsRawFnFactory) => {
await projectDAL.checkProjectUpgradeStatus(projectId); const updateManySecretsRawFn = async ({
projectId,
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); environment,
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Update secret" }); path: secretPath,
const folderId = folder.id; secrets, // accept instead ciphertext secrets
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets: secrets,
folderId,
isNew: false,
blindIndexCfg,
secretDAL,
userId userId
}); }: TUpdateManySecretsRawFn) => {
// TODO: fetch botKey from here
const inputSecrets = await Promise.all( await projectDAL.checkProjectUpgradeStatus(projectId);
secrets.map(async (secret) => {
if (secret.newSecretName === "") {
throw new BadRequestError({ message: "New secret name cannot be empty" });
}
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey); if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Update secret" });
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey); const folderId = folder.id;
if (secret.type === SecretType.Personal) { const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!userId) throw new BadRequestError({ message: "Missing user id for personal secret" }); if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const sharedExist = await secretDAL.findOne({ const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
secretBlindIndex: keyName2BlindIndex[secret.secretName], inputSecrets: secrets,
folderId, folderId,
type: SecretType.Shared isNew: false,
}); blindIndexCfg,
secretDAL,
userId
});
if (!sharedExist) const inputSecrets = await Promise.all(
throw new BadRequestError({ secrets.map(async (secret) => {
message: "Failed to update personal secret override for no corresponding shared secret" if (secret.newSecretName === "") {
throw new BadRequestError({ message: "New secret name cannot be empty" });
}
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 (secret.newSecretName) throw new BadRequestError({ message: "Personal secret cannot change the key name" }); if (!sharedExist)
} throw new BadRequestError({
message: "Failed to update personal secret override for no corresponding shared secret"
});
const tags = secret.tags ? await secretTagDAL.findManyTagsById(projectId, secret.tags) : []; if (secret.newSecretName)
if ((secret.tags || []).length !== tags.length) throw new BadRequestError({ message: "Tag not found" }); throw new BadRequestError({ message: "Personal secret cannot change the key name" });
return {
type: secret.type,
userId: secret.type === SecretType.Personal ? userId : null,
secretName: secret.secretName,
newSecretName: secret.newSecretName,
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 tagIds = inputSecrets.flatMap(({ tags = [] }) => tags);
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
if (tagIds.length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
// now find any secret that needs to update its name
// same process as above
const nameUpdatedSecrets = inputSecrets.filter(({ newSecretName }) => Boolean(newSecretName));
const { keyName2BlindIndex: newKeyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets: nameUpdatedSecrets,
folderId,
isNew: true,
blindIndexCfg,
secretDAL
});
const updatedSecrets = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({
folderId,
projectId,
tx,
inputSecrets: inputSecrets.map(({ secretName, newSecretName, ...el }) => ({
filter: { secretBlindIndex: keyName2BlindIndex[secretName], type: SecretType.Shared },
data: {
...el,
folderId,
secretBlindIndex:
newSecretName && newKeyName2BlindIndex[newSecretName]
? newKeyName2BlindIndex[newSecretName]
: keyName2BlindIndex[secretName],
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
} }
})),
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL
})
);
return updatedSecrets; 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,
newSecretName: secret.newSecretName,
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 tagIds = inputSecrets.flatMap(({ tags = [] }) => tags);
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
if (tagIds.length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
// now find any secret that needs to update its name
// same process as above
const nameUpdatedSecrets = inputSecrets.filter(({ newSecretName }) => Boolean(newSecretName));
const { keyName2BlindIndex: newKeyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets: nameUpdatedSecrets,
folderId,
isNew: true,
blindIndexCfg,
secretDAL
});
const updatedSecrets = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({
folderId,
projectId,
tx,
inputSecrets: inputSecrets.map(({ secretName, newSecretName, ...el }) => ({
filter: { secretBlindIndex: keyName2BlindIndex[secretName], type: SecretType.Shared },
data: {
...el,
folderId,
secretBlindIndex:
newSecretName && newKeyName2BlindIndex[newSecretName]
? newKeyName2BlindIndex[newSecretName]
: keyName2BlindIndex[secretName],
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
}
})),
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL
})
);
return updatedSecrets;
};
return updateManySecretsRawFn;
}; };
+14 -1
View File
@@ -29,6 +29,8 @@ 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 = {
@@ -318,6 +320,17 @@ export const secretQueueFactory = ({
}); });
} }
// const updateManySecretsRawFn = updateManySecretsRawFnFactory({
// botKey, // can move this out
// projectDAL,
// secretDAL,
// secretVersionDAL,
// secretBlindIndexDAL,
// secretTagDAL,
// secretVersionTagDAL,
// folderDAL
// });
await syncIntegrationSecrets({ await syncIntegrationSecrets({
projectDAL, projectDAL,
integrationDAL, integrationDAL,
@@ -328,7 +341,7 @@ export const secretQueueFactory = ({
secretVersionTagDAL, secretVersionTagDAL,
folderDAL, folderDAL,
botKey, botKey,
projectId, projectId, // service
environment, environment,
secretPath, secretPath,
integration, integration,
+13 -10
View File
@@ -274,7 +274,18 @@ export type TCreateManySecretsRawHelper = {
folderDAL: TSecretFolderDALFactory; folderDAL: TSecretFolderDALFactory;
}; };
export type TUpdateManySecretsRawHelper = { export type TUpdateManySecretsRawFnFactory = {
botKey: string;
projectDAL: TProjectDALFactory;
secretDAL: TSecretDALFactory;
secretVersionDAL: TSecretVersionDALFactory;
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
secretTagDAL: TSecretTagDALFactory;
secretVersionTagDAL: TSecretVersionTagDALFactory;
folderDAL: TSecretFolderDALFactory;
};
export type TUpdateManySecretsRawFn = {
projectId: string; projectId: string;
environment: string; environment: string;
path: string; path: string;
@@ -292,13 +303,5 @@ export type TUpdateManySecretsRawHelper = {
source?: string; source?: string;
}; };
}[]; }[];
userId?: string; // only relevant for personal secret(s) userId?: string;
botKey: string;
projectDAL: TProjectDALFactory;
secretDAL: TSecretDALFactory;
secretVersionDAL: TSecretVersionDALFactory;
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
secretTagDAL: TSecretTagDALFactory;
secretVersionTagDAL: TSecretVersionTagDALFactory;
folderDAL: TSecretFolderDALFactory;
}; };