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