mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 12:28:54 +00:00
Refactor migration to work with conflict/merge update logic
This commit is contained in:
+24
-15
@@ -5,7 +5,6 @@ import {
|
|||||||
SecretApprovalRequestsSecretsSchema,
|
SecretApprovalRequestsSecretsSchema,
|
||||||
TableName,
|
TableName,
|
||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
TSecretApprovalRequestsSecretsUpdate,
|
|
||||||
TSecretTags
|
TSecretTags
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
@@ -17,22 +16,32 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret);
|
const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret);
|
||||||
const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag);
|
const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag);
|
||||||
|
|
||||||
const bulkUpdateNoVersionIncrement = async (
|
const bulkUpdateNoVersionIncrement = async (data: TSecretApprovalRequestsSecrets[], tx?: Knex) => {
|
||||||
data: Array<{ filter: Partial<TSecretApprovalRequestsSecrets>; data: TSecretApprovalRequestsSecretsUpdate }>,
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
const secs = await Promise.all(
|
const existingApprovalSecrets = await secretApprovalRequestSecretOrm.find(
|
||||||
data.map(async ({ filter, data: updateData }) => {
|
{
|
||||||
const [doc] = await (tx || db)(TableName.SecretApprovalRequestSecret)
|
$in: {
|
||||||
.where(filter)
|
id: data.map((el) => el.id)
|
||||||
.update(updateData)
|
}
|
||||||
.returning("*");
|
},
|
||||||
if (!doc) throw new BadRequestError({ message: "Failed to update document" });
|
{ tx }
|
||||||
return doc;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
return secs;
|
|
||||||
|
if (existingApprovalSecrets.length !== data.length) {
|
||||||
|
throw new BadRequestError({ message: "Some of the secret approvals do not exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedApprovalSecrets = await (tx || db)(TableName.SecretApprovalRequestSecret)
|
||||||
|
.insert(data)
|
||||||
|
.onConflict("id") // this will cause a conflict then merge the data
|
||||||
|
.merge() // Merge the data with the existing data
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
if (!updatedApprovalSecrets || updatedApprovalSecrets.length === 0) {
|
||||||
|
throw new BadRequestError({ message: "Failed to bulk update secret approvals" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedApprovalSecrets;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "bulk update secret" });
|
throw new DatabaseError({ error, name: "bulk update secret" });
|
||||||
}
|
}
|
||||||
|
|||||||
+199
-70
@@ -1,46 +1,45 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TProjectKeys } from "@app/db/schemas";
|
import {
|
||||||
import { logger } from "@app/lib/logger";
|
SecretApprovalRequestsSecretsSchema,
|
||||||
|
SecretsSchema,
|
||||||
|
SecretVersionsSchema,
|
||||||
|
TProjectKeys,
|
||||||
|
TSecretApprovalRequestsSecrets,
|
||||||
|
TSecrets,
|
||||||
|
TSecretVersions
|
||||||
|
} from "@app/db/schemas";
|
||||||
|
|
||||||
import { decryptAsymmetric } from "../crypto";
|
import { decryptAsymmetric } from "../crypto";
|
||||||
|
|
||||||
export enum SecretDocType {
|
const DecryptedValuesSchema = z.object({
|
||||||
Secret = "secret",
|
|
||||||
SecretVersion = "secretVersion",
|
|
||||||
ApprovalSecret = "approvalSecret"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TPartialSecret {
|
|
||||||
id: string;
|
|
||||||
secretKeyCiphertext: string;
|
|
||||||
secretKeyIV: string;
|
|
||||||
secretKeyTag: string;
|
|
||||||
|
|
||||||
secretValueCiphertext: string;
|
|
||||||
secretValueIV: string;
|
|
||||||
secretValueTag: string;
|
|
||||||
|
|
||||||
secretCommentCiphertext?: string | null;
|
|
||||||
secretCommentIV?: string | null;
|
|
||||||
secretCommentTag?: string | null;
|
|
||||||
|
|
||||||
docType: SecretDocType;
|
|
||||||
keyEncoding: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PartialDecryptedSecretSchema = z.object({
|
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
secretKey: z.string(),
|
secretKey: z.string(),
|
||||||
secretValue: z.string(),
|
secretValue: z.string(),
|
||||||
secretComment: z.string().optional(),
|
secretComment: z.string().optional()
|
||||||
|
|
||||||
docType: z.nativeEnum(SecretDocType)
|
|
||||||
});
|
});
|
||||||
export type TPartialDecryptedSecret = z.infer<typeof PartialDecryptedSecretSchema>;
|
|
||||||
|
|
||||||
const decryptSecret = ({
|
const DecryptedSecretSchema = z.object({
|
||||||
|
decrypted: DecryptedValuesSchema,
|
||||||
|
original: SecretsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
const DecryptedSecretVersionsSchema = z.object({
|
||||||
|
decrypted: DecryptedValuesSchema,
|
||||||
|
original: SecretVersionsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DecryptedSecretApprovalsSchema = z.object({
|
||||||
|
decrypted: DecryptedValuesSchema,
|
||||||
|
original: SecretApprovalRequestsSecretsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DecryptedSecret = z.infer<typeof DecryptedSecretSchema>;
|
||||||
|
export type DecryptedSecretVersions = z.infer<typeof DecryptedSecretVersionsSchema>;
|
||||||
|
export type DecryptedSecretApprovals = z.infer<typeof DecryptedSecretApprovalsSchema>;
|
||||||
|
|
||||||
|
const decryptCipher = ({
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
@@ -60,8 +59,62 @@ const decryptSecret = ({
|
|||||||
return cleartext;
|
return cleartext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDecryptedValues = ({
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
key
|
||||||
|
}: {
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext?: string | null;
|
||||||
|
secretCommentIV?: string | null;
|
||||||
|
secretCommentTag?: string | null;
|
||||||
|
key: string | Buffer;
|
||||||
|
}) => {
|
||||||
|
const secretKey = decryptCipher({
|
||||||
|
ciphertext: secretKeyCiphertext,
|
||||||
|
iv: secretKeyIV,
|
||||||
|
tag: secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretValue = decryptCipher({
|
||||||
|
ciphertext: secretValueCiphertext,
|
||||||
|
iv: secretValueIV,
|
||||||
|
tag: secretValueTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretComment =
|
||||||
|
secretCommentCiphertext && secretCommentIV && secretCommentTag
|
||||||
|
? decryptCipher({
|
||||||
|
ciphertext: secretCommentCiphertext,
|
||||||
|
iv: secretCommentIV,
|
||||||
|
tag: secretCommentTag,
|
||||||
|
key
|
||||||
|
})
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
secretKey,
|
||||||
|
secretValue,
|
||||||
|
secretComment
|
||||||
|
};
|
||||||
|
};
|
||||||
export const decryptSecrets = (
|
export const decryptSecrets = (
|
||||||
encryptedSecrets: TPartialSecret[],
|
encryptedSecrets: TSecrets[],
|
||||||
privateKey: string,
|
privateKey: string,
|
||||||
latestKey: TProjectKeys & {
|
latestKey: TProjectKeys & {
|
||||||
sender: {
|
sender: {
|
||||||
@@ -76,47 +129,123 @@ export const decryptSecrets = (
|
|||||||
privateKey
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets: TPartialDecryptedSecret[] = [];
|
const decryptedSecrets: DecryptedSecret[] = [];
|
||||||
|
|
||||||
encryptedSecrets.forEach((encSecret) => {
|
encryptedSecrets.forEach((encSecret) => {
|
||||||
try {
|
const decrypted = getDecryptedValues({
|
||||||
const secretKey = decryptSecret({
|
secretKeyCiphertext: encSecret.secretKeyCiphertext,
|
||||||
ciphertext: encSecret.secretKeyCiphertext,
|
secretKeyIV: encSecret.secretKeyIV,
|
||||||
iv: encSecret.secretKeyIV,
|
secretKeyTag: encSecret.secretKeyTag,
|
||||||
tag: encSecret.secretKeyTag,
|
secretValueCiphertext: encSecret.secretValueCiphertext,
|
||||||
key
|
secretValueIV: encSecret.secretValueIV,
|
||||||
});
|
secretValueTag: encSecret.secretValueTag,
|
||||||
|
secretCommentCiphertext: encSecret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: encSecret.secretCommentIV,
|
||||||
|
secretCommentTag: encSecret.secretCommentTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
const secretValue = decryptSecret({
|
const decryptedSecret: DecryptedSecret = {
|
||||||
ciphertext: encSecret.secretValueCiphertext,
|
decrypted: {
|
||||||
iv: encSecret.secretValueIV,
|
...decrypted,
|
||||||
tag: encSecret.secretValueTag,
|
id: encSecret.id
|
||||||
key
|
},
|
||||||
});
|
original: encSecret
|
||||||
|
};
|
||||||
|
|
||||||
const secretComment =
|
decryptedSecrets.push(DecryptedSecretSchema.parse(decryptedSecret));
|
||||||
encSecret.secretCommentCiphertext && encSecret.secretCommentIV && encSecret.secretCommentTag
|
});
|
||||||
? decryptSecret({
|
|
||||||
ciphertext: encSecret.secretCommentCiphertext,
|
return decryptedSecrets;
|
||||||
iv: encSecret.secretCommentIV,
|
};
|
||||||
tag: encSecret.secretCommentTag,
|
|
||||||
key
|
export const decryptSecretVersions = (
|
||||||
})
|
encryptedSecretVersions: TSecretVersions[],
|
||||||
: "";
|
privateKey: string,
|
||||||
|
latestKey: TProjectKeys & {
|
||||||
const decryptedSecret: TPartialDecryptedSecret = {
|
sender: {
|
||||||
id: encSecret.id,
|
publicKey: string;
|
||||||
secretKey,
|
};
|
||||||
secretValue,
|
}
|
||||||
secretComment,
|
) => {
|
||||||
docType: encSecret.docType
|
const key = decryptAsymmetric({
|
||||||
};
|
ciphertext: latestKey.encryptedKey,
|
||||||
|
nonce: latestKey.nonce,
|
||||||
decryptedSecrets.push(PartialDecryptedSecretSchema.parse(decryptedSecret));
|
publicKey: latestKey.sender.publicKey,
|
||||||
} catch (err) {
|
privateKey
|
||||||
// This is ok, because we check that the decrypted secrets array length is the same as the encrypted secrets input array length.
|
});
|
||||||
logger.error(`[${encSecret.id}] - failed to decrypt`, err);
|
|
||||||
}
|
const decryptedSecrets: DecryptedSecretVersions[] = [];
|
||||||
|
|
||||||
|
encryptedSecretVersions.forEach((encSecret) => {
|
||||||
|
const decrypted = getDecryptedValues({
|
||||||
|
secretKeyCiphertext: encSecret.secretKeyCiphertext,
|
||||||
|
secretKeyIV: encSecret.secretKeyIV,
|
||||||
|
secretKeyTag: encSecret.secretKeyTag,
|
||||||
|
secretValueCiphertext: encSecret.secretValueCiphertext,
|
||||||
|
secretValueIV: encSecret.secretValueIV,
|
||||||
|
secretValueTag: encSecret.secretValueTag,
|
||||||
|
secretCommentCiphertext: encSecret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: encSecret.secretCommentIV,
|
||||||
|
secretCommentTag: encSecret.secretCommentTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedSecret: DecryptedSecretVersions = {
|
||||||
|
decrypted: {
|
||||||
|
...decrypted,
|
||||||
|
id: encSecret.id
|
||||||
|
},
|
||||||
|
original: encSecret
|
||||||
|
};
|
||||||
|
|
||||||
|
decryptedSecrets.push(DecryptedSecretVersionsSchema.parse(decryptedSecret));
|
||||||
|
});
|
||||||
|
|
||||||
|
return decryptedSecrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decryptSecretApprovals = (
|
||||||
|
encryptedSecretApprovals: TSecretApprovalRequestsSecrets[],
|
||||||
|
privateKey: string,
|
||||||
|
latestKey: TProjectKeys & {
|
||||||
|
sender: {
|
||||||
|
publicKey: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const key = decryptAsymmetric({
|
||||||
|
ciphertext: latestKey.encryptedKey,
|
||||||
|
nonce: latestKey.nonce,
|
||||||
|
publicKey: latestKey.sender.publicKey,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedSecrets: DecryptedSecretApprovals[] = [];
|
||||||
|
|
||||||
|
encryptedSecretApprovals.forEach((encSecret) => {
|
||||||
|
const decrypted = getDecryptedValues({
|
||||||
|
secretKeyCiphertext: encSecret.secretKeyCiphertext,
|
||||||
|
secretKeyIV: encSecret.secretKeyIV,
|
||||||
|
secretKeyTag: encSecret.secretKeyTag,
|
||||||
|
secretValueCiphertext: encSecret.secretValueCiphertext,
|
||||||
|
secretValueIV: encSecret.secretValueIV,
|
||||||
|
secretValueTag: encSecret.secretValueTag,
|
||||||
|
secretCommentCiphertext: encSecret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: encSecret.secretCommentIV,
|
||||||
|
secretCommentTag: encSecret.secretCommentTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedSecret: DecryptedSecretApprovals = {
|
||||||
|
decrypted: {
|
||||||
|
...decrypted,
|
||||||
|
id: encSecret.id
|
||||||
|
},
|
||||||
|
original: encSecret
|
||||||
|
};
|
||||||
|
|
||||||
|
decryptedSecrets.push(DecryptedSecretApprovalsSchema.parse(decryptedSecret));
|
||||||
});
|
});
|
||||||
|
|
||||||
return decryptedSecrets;
|
return decryptedSecrets;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
||||||
import { TProjects } from "@app/db/schemas/projects";
|
import { TProjects } from "@app/db/schemas/projects";
|
||||||
@@ -133,7 +132,7 @@ export const orgServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addGhostUser = async (orgId: string, tx?: Knex) => {
|
const addGhostUser = async (orgId: string, tx?: Knex) => {
|
||||||
const email = `ghost-${nanoid(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key.
|
const email = `ghost-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key.
|
||||||
const password = crypto.randomBytes(128).toString("hex");
|
const password = crypto.randomBytes(128).toString("hex");
|
||||||
|
|
||||||
const user = await userDAL.create(
|
const user = await userDAL.create(
|
||||||
|
|||||||
@@ -3,8 +3,13 @@ import {
|
|||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUpgradeStatus,
|
ProjectUpgradeStatus,
|
||||||
ProjectVersion,
|
ProjectVersion,
|
||||||
|
SecretApprovalRequestsSecretsSchema,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
TSecrets
|
SecretsSchema,
|
||||||
|
SecretVersionsSchema,
|
||||||
|
TSecretApprovalRequestsSecrets,
|
||||||
|
TSecrets,
|
||||||
|
TSecretVersions
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||||
@@ -16,7 +21,7 @@ import {
|
|||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { decryptSecrets, SecretDocType, TPartialSecret } from "@app/lib/secret";
|
import { decryptSecretApprovals, decryptSecrets, decryptSecretVersions } from "@app/lib/secret";
|
||||||
import { QueueJobs, QueueName, TQueueJobTypes, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueJobTypes, TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
@@ -115,7 +120,9 @@ export const projectQueueFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get all the secrets within the project (as encrypted)
|
// Get all the secrets within the project (as encrypted)
|
||||||
const secrets: TPartialSecret[] = [];
|
const secrets: TSecrets[] = [];
|
||||||
|
const secretVersions: TSecretVersions[] = [];
|
||||||
|
const approvalSecrets: TSecretApprovalRequestsSecrets[] = [];
|
||||||
for (const folder of projectFolders) {
|
for (const folder of projectFolders) {
|
||||||
const folderSecrets = await secretDAL.find({ folderId: folder.id });
|
const folderSecrets = await secretDAL.find({ folderId: folder.id });
|
||||||
|
|
||||||
@@ -132,22 +139,30 @@ export const projectQueueFactory = ({
|
|||||||
status: RequestState.Open,
|
status: RequestState.Open,
|
||||||
folderId: folder.id
|
folderId: folder.id
|
||||||
});
|
});
|
||||||
const approvalSecrets = await secretApprovalSecretDAL.find({
|
const secretApprovals = await secretApprovalSecretDAL.find({
|
||||||
$in: {
|
$in: {
|
||||||
requestId: approvalRequests.map((el) => el.id)
|
requestId: approvalRequests.map((el) => el.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
secrets.push(...folderSecrets.map((el) => ({ ...el, docType: SecretDocType.Secret })));
|
secrets.push(...folderSecrets);
|
||||||
secrets.push(...folderSecretVersions.map((el) => ({ ...el, docType: SecretDocType.SecretVersion })));
|
secretVersions.push(...folderSecretVersions);
|
||||||
secrets.push(...approvalSecrets.map((el) => ({ ...el, docType: SecretDocType.ApprovalSecret })));
|
approvalSecrets.push(...secretApprovals);
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedSecrets = decryptSecrets(secrets, userPrivateKey, oldProjectKey);
|
const decryptedSecrets = decryptSecrets(secrets, userPrivateKey, oldProjectKey);
|
||||||
|
const decryptedSecretVersions = decryptSecretVersions(secretVersions, userPrivateKey, oldProjectKey);
|
||||||
|
const decryptedApprovalSecrets = decryptSecretApprovals(approvalSecrets, userPrivateKey, oldProjectKey);
|
||||||
|
|
||||||
if (secrets.length !== decryptedSecrets.length) {
|
if (secrets.length !== decryptedSecrets.length) {
|
||||||
throw new Error("Failed to decrypt some secret versions");
|
throw new Error("Failed to decrypt some secret versions");
|
||||||
}
|
}
|
||||||
|
if (secretVersions.length !== decryptedSecretVersions.length) {
|
||||||
|
throw new Error("Failed to decrypt some secret versions");
|
||||||
|
}
|
||||||
|
if (approvalSecrets.length !== decryptedApprovalSecrets.length) {
|
||||||
|
throw new Error("Failed to decrypt some secret approvals");
|
||||||
|
}
|
||||||
|
|
||||||
// Get the existing bot and the existing project keys for the members of the project
|
// Get the existing bot and the existing project keys for the members of the project
|
||||||
const existingBot = await projectBotDAL.findOne({ projectId: project.id }).catch(() => null);
|
const existingBot = await projectBotDAL.findOne({ projectId: project.id }).catch(() => null);
|
||||||
@@ -286,107 +301,137 @@ export const projectQueueFactory = ({
|
|||||||
publicKey: ghostUser.keys.publicKey
|
publicKey: ghostUser.keys.publicKey
|
||||||
});
|
});
|
||||||
|
|
||||||
type TPartialSecret = Pick<
|
const updatedSecrets: TSecrets[] = [];
|
||||||
TSecrets,
|
const updatedSecretVersions: TSecretVersions[] = [];
|
||||||
| "id"
|
const updatedSecretApprovals: TSecretApprovalRequestsSecrets[] = [];
|
||||||
| "secretKeyCiphertext"
|
|
||||||
| "secretKeyIV"
|
|
||||||
| "secretKeyTag"
|
|
||||||
| "secretValueCiphertext"
|
|
||||||
| "secretValueIV"
|
|
||||||
| "secretValueTag"
|
|
||||||
| "secretCommentCiphertext"
|
|
||||||
| "secretCommentIV"
|
|
||||||
| "secretCommentTag"
|
|
||||||
>;
|
|
||||||
|
|
||||||
const updatedSecrets: TPartialSecret[] = [];
|
|
||||||
const updatedSecretVersions: TPartialSecret[] = [];
|
|
||||||
const updatedSecretApprovals: TPartialSecret[] = [];
|
|
||||||
for (const rawSecret of decryptedSecrets) {
|
for (const rawSecret of decryptedSecrets) {
|
||||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.secretKey, botKey);
|
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.decrypted.secretKey, botKey);
|
||||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.secretValue || "", botKey);
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.decrypted.secretValue || "", botKey);
|
||||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.secretComment || "", botKey);
|
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(
|
||||||
|
rawSecret.decrypted.secretComment || "",
|
||||||
|
botKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload: TSecrets = {
|
||||||
|
...rawSecret.original,
|
||||||
|
|
||||||
const payload = {
|
|
||||||
id: rawSecret.id,
|
|
||||||
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
|
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
|
||||||
secretKeyIV: secretKeyEncrypted.iv,
|
secretKeyIV: secretKeyEncrypted.iv,
|
||||||
secretKeyTag: secretKeyEncrypted.tag,
|
secretKeyTag: secretKeyEncrypted.tag,
|
||||||
|
|
||||||
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||||
secretValueIV: secretValueEncrypted.iv,
|
secretValueIV: secretValueEncrypted.iv,
|
||||||
secretValueTag: secretValueEncrypted.tag,
|
secretValueTag: secretValueEncrypted.tag,
|
||||||
|
|
||||||
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||||
secretCommentIV: secretCommentEncrypted.iv,
|
secretCommentIV: secretCommentEncrypted.iv,
|
||||||
secretCommentTag: secretCommentEncrypted.tag
|
secretCommentTag: secretCommentEncrypted.tag
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
if (rawSecret.docType === SecretDocType.Secret) {
|
if (!SecretsSchema.safeParse(payload).success) {
|
||||||
updatedSecrets.push(payload);
|
throw new Error(`Invalid secret payload: ${JSON.stringify(payload)}`);
|
||||||
} else if (rawSecret.docType === SecretDocType.SecretVersion) {
|
|
||||||
updatedSecretVersions.push(payload);
|
|
||||||
} else if (rawSecret.docType === SecretDocType.ApprovalSecret) {
|
|
||||||
updatedSecretApprovals.push(payload);
|
|
||||||
} else {
|
|
||||||
throw new Error("Unknown secret type");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedSecrets.push(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretUpdates = await secretDAL.bulkUpdateNoVersionIncrement(
|
for (const rawSecretVersion of decryptedSecretVersions) {
|
||||||
[
|
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecretVersion.decrypted.secretKey, botKey);
|
||||||
...updatedSecrets.map((secret) => ({
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(
|
||||||
filter: { id: secret.id },
|
rawSecretVersion.decrypted.secretValue || "",
|
||||||
data: {
|
botKey
|
||||||
...secret,
|
);
|
||||||
id: undefined
|
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(
|
||||||
}
|
rawSecretVersion.decrypted.secretComment || "",
|
||||||
}))
|
botKey
|
||||||
],
|
);
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
const secretVersionUpdates = await secretVersionDAL.bulkUpdateNoVersionIncrement(
|
const payload: TSecretVersions = {
|
||||||
[
|
...rawSecretVersion.original,
|
||||||
...updatedSecretVersions.map((version) => ({
|
|
||||||
filter: { id: version.id },
|
|
||||||
data: {
|
|
||||||
...version,
|
|
||||||
id: undefined
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
],
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
const secretApprovalUpdates = await secretApprovalSecretDAL.bulkUpdateNoVersionIncrement(
|
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
|
||||||
[
|
secretKeyIV: secretKeyEncrypted.iv,
|
||||||
...updatedSecretApprovals.map((approval) => ({
|
secretKeyTag: secretKeyEncrypted.tag,
|
||||||
filter: {
|
|
||||||
id: approval.id
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
...approval,
|
|
||||||
id: undefined
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
],
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (secretUpdates.length !== updatedSecrets.length) {
|
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||||
|
secretValueIV: secretValueEncrypted.iv,
|
||||||
|
secretValueTag: secretValueEncrypted.tag,
|
||||||
|
|
||||||
|
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||||
|
secretCommentIV: secretCommentEncrypted.iv,
|
||||||
|
secretCommentTag: secretCommentEncrypted.tag
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
if (!SecretVersionsSchema.safeParse(payload).success) {
|
||||||
|
throw new Error(`Invalid secret version payload: ${JSON.stringify(payload)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedSecretVersions.push(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rawSecretApproval of decryptedApprovalSecrets) {
|
||||||
|
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecretApproval.decrypted.secretKey, botKey);
|
||||||
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(
|
||||||
|
rawSecretApproval.decrypted.secretValue || "",
|
||||||
|
botKey
|
||||||
|
);
|
||||||
|
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(
|
||||||
|
rawSecretApproval.decrypted.secretComment || "",
|
||||||
|
botKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload: TSecretApprovalRequestsSecrets = {
|
||||||
|
...rawSecretApproval.original,
|
||||||
|
|
||||||
|
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
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
if (!SecretApprovalRequestsSecretsSchema.safeParse(payload).success) {
|
||||||
|
throw new Error(`Invalid secret approval payload: ${JSON.stringify(payload)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedSecretApprovals.push(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedSecrets.length !== secrets.length) {
|
||||||
throw new Error("Failed to update some secrets");
|
throw new Error("Failed to update some secrets");
|
||||||
}
|
}
|
||||||
if (secretVersionUpdates.length !== updatedSecretVersions.length) {
|
if (updatedSecretVersions.length !== secretVersions.length) {
|
||||||
throw new Error("Failed to update some secret versions");
|
throw new Error("Failed to update some secret versions");
|
||||||
}
|
}
|
||||||
if (secretApprovalUpdates.length !== updatedSecretApprovals.length) {
|
if (updatedSecretApprovals.length !== approvalSecrets.length) {
|
||||||
throw new Error("Failed to update some secret approvals");
|
throw new Error("Failed to update some secret approvals");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const secretUpdates = await secretDAL.bulkUpdateNoVersionIncrement(updatedSecrets, tx);
|
||||||
|
const secretVersionUpdates = await secretVersionDAL.bulkUpdateNoVersionIncrement(updatedSecretVersions, tx);
|
||||||
|
const secretApprovalUpdates = await secretApprovalSecretDAL.bulkUpdateNoVersionIncrement(
|
||||||
|
updatedSecretApprovals,
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
secretUpdates.length !== updatedSecrets.length ||
|
||||||
|
secretVersionUpdates.length !== updatedSecretVersions.length ||
|
||||||
|
secretApprovalUpdates.length !== updatedSecretApprovals.length
|
||||||
|
) {
|
||||||
|
throw new Error("Parts of the upgrade failed. Some secrets were not updated");
|
||||||
|
}
|
||||||
|
|
||||||
await projectDAL.setProjectUpgradeStatus(data.projectId, null, tx);
|
await projectDAL.setProjectUpgradeStatus(data.projectId, null, tx);
|
||||||
|
|
||||||
// await new Promise((resolve) => setTimeout(resolve, 15_000));
|
// await new Promise((resolve) => setTimeout(resolve, 15_000));
|
||||||
throw new Error("Transaction was successful!");
|
// throw new Error("Transaction was successful!");
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const [project] = await projectDAL
|
const [project] = await projectDAL
|
||||||
|
|||||||
@@ -45,19 +45,32 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const bulkUpdateNoVersionIncrement = async (
|
const bulkUpdateNoVersionIncrement = async (data: TSecrets[], tx?: Knex) => {
|
||||||
data: Array<{ filter: Partial<TSecrets>; data: TSecretsUpdate }>,
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
const secs = await Promise.all(
|
const existingSecrets = await secretOrm.find(
|
||||||
data.map(async ({ filter, data: updateData }) => {
|
{
|
||||||
const [doc] = await (tx || db)(TableName.Secret).where(filter).update(updateData).returning("*");
|
$in: {
|
||||||
if (!doc) throw new BadRequestError({ message: "Failed to update document" });
|
id: data.map((el) => el.id)
|
||||||
return doc;
|
}
|
||||||
})
|
},
|
||||||
|
{ tx }
|
||||||
);
|
);
|
||||||
return secs;
|
|
||||||
|
if (existingSecrets.length !== data.length) {
|
||||||
|
throw new BadRequestError({ message: "Some of the secrets do not exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSecrets = await (tx || db)(TableName.Secret)
|
||||||
|
.insert(data)
|
||||||
|
.onConflict("id") // this will cause a conflict then merge the data
|
||||||
|
.merge() // Merge the data with the existing data
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
if (!updatedSecrets || updatedSecrets.length === 0) {
|
||||||
|
throw new BadRequestError({ message: "Failed to bulk update secret approvals" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedSecrets;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "bulk update secret" });
|
throw new DatabaseError({ error, name: "bulk update secret" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,19 +58,32 @@ export const secretVersionDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const bulkUpdateNoVersionIncrement = async (
|
const bulkUpdateNoVersionIncrement = async (data: TSecretVersions[], tx?: Knex) => {
|
||||||
data: Array<{ filter: Partial<TSecretVersions>; data: TSecretVersionsUpdate }>,
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
const secs = await Promise.all(
|
const existingSecretVersions = await secretVersionOrm.find(
|
||||||
data.map(async ({ filter, data: updateData }) => {
|
{
|
||||||
const [doc] = await (tx || db)(TableName.SecretVersion).where(filter).update(updateData).returning("*");
|
$in: {
|
||||||
if (!doc) throw new BadRequestError({ message: "Failed to update document" });
|
id: data.map((el) => el.id)
|
||||||
return doc;
|
}
|
||||||
})
|
},
|
||||||
|
{ tx }
|
||||||
);
|
);
|
||||||
return secs;
|
|
||||||
|
if (existingSecretVersions.length !== data.length) {
|
||||||
|
throw new BadRequestError({ message: "Some of the secret versions do not exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSecretVersions = await (tx || db)(TableName.SecretVersion)
|
||||||
|
.insert(data)
|
||||||
|
.onConflict("id") // this will cause a conflict then merge the data
|
||||||
|
.merge() // Merge the data with the existing data
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
if (!updatedSecretVersions || updatedSecretVersions.length === 0) {
|
||||||
|
throw new BadRequestError({ message: "Failed to bulk update secret versions" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedSecretVersions;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "bulk update secret" });
|
throw new DatabaseError({ error, name: "bulk update secret" });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user