diff --git a/backend/src/controllers/v2/index.ts b/backend/src/controllers/v2/index.ts index dc6977c91..24ca015ac 100644 --- a/backend/src/controllers/v2/index.ts +++ b/backend/src/controllers/v2/index.ts @@ -1,5 +1,7 @@ import * as workspaceController from './workspaceController'; +import * as secretController from './secretController'; -export { - workspaceController +export { + workspaceController, + secretController } diff --git a/backend/src/controllers/v2/secretController.ts b/backend/src/controllers/v2/secretController.ts index e69de29bb..e29a8d19b 100644 --- a/backend/src/controllers/v2/secretController.ts +++ b/backend/src/controllers/v2/secretController.ts @@ -0,0 +1,138 @@ +import to from "await-to-js"; +import { Request, Response } from "express"; +import mongoose, { Types } from "mongoose"; +import Secret, { ISecret } from "../../models/secret"; +import { CreateSecretRequestBody, ModifySecretRequestBody, SanitizedSecretForCreate, SanitizedSecretModify } from "../../types/secret/types"; +const { ValidationError } = mongoose.Error; +import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors'; +import { AnyBulkWriteOperation } from 'mongodb'; + +export const batchCreateSecrets = async (req: Request, res: Response) => { + const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets; + const { workspaceId, environmentName } = req.params + const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = [] + + secretsToCreate.forEach(rawSecret => { + const safeUpdateFields: SanitizedSecretForCreate = { + secretKeyCiphertext: rawSecret.secretKeyCiphertext, + secretKeyIV: rawSecret.secretKeyIV, + secretKeyTag: rawSecret.secretKeyTag, + secretKeyHash: rawSecret.secretKeyHash, + secretValueCiphertext: rawSecret.secretValueCiphertext, + secretValueIV: rawSecret.secretValueIV, + secretValueTag: rawSecret.secretValueTag, + secretValueHash: rawSecret.secretValueHash, + secretCommentCiphertext: rawSecret.secretCommentCiphertext, + secretCommentIV: rawSecret.secretCommentIV, + secretCommentTag: rawSecret.secretCommentTag, + secretCommentHash: rawSecret.secretCommentHash, + workspace: new Types.ObjectId(workspaceId), + environment: environmentName, + type: rawSecret.type, + user: new Types.ObjectId(req.user._id) + } + + sanitizedSecretesToCreate.push(safeUpdateFields) + }) + + const [bulkCreateError, newlyCreatedSecrets] = await to(Secret.insertMany(sanitizedSecretesToCreate).then()) + if (bulkCreateError) { + if (bulkCreateError instanceof ValidationError) { + throw RouteValidationError({ message: bulkCreateError.message, stack: bulkCreateError.stack }) + } + + throw InternalServerError({ message: "Unable to process your batch create request. Please try again", stack: bulkCreateError.stack }) + } + + res.status(200).send() +} + + +export const createSingleSecret = async (req: Request, res: Response) => { + try { + const secretFromDB = await Secret.findById(req.params.secretId) + return res.status(200).send(secretFromDB); + } catch (e) { + throw BadRequestError({ message: "Unable to find the requested secret" }) + } +} + +export const batchDeleteSecrets = async (req: Request, res: Response) => { + const { workspaceId, environmentName } = req.params + const secretIdsToDelete: string[] = req.body.secretIds + + const [secretIdsUserCanDeleteError, secretIdsUserCanDelete] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) + if (secretIdsUserCanDeleteError) { + throw InternalServerError({ message: `Unable to fetch secrets you own: [error=${secretIdsUserCanDeleteError.message}]` }) + } + + const secretsUserCanDeleteSet: Set = new Set(secretIdsUserCanDelete.map(objectId => objectId._id.toString())); + const deleteOperationsToPerform: AnyBulkWriteOperation[] = [] + + secretIdsToDelete.forEach(secretIdToDelete => { + if (secretsUserCanDeleteSet.has(secretIdToDelete)) { + const deleteOperation = { deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } } } + deleteOperationsToPerform.push(deleteOperation) + } else { + throw RouteValidationError({ message: "You cannot delete secrets that you do not have access to" }) + } + }) + + const [bulkDeleteError, bulkDelete] = await to(Secret.bulkWrite(deleteOperationsToPerform).then()) + if (bulkDeleteError) { + if (bulkDeleteError instanceof ValidationError) { + throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkDeleteError.stack }) + } + throw InternalServerError() + } + + res.status(200).send() +} + +export const batchModifySecrets = async (req: Request, res: Response) => { + const { workspaceId, environmentName } = req.params + const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets; + const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) + if (secretIdsUserCanModifyError) { + throw InternalServerError({ message: "Unable to fetch secrets you own" }) + } + + const secretsUserCanModifySet: Set = new Set(secretIdsUserCanModify.map(objectId => objectId._id.toString())); + const updateOperationsToPerform: any = [] + + + secretsModificationsRequested.forEach(userModifiedSecret => { + if (secretsUserCanModifySet.has(userModifiedSecret._id.toString())) { + const sanitizedSecret: SanitizedSecretModify = { + secretKeyCiphertext: userModifiedSecret.secretKeyCiphertext, + secretKeyIV: userModifiedSecret.secretKeyIV, + secretKeyTag: userModifiedSecret.secretKeyTag, + secretKeyHash: userModifiedSecret.secretKeyHash, + secretValueCiphertext: userModifiedSecret.secretValueCiphertext, + secretValueIV: userModifiedSecret.secretValueIV, + secretValueTag: userModifiedSecret.secretValueTag, + secretValueHash: userModifiedSecret.secretValueHash, + secretCommentCiphertext: userModifiedSecret.secretCommentCiphertext, + secretCommentIV: userModifiedSecret.secretCommentIV, + secretCommentTag: userModifiedSecret.secretCommentTag, + secretCommentHash: userModifiedSecret.secretCommentHash, + } + + const updateOperation = { updateOne: { filter: { _id: userModifiedSecret._id, workspace: workspaceId }, update: { $inc: { version: 1 }, $set: sanitizedSecret } } } + updateOperationsToPerform.push(updateOperation) + } else { + throw UnauthorizedRequestError({ message: "You do not have permission to modify one or more of the requested secrets" }) + } + }) + + const [bulkModificationInfoError, bulkModificationInfo] = await to(Secret.bulkWrite(updateOperationsToPerform).then()) + if (bulkModificationInfoError) { + if (bulkModificationInfoError instanceof ValidationError) { + throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkModificationInfoError.stack }) + } + + throw InternalServerError() + } + + return res.status(200).send() +} \ No newline at end of file diff --git a/backend/src/routes/v2/secret.ts b/backend/src/routes/v2/secret.ts index 8c87db882..477e0039e 100644 --- a/backend/src/routes/v2/secret.ts +++ b/backend/src/routes/v2/secret.ts @@ -1,14 +1,9 @@ -import express, { Request, Response } from 'express'; +import express from 'express'; import { requireAuth, requireWorkspaceAuth, validateRequest } from '../../middleware'; -import { ISecret, Secret } from '../../models'; -import { body, param, query, check } from 'express-validator'; -import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors'; +import { body, param } from 'express-validator'; import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables'; -import { SanitizedSecretModify, CreateSecretRequestBody, SanitizedSecretForCreate, ModifySecretRequestBody } from '../../types/secret/types'; -import to from 'await-to-js'; -import mongoose, { Types } from 'mongoose'; -import { AnyBulkWriteOperation } from 'mongodb'; -const { ValidationError } = mongoose.Error; +import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret/types'; +import { secretController } from '../../controllers/v2'; const router = express.Router(); @@ -26,45 +21,7 @@ router.post( param('environmentName').exists().trim(), body('secrets').exists().isArray().custom((value) => value.every((item: CreateSecretRequestBody) => typeof item === 'object')), validateRequest, - async (req: Request, res: Response) => { - const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets; - const { workspaceId, environmentName } = req.params - const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = [] - - secretsToCreate.forEach(rawSecret => { - const safeUpdateFields: SanitizedSecretForCreate = { - secretKeyCiphertext: rawSecret.secretKeyCiphertext, - secretKeyIV: rawSecret.secretKeyIV, - secretKeyTag: rawSecret.secretKeyTag, - secretKeyHash: rawSecret.secretKeyHash, - secretValueCiphertext: rawSecret.secretValueCiphertext, - secretValueIV: rawSecret.secretValueIV, - secretValueTag: rawSecret.secretValueTag, - secretValueHash: rawSecret.secretValueHash, - secretCommentCiphertext: rawSecret.secretCommentCiphertext, - secretCommentIV: rawSecret.secretCommentIV, - secretCommentTag: rawSecret.secretCommentTag, - secretCommentHash: rawSecret.secretCommentHash, - workspace: new Types.ObjectId(workspaceId), - environment: environmentName, - type: rawSecret.type, - user: new Types.ObjectId(req.user._id) - } - - sanitizedSecretesToCreate.push(safeUpdateFields) - }) - - const [bulkCreateError, newlyCreatedSecrets] = await to(Secret.insertMany(sanitizedSecretesToCreate).then()) - if (bulkCreateError) { - if (bulkCreateError instanceof ValidationError) { - throw RouteValidationError({ message: bulkCreateError.message, stack: bulkCreateError.stack }) - } - - throw InternalServerError({ message: "Unable to process your batch create request. Please try again", stack: bulkCreateError.stack }) - } - - res.status(200).send() - } + secretController.batchCreateSecrets ); /** @@ -76,14 +33,8 @@ router.get( acceptedRoles: [ADMIN, MEMBER], acceptedStatuses: [COMPLETED, GRANTED] }), - validateRequest, async (req: Request, res: Response) => { - try { - const secretFromDB = await Secret.findById(req.params.secretId) - return res.status(200).send(secretFromDB); - } catch (e) { - throw BadRequestError({ message: "Unable to find the requested secret" }) - } - } + validateRequest, + secretController.createSingleSecret ); /** @@ -99,37 +50,9 @@ router.delete( acceptedRoles: [ADMIN, MEMBER], acceptedStatuses: [COMPLETED, GRANTED] }), - validateRequest, async (req: Request, res: Response) => { - const { workspaceId, environmentName } = req.params - const secretIdsToDelete: string[] = req.body.secretIds + validateRequest, + secretController.batchDeleteSecrets - const [secretIdsUserCanDeleteError, secretIdsUserCanDelete] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) - if (secretIdsUserCanDeleteError) { - throw InternalServerError({ message: `Unable to fetch secrets you own: [error=${secretIdsUserCanDeleteError.message}]` }) - } - - const secretsUserCanDeleteSet: Set = new Set(secretIdsUserCanDelete.map(objectId => objectId._id.toString())); - const deleteOperationsToPerform: AnyBulkWriteOperation[] = [] - - secretIdsToDelete.forEach(secretIdToDelete => { - if (secretsUserCanDeleteSet.has(secretIdToDelete)) { - const deleteOperation = { deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } } } - deleteOperationsToPerform.push(deleteOperation) - } else { - throw RouteValidationError({ message: "You cannot delete secrets that you do not have access to" }) - } - }) - - const [bulkDeleteError, bulkDelete] = await to(Secret.bulkWrite(deleteOperationsToPerform).then()) - if (bulkDeleteError) { - if (bulkDeleteError instanceof ValidationError) { - throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkDeleteError.stack }) - } - throw InternalServerError() - } - - res.status(200).send() - } ); /** @@ -145,53 +68,8 @@ router.patch( acceptedRoles: [ADMIN, MEMBER], acceptedStatuses: [COMPLETED, GRANTED] }), - validateRequest, async (req: Request, res: Response) => { - const { workspaceId, environmentName } = req.params - const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets; - const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) - if (secretIdsUserCanModifyError) { - throw InternalServerError({ message: "Unable to fetch secrets you own" }) - } - - const secretsUserCanModifySet: Set = new Set(secretIdsUserCanModify.map(objectId => objectId._id.toString())); - const updateOperationsToPerform: any = [] - - - secretsModificationsRequested.forEach(userModifiedSecret => { - if (secretsUserCanModifySet.has(userModifiedSecret._id.toString())) { - const sanitizedSecret: SanitizedSecretModify = { - secretKeyCiphertext: userModifiedSecret.secretKeyCiphertext, - secretKeyIV: userModifiedSecret.secretKeyIV, - secretKeyTag: userModifiedSecret.secretKeyTag, - secretKeyHash: userModifiedSecret.secretKeyHash, - secretValueCiphertext: userModifiedSecret.secretValueCiphertext, - secretValueIV: userModifiedSecret.secretValueIV, - secretValueTag: userModifiedSecret.secretValueTag, - secretValueHash: userModifiedSecret.secretValueHash, - secretCommentCiphertext: userModifiedSecret.secretCommentCiphertext, - secretCommentIV: userModifiedSecret.secretCommentIV, - secretCommentTag: userModifiedSecret.secretCommentTag, - secretCommentHash: userModifiedSecret.secretCommentHash, - } - - const updateOperation = { updateOne: { filter: { _id: userModifiedSecret._id, workspace: workspaceId }, update: { $inc: { version: 1 }, $set: sanitizedSecret } } } - updateOperationsToPerform.push(updateOperation) - } else { - throw UnauthorizedRequestError({ message: "You do not have permission to modify one or more of the requested secrets" }) - } - }) - - const [bulkModificationInfoError, bulkModificationInfo] = await to(Secret.bulkWrite(updateOperationsToPerform).then()) - if (bulkModificationInfoError) { - if (bulkModificationInfoError instanceof ValidationError) { - throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkModificationInfoError.stack }) - } - - throw InternalServerError() - } - - return res.status(200).send() - } + validateRequest, + secretController.batchModifySecrets ); export default router;