Update modify secrets api v2 so that fields are optional

This commit is contained in:
Maidul Islam
2023-01-12 15:31:58 -08:00
parent 47fd48b7b0
commit 71f60f1589
4 changed files with 141 additions and 158 deletions
+30 -35
View File
@@ -242,7 +242,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentTag: string; secretCommentTag: string;
} }
const ops = req.body.secrets.map((secret: PatchSecret) => { const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
const { const {
secretKeyCiphertext, secretKeyCiphertext,
secretKeyIV, secretKeyIV,
@@ -254,6 +254,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentIV, secretCommentIV,
secretCommentTag secretCommentTag
} = secret; } = secret;
return ({ return ({
updateOne: { updateOne: {
filter: { _id: new Types.ObjectId(secret.id) }, filter: { _id: new Types.ObjectId(secret.id) },
@@ -280,15 +281,17 @@ export const updateSecrets = async (req: Request, res: Response) => {
} }
}); });
}); });
await Secret.bulkWrite(ops);
const newSecretsObj: { [key: string]: PatchSecret } = {}; await Secret.bulkWrite(updateOperationsToPerform);
const secretModificationsBySecretId: { [key: string]: PatchSecret } = {};
req.body.secrets.forEach((secret: PatchSecret) => { req.body.secrets.forEach((secret: PatchSecret) => {
newSecretsObj[secret.id] = secret; secretModificationsBySecretId[secret.id] = secret;
}); });
await EESecretService.addSecretVersions({ const ListOfSecretsBeforeModifications = req.secrets
secretVersions: req.secrets.map((secret: ISecret) => { const secretVersions = {
secretVersions: ListOfSecretsBeforeModifications.map((secret: ISecret) => {
const { const {
secretKeyCiphertext, secretKeyCiphertext,
secretKeyIV, secretKeyIV,
@@ -298,37 +301,29 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretValueTag, secretValueTag,
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag secretCommentTag,
} = newSecretsObj[secret._id.toString()] } = secretModificationsBySecretId[secret._id.toString()]
return ({ return ({
secret: secret._id, secret: secret._id,
version: secret.version + 1, version: secret.version + 1,
workspace: secret.workspace, workspace: secret.workspace,
type: secret.type, type: secret.type,
environment: secret.environment, environment: secret.environment,
isDeleted: false, secretKeyCiphertext: secretKeyCiphertext ? secretKeyCiphertext : secret.secretKeyCiphertext,
secretKeyCiphertext, secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV,
secretKeyIV, secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag,
secretKeyTag, secretValueCiphertext: secretValueCiphertext ? secretValueCiphertext : secret.secretValueCiphertext,
secretValueCiphertext, secretValueIV: secretValueIV ? secretValueIV : secret.secretValueIV,
secretValueIV, secretValueTag: secretValueTag ? secretValueTag : secret.secretValueTag,
secretValueTag, secretCommentCiphertext: secretCommentCiphertext ? secretCommentCiphertext : secret.secretCommentCiphertext,
...(( secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
secretCommentCiphertext && secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
secretCommentIV &&
secretCommentTag
) ? {
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
} : {
secretCommentCiphertext: '',
secretCommentIV: '',
secretCommentTag: ''
})
}); });
}) })
}); }
await EESecretService.addSecretVersions(secretVersions);
// group secrets into workspaces so updated secrets can // group secrets into workspaces so updated secrets can
@@ -367,9 +362,9 @@ export const updateSecrets = async (req: Request, res: Response) => {
}); });
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: key workspaceId: key
}) })
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
@@ -449,9 +444,9 @@ export const deleteSecrets = async (req: Request, res: Response) => {
}); });
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: key workspaceId: key
}) })
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
+33 -33
View File
@@ -1,11 +1,11 @@
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { import {
Secret, Secret,
ISecret ISecret
} from '../../models'; } from '../../models';
import { import {
SecretSnapshot, SecretSnapshot,
SecretVersion, SecretVersion,
ISecretVersion ISecretVersion
} from '../models'; } from '../models';
@@ -18,7 +18,7 @@ import {
* @param {String} obj.workspaceId * @param {String} obj.workspaceId
* @returns {SecretSnapshot} secretSnapshot - new secret snapshot * @returns {SecretSnapshot} secretSnapshot - new secret snapshot
*/ */
const takeSecretSnapshotHelper = async ({ const takeSecretSnapshotHelper = async ({
workspaceId workspaceId
}: { }: {
workspaceId: string; workspaceId: string;
@@ -48,7 +48,7 @@ import {
{ {
$sort: { version: -1 } $sort: { version: -1 }
} }
]) ])
.exec()) .exec())
.map((s) => s.versionId); .map((s) => s.versionId);
@@ -87,7 +87,7 @@ const addSecretVersionsHelper = async ({
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed to add secret versions'); throw new Error(`Failed to add secret versions [err=${err}]`);
} }
return newSecretVersions; return newSecretVersions;
@@ -125,34 +125,34 @@ const initSecretVersioningHelper = async () => {
{ $set: { version: 1 } } { $set: { version: 1 } }
); );
const unversionedSecrets: ISecret[] = await Secret.aggregate([ const unversionedSecrets: ISecret[] = await Secret.aggregate([
{ {
$lookup: { $lookup: {
from: 'secretversions', from: 'secretversions',
localField: '_id', localField: '_id',
foreignField: 'secret', foreignField: 'secret',
as: 'versions', as: 'versions',
}, },
}, },
{ {
$match: { $match: {
versions: { $size: 0 }, versions: { $size: 0 },
}, },
}, },
]); ]);
if (unversionedSecrets.length > 0) { if (unversionedSecrets.length > 0) {
await addSecretVersionsHelper({ await addSecretVersionsHelper({
secretVersions: unversionedSecrets.map((s, idx) => ({ secretVersions: unversionedSecrets.map((s, idx) => ({
...s, ...s,
secret: s._id, secret: s._id,
version: s.version ? s.version : 1, version: s.version ? s.version : 1,
isDeleted: false, isDeleted: false,
workspace: s.workspace, workspace: s.workspace,
environment: s.environment environment: s.environment
})) }))
}); });
} }
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -162,7 +162,7 @@ const initSecretVersioningHelper = async () => {
} }
export { export {
takeSecretSnapshotHelper, takeSecretSnapshotHelper,
addSecretVersionsHelper, addSecretVersionsHelper,
markDeletedSecretVersionsHelper, markDeletedSecretVersionsHelper,
initSecretVersioningHelper initSecretVersioningHelper
+25 -25
View File
@@ -10,14 +10,14 @@ import {
export interface ISecretVersion { export interface ISecretVersion {
_id: Types.ObjectId; _id: Types.ObjectId;
secret: Types.ObjectId; secret: Types.ObjectId;
version: number; version: number;
workspace: Types.ObjectId; // new workspace: Types.ObjectId; // new
type: string; // new type: string; // new
user: Types.ObjectId; // new user: Types.ObjectId; // new
environment: string; // new environment: string; // new
isDeleted: boolean; isDeleted: boolean;
secretKeyCiphertext: string; secretKeyCiphertext: string;
secretKeyIV: string; secretKeyIV: string;
secretKeyTag: string; secretKeyTag: string;
secretKeyHash: string; secretKeyHash: string;
@@ -28,17 +28,17 @@ export interface ISecretVersion {
} }
const secretVersionSchema = new Schema<ISecretVersion>( const secretVersionSchema = new Schema<ISecretVersion>(
{ {
secret: { // could be deleted secret: { // could be deleted
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'Secret', ref: 'Secret',
required: true required: true
}, },
version: { version: {
type: Number, type: Number,
default: 1, default: 1,
required: true required: true
}, },
workspace: { workspace: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'Workspace', ref: 'Workspace',
@@ -59,12 +59,12 @@ const secretVersionSchema = new Schema<ISecretVersion>(
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD], enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true required: true
}, },
isDeleted: { // consider removing field isDeleted: { // consider removing field
type: Boolean, type: Boolean,
default: false, default: false,
required: true required: true
}, },
secretKeyCiphertext: { secretKeyCiphertext: {
type: String, type: String,
required: true required: true
}, },
@@ -94,10 +94,10 @@ const secretVersionSchema = new Schema<ISecretVersion>(
secretValueHash: { secretValueHash: {
type: String type: String
} }
}, },
{ {
timestamps: true timestamps: true
} }
); );
const SecretVersion = model<ISecretVersion>('SecretVersion', secretVersionSchema); const SecretVersion = model<ISecretVersion>('SecretVersion', secretVersionSchema);
+6 -18
View File
@@ -58,7 +58,7 @@ router.post(
} }
return true; return true;
}), }),
validateRequest, validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt'] acceptedAuthModes: ['jwt']
@@ -95,36 +95,24 @@ router.patch(
if (value.length === 0) throw new Error('secrets cannot be an empty array') if (value.length === 0) throw new Error('secrets cannot be an empty array')
for (const secret of value) { for (const secret of value) {
if ( if (
!secret.id || !secret.id
!secret.secretKeyCiphertext ||
!secret.secretKeyIV ||
!secret.secretKeyTag ||
!secret.secretValueCiphertext ||
!secret.secretValueIV ||
!secret.secretValueTag
) { ) {
throw new Error('secrets array must contain objects that have required secret properties'); throw new Error('Each secret must contain a ID property');
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
// case: update 1 secret // case: update 1 secret
if ( if (
!value.id || !value.id
!value.secretKeyCiphertext ||
!value.secretKeyIV ||
!value.secretKeyTag ||
!value.secretValueCiphertext ||
!value.secretValueIV ||
!value.secretValueTag
) { ) {
throw new Error('secrets object is missing required secret properties'); throw new Error('secret must contain a ID property');
} }
} else { } else {
throw new Error('secrets must be an object or an array of objects') throw new Error('secrets must be an object or an array of objects')
} }
return true; return true;
}), }),
validateRequest, validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt'] acceptedAuthModes: ['jwt']