Skip to content

Commit

Permalink
Add single secrets v2 operations
Browse files Browse the repository at this point in the history
  • Loading branch information
maidul98 committed Jan 6, 2023
1 parent a04fe00 commit 53273df
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 12 deletions.
130 changes: 119 additions & 11 deletions backend/src/controllers/v2/secretController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,39 @@ const { ValidationError } = mongoose.Error;
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
import { AnyBulkWriteOperation } from 'mongodb';
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
import { validateMembership } from "../../helpers/membership";
import { ADMIN, MEMBER } from '../../variables';

export const createSingleSecret = async (req: Request, res: Response) => {
const secretToCreate: CreateSecretRequestBody = req.body.secret;
const { workspaceId, environmentName } = req.params
const sanitizedSecret: SanitizedSecretForCreate = {
secretKeyCiphertext: secretToCreate.secretKeyCiphertext,
secretKeyIV: secretToCreate.secretKeyIV,
secretKeyTag: secretToCreate.secretKeyTag,
secretKeyHash: secretToCreate.secretKeyHash,
secretValueCiphertext: secretToCreate.secretValueCiphertext,
secretValueIV: secretToCreate.secretValueIV,
secretValueTag: secretToCreate.secretValueTag,
secretValueHash: secretToCreate.secretValueHash,
secretCommentCiphertext: secretToCreate.secretCommentCiphertext,
secretCommentIV: secretToCreate.secretCommentIV,
secretCommentTag: secretToCreate.secretCommentTag,
secretCommentHash: secretToCreate.secretCommentHash,
workspace: new Types.ObjectId(workspaceId),
environment: environmentName,
type: secretToCreate.type,
user: new Types.ObjectId(req.user._id)
}


const [error, newlyCreatedSecret] = await to(Secret.create(sanitizedSecret).then())
if (error instanceof ValidationError) {
throw RouteValidationError({ message: error.message, stack: error.stack })
}

res.status(200).send()
}

export const batchCreateSecrets = async (req: Request, res: Response) => {
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
Expand Down Expand Up @@ -48,16 +81,6 @@ export const batchCreateSecrets = async (req: Request, res: Response) => {
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
Expand Down Expand Up @@ -90,6 +113,33 @@ export const batchDeleteSecrets = async (req: Request, res: Response) => {
res.status(200).send()
}

export const deleteSingleSecret = async (req: Request, res: Response) => {
const { secretId } = req.params;

const [error, singleSecretRetrieved] = await to(Secret.findById(secretId).then())
if (error instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to get secret, please try again", stack: error.stack })
}

if (singleSecretRetrieved) {
const [membershipValidationError, membership] = await to(validateMembership({
userId: req.user._id,
workspaceId: singleSecretRetrieved.workspace._id.toString(),
acceptedRoles: [ADMIN, MEMBER]
}))

if (membershipValidationError || !membership) {
throw UnauthorizedRequestError()
}

await Secret.findByIdAndDelete(secretId)

res.status(200).send()
} else {
throw BadRequestError()
}
}

export const batchModifySecrets = async (req: Request, res: Response) => {
const { workspaceId, environmentName } = req.params
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
Expand All @@ -101,7 +151,6 @@ export const batchModifySecrets = async (req: Request, res: Response) => {
const secretsUserCanModifySet: Set<string> = new Set(secretIdsUserCanModify.map(objectId => objectId._id.toString()));
const updateOperationsToPerform: any = []


secretsModificationsRequested.forEach(userModifiedSecret => {
if (secretsUserCanModifySet.has(userModifiedSecret._id.toString())) {
const sanitizedSecret: SanitizedSecretModify = {
Expand Down Expand Up @@ -138,6 +187,38 @@ export const batchModifySecrets = async (req: Request, res: Response) => {
return res.status(200).send()
}

export const modifySingleSecrets = async (req: Request, res: Response) => {
const { workspaceId, environmentName } = req.params
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;

const [secretIdUserCanModifyError, secretIdUserCanModify] = await to(Secret.findOne({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
if (secretIdUserCanModifyError && !secretIdUserCanModify) {
throw BadRequestError()
}

const sanitizedSecret: SanitizedSecretModify = {
secretKeyCiphertext: secretModificationsRequested.secretKeyCiphertext,
secretKeyIV: secretModificationsRequested.secretKeyIV,
secretKeyTag: secretModificationsRequested.secretKeyTag,
secretKeyHash: secretModificationsRequested.secretKeyHash,
secretValueCiphertext: secretModificationsRequested.secretValueCiphertext,
secretValueIV: secretModificationsRequested.secretValueIV,
secretValueTag: secretModificationsRequested.secretValueTag,
secretValueHash: secretModificationsRequested.secretValueHash,
secretCommentCiphertext: secretModificationsRequested.secretCommentCiphertext,
secretCommentIV: secretModificationsRequested.secretCommentIV,
secretCommentTag: secretModificationsRequested.secretCommentTag,
secretCommentHash: secretModificationsRequested.secretCommentHash,
}

const [error, singleModificationUpdate] = await to(Secret.updateOne({ _id: secretModificationsRequested._id, workspace: workspaceId }, { $inc: { version: 1 }, $set: sanitizedSecret }).then())
if (error instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: error.stack })
}

return res.status(200).send(singleModificationUpdate)
}

export const fetchAllSecrets = async (req: Request, res: Response) => {
const { environment } = req.query;
const { workspaceId } = req.params;
Expand Down Expand Up @@ -165,4 +246,31 @@ export const fetchAllSecrets = async (req: Request, res: Response) => {
}

return res.json(allSecrets)
}

export const fetchSingleSecret = async (req: Request, res: Response) => {
const { secretId } = req.params;

const [error, singleSecretRetrieved] = await to(Secret.findById(secretId).then())

if (error instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to get secret, please try again", stack: error.stack })
}

if (singleSecretRetrieved) {
const [membershipValidationError, membership] = await to(validateMembership({
userId: req.user._id,
workspaceId: singleSecretRetrieved.workspace._id.toString(),
acceptedRoles: [ADMIN, MEMBER]
}))

if (membershipValidationError || !membership) {
throw UnauthorizedRequestError()
}

res.json(singleSecretRetrieved)

} else {
throw BadRequestError()
}
}
63 changes: 62 additions & 1 deletion backend/src/routes/v2/secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { body, param, query } from 'express-validator';
import { ADMIN, MEMBER } from '../../variables';
import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret';
import { secretController } from '../../controllers/v2';
import { fetchAllSecrets } from '../../controllers/v2/secretController';
import { fetchAllSecrets, fetchSingleSecret } from '../../controllers/v2/secretController';

const router = express.Router();

Expand All @@ -26,6 +26,24 @@ router.post(
secretController.batchCreateSecrets
);

/**
* Create single secret for a given workspace and environmentName
*/
router.post(
'/workspace/:workspaceId/environment/:environmentName',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
param('workspaceId').exists().isMongoId().trim(),
param('environmentName').exists().trim(),
body('secret').exists().isObject(),
validateRequest,
secretController.createSingleSecret
);

/**
* Get all secrets for a given environment and workspace id
*/
Expand All @@ -43,6 +61,18 @@ router.get(
fetchAllSecrets
);

/**
* Get single secret by id
*/
router.get(
'/:secretId',
requireAuth({
acceptedAuthModes: ['jwt', 'serviceToken']
}),
validateRequest,
fetchSingleSecret
);

/**
* Batch delete secrets in a given workspace and environment name
*/
Expand All @@ -62,6 +92,19 @@ router.delete(

);

/**
* delete single secret by id
*/
router.delete(
'/:secretId',
requireAuth({
acceptedAuthModes: ['jwt']
}),
param('secretId').isMongoId(),
validateRequest,
secretController.deleteSingleSecret
);

/**
* Apply modifications to many existing secrets in a given workspace and environment
*/
Expand All @@ -80,4 +123,22 @@ router.patch(
secretController.batchModifySecrets
);

/**
* Apply modifications to single existing secret in a given workspace and environment
*/
router.patch(
'/workspace/:workspaceId/environment/:environmentName',
requireAuth({
acceptedAuthModes: ['jwt']
}),
body('secret').isObject(),
param('workspaceId').exists().isMongoId().trim(),
param('environmentName').exists().trim(),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
validateRequest,
secretController.modifySingleSecrets
);

export default router;

1 comment on commit 53273df

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for backend

St.
Category Percentage Covered / Total
🟡 Statements 72.6% 53/73
🔴 Branches 0% 0/5
🔴 Functions 50% 1/2
🟡 Lines 73.61% 53/72

Test suite run success

1 tests passing in 1 suite.

Report generated by 🧪jest coverage report action from 53273df

Please sign in to comment.