Merge branch 'main' into activity-logs

This commit is contained in:
mv-turtle
2023-01-04 21:12:24 -08:00
committed by GitHub
29 changed files with 1257 additions and 72 deletions

View File

@@ -15,6 +15,7 @@
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0",
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
@@ -39,6 +40,7 @@
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6"
},
@@ -3744,6 +3746,14 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/await-to-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/axios": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz",
@@ -11521,6 +11531,14 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/utility-types": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
"integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==",
"engines": {
"node": ">= 4"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -14962,6 +14980,11 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"await-to-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="
},
"axios": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz",
@@ -17175,9 +17198,9 @@
"dev": true
},
"json5": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"dev": true
},
"jsonwebtoken": {
@@ -20632,6 +20655,11 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"utility-types": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
"integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg=="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View File

@@ -6,6 +6,7 @@
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0",
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
@@ -30,6 +31,7 @@
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6"
},

View File

@@ -98,13 +98,13 @@ app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
// v2 routes
app.use('/api/v2/workspace', v2WorkspaceRouter);
app.use('/api/v2/secret', v2SecretRouter);
app.use('/api/v2/service-token-data', v2ServiceTokenDataRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter);
app.use('/api/v2/api-key-data', v2APIKeyDataRouter);
//* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next)=>{
if(res.headersSent) return next();
next(RouteNotFoundError({message: `The requested source '(${req.method})${req.url}' was not found`}))
app.use((req, res, next) => {
if (res.headersSent) return next();
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
})
//* Error Handling Middleware (must be after all routing logic)

View File

@@ -1,9 +1,11 @@
import * as workspaceController from './workspaceController';
import * as serviceTokenDataController from './serviceTokenDataController';
import * as apiKeyDataController from './apiKeyDataController';
import * as secretController from './secretController';
export {
export {
workspaceController,
serviceTokenDataController,
apiKeyDataController
apiKeyDataController,
secretController
}

View File

@@ -0,0 +1,168 @@
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';
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
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<string> = new Set(secretIdsUserCanDelete.map(objectId => objectId._id.toString()));
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = []
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<string> = 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()
}
export const fetchAllSecrets = async (req: Request, res: Response) => {
const { environment } = req.query;
const { workspaceId } = req.params;
let userId: string | undefined = undefined // Used for choosing the personal secrets to fetch in
if (req.user) {
userId = req.user._id.toString();
}
if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
}
const [retriveAllSecretsError, allSecrets] = await to(Secret.find(
{
workspace: workspaceId,
environment,
$or: [{ user: userId }, { user: { $exists: false } }],
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
}
).then())
if (retriveAllSecretsError instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to get secrets, please try again", stack: retriveAllSecretsError.stack })
}
return res.json(allSecrets)
}

View File

@@ -15,9 +15,7 @@ import {
* @param res
* @returns
*/
export const getServiceTokenData = async (req: Request, res: Response) => res.status(200).send({
serviceTokenData: req.serviceTokenData
});
export const getServiceTokenData = async (req: Request, res: Response) => res.status(200).json(req.serviceTokenData);
/**
* Create new service token data for workspace with id [workspaceId] and
@@ -29,9 +27,9 @@ export const getServiceTokenData = async (req: Request, res: Response) => res.st
export const createServiceTokenData = async (req: Request, res: Response) => {
let serviceToken, serviceTokenData;
try {
const {
const {
name,
workspaceId,
workspaceId,
environment,
encryptedKey,
iv,
@@ -41,10 +39,10 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, SALT_ROUNDS);
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
serviceTokenData = await new ServiceTokenData({
name,
workspace: workspaceId,
@@ -56,12 +54,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
iv,
tag
}).save();
// return service token data without sensitive data
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
if (!serviceTokenData) throw new Error('Failed to find service token data');
serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
} catch (err) {
@@ -90,7 +88,7 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
const { serviceTokenDataId } = req.params;
serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
@@ -98,7 +96,7 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
message: 'Failed to delete service token data'
});
}
return res.status(200).send({
serviceTokenData
});

View File

@@ -1,7 +1,14 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import {
Key,
Workspace,
Membership,
MembershipOrg,
Integration,
IntegrationAuth,
Key,
IUser,
ServiceToken,
ServiceTokenData
} from '../../models';
import {
@@ -68,7 +75,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
workspaceId,
keys
});
if (postHogClient) {
postHogClient.capture({
event: 'secrets pushed',
@@ -115,7 +122,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
let userId;
if (req.user) {
userId = req.user._id.toString();
@@ -130,7 +137,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
channel: channel ? channel : 'cli',
ipAddress: req.ip
});
if (channel !== 'cli') {
secrets = reformatPullSecrets({ secrets });
}
@@ -170,7 +177,7 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
workspace: workspaceId,
receiver: req.user._id
}).populate('sender', '+publicKey');
if (!key) throw new Error('Failed to find workspace key');
} catch (err) {
Sentry.setUser({ email: req.user.email });
@@ -180,9 +187,7 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
});
}
return res.status(200).send({
key
});
return res.status(200).json(key);
}
export const getWorkspaceServiceTokenData = async (
req: Request,
@@ -205,7 +210,7 @@ export const getWorkspaceServiceTokenData = async (
message: 'Failed to get workspace service token data'
});
}
return res.status(200).send({
serviceTokenData
});

View File

@@ -4,26 +4,33 @@ import * as Sentry from '@sentry/node';
import { InternalServerError } from "../utils/errors";
import { getLogger } from "../utils/logger";
import RequestError, { LogLevel } from "../utils/requestError";
import { NODE_ENV } from "../config";
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError|Error, req, res, next) => {
if(res.headersSent) return next();
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => {
if (res.headersSent) return next();
if (NODE_ENV !== "production") {
/* eslint-disable no-console */
console.log(error)
/* eslint-enable no-console */
}
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
if(!(error instanceof RequestError)){
error = InternalServerError({context: {exception: error.message}, stack: error.stack})
if (!(error instanceof RequestError)) {
error = InternalServerError({ context: { exception: error.message }, stack: error.stack })
getLogger('backend-main').log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
}
//* Set Sentry user identification if req.user is populated
if(req.user !== undefined && req.user !== null){
if (req.user !== undefined && req.user !== null) {
Sentry.setUser({ email: req.user.email })
}
//* Only sent error to Sentry if LogLevel is one of the following level 'ERROR', 'EMERGENCY' or 'CRITICAL'
//* with this we will eliminate false-positive errors like 'BadRequestError', 'UnauthorizedRequestError' and so on
if([LogLevel.ERROR, LogLevel.EMERGENCY, LogLevel.CRITICAL].includes((<RequestError>error).level)){
if ([LogLevel.ERROR, LogLevel.EMERGENCY, LogLevel.CRITICAL].includes((<RequestError>error).level)) {
Sentry.captureException(error)
}
res.status((<RequestError>error).statusCode).json((<RequestError>error).format(req))
next()
}

View File

@@ -25,28 +25,28 @@ declare module 'jsonwebtoken' {
* @param {String[]} obj.acceptedAuthModes - accepted modes of authentication (jwt/st)
* @returns
*/
const requireAuth = ({
const requireAuth = ({
acceptedAuthModes = ['jwt']
}: {
acceptedAuthModes: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
if(AUTH_TOKEN_TYPE === null)
return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer')
return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
if(AUTH_TOKEN_VALUE === null)
return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
if (AUTH_TOKEN_TYPE === null)
return next(BadRequestError({ message: `Missing Authorization Header in the request header.` }))
if (AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer')
return next(BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` }))
if (AUTH_TOKEN_VALUE === null)
return next(BadRequestError({ message: 'Missing Authorization Body in the request header' }))
// validate auth token against
const authMode = validateAuthMode({
authTokenValue: AUTH_TOKEN_VALUE,
acceptedAuthModes
});
if (!acceptedAuthModes.includes(authMode)) throw new Error('Failed to validate auth mode');
// attach auth payloads
switch (authMode) {
case 'serviceToken':

View File

@@ -1,6 +1,6 @@
import { Request, Response, NextFunction } from 'express';
import { validationResult } from 'express-validator';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
import { BadRequestError, UnauthorizedRequestError, ValidationError } from '../utils/errors';
/**
* Validate intended inputs on [req] via express-validator
@@ -15,12 +15,12 @@ const validate = (req: Request, res: Response, next: NextFunction) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(BadRequestError({context: {errors: errors.array}}))
return next(ValidationError({ context: { errors: `One or more of your parameters are invalid [error(s)=${(JSON.stringify(errors))}]` } }))
}
return next();
} catch (err) {
return next(UnauthorizedRequestError({message: 'Unauthenticated requests are not allowed. Try logging in'}))
return next(UnauthorizedRequestError({ message: 'Unauthenticated requests are not allowed. Try logging in' }))
}
};

View File

@@ -33,7 +33,8 @@ const secretSchema = new Schema<ISecret>(
{
version: {
type: Number,
required: true
required: true,
default: 1
},
workspace: {
type: Schema.Types.ObjectId,

View File

@@ -1,4 +1,83 @@
import express from 'express';
import express, { Request, Response } from 'express';
import { requireAuth, requireWorkspaceAuth, validateRequest } from '../../middleware';
import { body, param, query } from 'express-validator';
import { ADMIN, MEMBER } from '../../variables';
import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret/types';
import { secretController } from '../../controllers/v2';
import { fetchAllSecrets } from '../../controllers/v2/secretController';
const router = express.Router();
/**
* Create many secrets for a given workspace and environmentName
*/
router.post(
'/batch-create/workspace/:workspaceId/environment/:environmentName',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
param('workspaceId').exists().isMongoId().trim(),
param('environmentName').exists().trim(),
body('secrets').exists().isArray().custom((value) => value.every((item: CreateSecretRequestBody) => typeof item === 'object')),
validateRequest,
secretController.batchCreateSecrets
);
/**
* Get all secrets for a given environment and workspace id
*/
router.get(
'/workspace/:workspaceId',
param('workspaceId').exists().trim(),
query("environment").exists(),
requireAuth({
acceptedAuthModes: ['jwt', 'serviceToken']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
validateRequest,
fetchAllSecrets
);
/**
* Batch delete secrets in a given workspace and environment name
*/
router.delete(
'/batch/workspace/:workspaceId/environment/:environmentName',
requireAuth({
acceptedAuthModes: ['jwt']
}),
param('workspaceId').exists().isMongoId().trim(),
param('environmentName').exists().trim(),
body('secretIds').exists().isArray().custom(array => array.length > 0),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
validateRequest,
secretController.batchDeleteSecrets
);
/**
* Apply modifications to many existing secrets in a given workspace and environment
*/
router.patch(
'/batch-modify/workspace/:workspaceId/environment/:environmentName',
requireAuth({
acceptedAuthModes: ['jwt']
}),
body('secrets').exists().isArray().custom((secrets: ModifySecretRequestBody[]) => secrets.length > 0),
param('workspaceId').exists().isMongoId().trim(),
param('environmentName').exists().trim(),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
validateRequest,
secretController.batchModifySecrets
);
export default router;

View File

@@ -42,15 +42,15 @@ router.get(
);
router.get(
'/:workspaceId/key',
'/:workspaceId/encrypted-key',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
}),
param('workspaceId').exists().trim(),
validateRequest,
validateRequest,
workspaceController.getWorkspaceKey
);

View File

@@ -0,0 +1,14 @@
import { Assign, Omit } from 'utility-types';
import { ISecret } from '../../models';
// Everything is required, except the omitted types
export type CreateSecretRequestBody = Omit<ISecret, "user" | "version" | "environment" | "workspace">;
// Omit the listed properties, then make everything optional and then make _id required
export type ModifySecretRequestBody = Assign<Partial<Omit<ISecret, "user" | "version" | "environment" | "workspace">>, { _id: string }>;
// Used for modeling sanitized secrets before uplaod. To be used for converting user input for uploading
export type SanitizedSecretModify = Partial<Omit<ISecret, "user" | "version" | "environment" | "workspace">>;
// Everything is required, except the omitted types
export type SanitizedSecretForCreate = Omit<ISecret, "version" | "_id">;

View File

@@ -1,7 +1,9 @@
{
"compilerOptions": {
"target": "es2016",
"lib": ["es6"],
"lib": [
"es6"
],
"module": "commonjs",
"rootDir": "src",
"resolveJsonModule": true,
@@ -13,8 +15,15 @@
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
"typeRoots": ["./src/types", "./node_modules/@types"]
"typeRoots": [
"./src/types",
"./node_modules/@types"
]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}