mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 21:37:59 +00:00
Finish preliminary /v2/secrets routes for batch/single CRUD secrets endpoints
This commit is contained in:
@@ -2,10 +2,12 @@ import * as workspaceController from './workspaceController';
|
||||
import * as serviceTokenDataController from './serviceTokenDataController';
|
||||
import * as apiKeyDataController from './apiKeyDataController';
|
||||
import * as secretController from './secretController';
|
||||
import * as secretsController from './secretsController';
|
||||
|
||||
export {
|
||||
workspaceController,
|
||||
serviceTokenDataController,
|
||||
apiKeyDataController,
|
||||
secretController
|
||||
secretController,
|
||||
secretsController
|
||||
}
|
||||
|
||||
@@ -7,13 +7,16 @@ 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';
|
||||
import { postHogClient } from '../../services';
|
||||
|
||||
export const createSingleSecret = async (req: Request, res: Response) => {
|
||||
/**
|
||||
* Create secret for workspace with id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const createSecret = async (req: Request, res: Response) => {
|
||||
const secretToCreate: CreateSecretRequestBody = req.body.secret;
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const { workspaceId, environment } = req.params
|
||||
const sanitizedSecret: SanitizedSecretForCreate = {
|
||||
secretKeyCiphertext: secretToCreate.secretKeyCiphertext,
|
||||
secretKeyIV: secretToCreate.secretKeyIV,
|
||||
@@ -28,13 +31,13 @@ export const createSingleSecret = async (req: Request, res: Response) => {
|
||||
secretCommentTag: secretToCreate.secretCommentTag,
|
||||
secretCommentHash: secretToCreate.secretCommentHash,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment: environmentName,
|
||||
environment,
|
||||
type: secretToCreate.type,
|
||||
user: new Types.ObjectId(req.user._id)
|
||||
}
|
||||
|
||||
|
||||
const [error, newlyCreatedSecret] = await to(Secret.create(sanitizedSecret).then())
|
||||
const [error, secret] = await to(Secret.create(sanitizedSecret).then())
|
||||
if (error instanceof ValidationError) {
|
||||
throw RouteValidationError({ message: error.message, stack: error.stack })
|
||||
}
|
||||
@@ -45,20 +48,27 @@ export const createSingleSecret = async (req: Request, res: Response) => {
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
environment: environmentName,
|
||||
workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
environment,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).send()
|
||||
res.status(200).send({
|
||||
secret
|
||||
})
|
||||
}
|
||||
|
||||
export const batchCreateSecrets = async (req: Request, res: Response) => {
|
||||
/**
|
||||
* Create many secrets for workspace wiht id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const createSecrets = async (req: Request, res: Response) => {
|
||||
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const { workspaceId, environment } = req.params
|
||||
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
|
||||
|
||||
secretsToCreate.forEach(rawSecret => {
|
||||
@@ -76,7 +86,7 @@ export const batchCreateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentTag: rawSecret.secretCommentTag,
|
||||
secretCommentHash: rawSecret.secretCommentHash,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment: environmentName,
|
||||
environment,
|
||||
type: rawSecret.type,
|
||||
user: new Types.ObjectId(req.user._id)
|
||||
}
|
||||
@@ -84,7 +94,7 @@ export const batchCreateSecrets = async (req: Request, res: Response) => {
|
||||
sanitizedSecretesToCreate.push(safeUpdateFields)
|
||||
})
|
||||
|
||||
const [bulkCreateError, newlyCreatedSecrets] = await to(Secret.insertMany(sanitizedSecretesToCreate).then())
|
||||
const [bulkCreateError, secrets] = await to(Secret.insertMany(sanitizedSecretesToCreate).then())
|
||||
if (bulkCreateError) {
|
||||
if (bulkCreateError instanceof ValidationError) {
|
||||
throw RouteValidationError({ message: bulkCreateError.message, stack: bulkCreateError.stack })
|
||||
@@ -99,18 +109,25 @@ export const batchCreateSecrets = async (req: Request, res: Response) => {
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: (secretsToCreate ?? []).length,
|
||||
environment: environmentName,
|
||||
workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
environment,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).send()
|
||||
res.status(200).send({
|
||||
secrets
|
||||
})
|
||||
}
|
||||
|
||||
export const batchDeleteSecrets = async (req: Request, res: Response) => {
|
||||
/**
|
||||
* Delete secrets in workspace with id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretIdsToDelete: string[] = req.body.secretIds
|
||||
|
||||
@@ -149,8 +166,8 @@ export const batchDeleteSecrets = async (req: Request, res: Response) => {
|
||||
numberOfSecrets: numSecretsDeleted,
|
||||
environment: environmentName,
|
||||
workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -158,49 +175,40 @@ export const batchDeleteSecrets = async (req: Request, res: Response) => {
|
||||
res.status(200).send()
|
||||
}
|
||||
|
||||
export const deleteSingleSecret = async (req: Request, res: Response) => {
|
||||
const { secretId } = req.params;
|
||||
/**
|
||||
* Delete secret with id [secretId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecret = async (req: Request, res: Response) => {
|
||||
await Secret.findByIdAndDelete(req._secret._id)
|
||||
|
||||
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 (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
workspaceId: req._secret.workspace.toString(),
|
||||
environment: req._secret.environment,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
// #TODO: how do we get env name ans project id?
|
||||
// environment: environmentName,
|
||||
// workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).send()
|
||||
} else {
|
||||
throw BadRequestError()
|
||||
}
|
||||
res.status(200).send({
|
||||
secret: req._secret
|
||||
})
|
||||
}
|
||||
|
||||
export const batchModifySecrets = async (req: Request, res: Response) => {
|
||||
/**
|
||||
* Update secrets for workspace with id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateSecrets = 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())
|
||||
@@ -252,8 +260,8 @@ export const batchModifySecrets = async (req: Request, res: Response) => {
|
||||
numberOfSecrets: (secretsModificationsRequested ?? []).length,
|
||||
environment: environmentName,
|
||||
workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -261,8 +269,13 @@ export const batchModifySecrets = async (req: Request, res: Response) => {
|
||||
return res.status(200).send()
|
||||
}
|
||||
|
||||
// #TODO: I assume this should be '...Secret'?
|
||||
export const modifySingleSecrets = async (req: Request, res: Response) => {
|
||||
/**
|
||||
* Update a secret within workspace with id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateSecret = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
||||
|
||||
@@ -299,8 +312,8 @@ export const modifySingleSecrets = async (req: Request, res: Response) => {
|
||||
numberOfSecrets: 1,
|
||||
environment: environmentName,
|
||||
workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -308,11 +321,18 @@ export const modifySingleSecrets = async (req: Request, res: Response) => {
|
||||
return res.status(200).send(singleModificationUpdate)
|
||||
}
|
||||
|
||||
export const fetchAllSecrets = async (req: Request, res: Response) => {
|
||||
/**
|
||||
* Return secrets for workspace with id [workspaceId], environment [environment] and user
|
||||
* with id [req.user._id]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getSecrets = 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
|
||||
let userId: string | undefined = undefined // used for getting personal secrets for user
|
||||
if (req.user) {
|
||||
userId = req.user._id.toString();
|
||||
}
|
||||
@@ -321,7 +341,7 @@ export const fetchAllSecrets = async (req: Request, res: Response) => {
|
||||
userId = req.serviceTokenData.user._id
|
||||
}
|
||||
|
||||
const [retriveAllSecretsError, allSecrets] = await to(Secret.find(
|
||||
const [err, secrets] = await to(Secret.find(
|
||||
{
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
@@ -330,8 +350,8 @@ export const fetchAllSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
).then())
|
||||
|
||||
if (retriveAllSecretsError instanceof ValidationError) {
|
||||
throw RouteValidationError({ message: "Unable to get secrets, please try again", stack: retriveAllSecretsError.stack })
|
||||
if (err) {
|
||||
throw RouteValidationError({ message: "Failed to get secrets, please try again", stack: err.stack })
|
||||
}
|
||||
|
||||
if (postHogClient) {
|
||||
@@ -339,56 +359,40 @@ export const fetchAllSecrets = async (req: Request, res: Response) => {
|
||||
event: 'secrets pulled',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: (allSecrets ?? []).length,
|
||||
numberOfSecrets: (secrets ?? []).length,
|
||||
environment,
|
||||
workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.json(allSecrets)
|
||||
return res.json(secrets)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pulled',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
// #TODO: how do we get environment and workspace here? Do we need that? When is this route used?
|
||||
// environment,
|
||||
// workspaceId,
|
||||
// #TODO: why does this route have no channel?
|
||||
// channel: channel ? channel : 'cli'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
throw BadRequestError()
|
||||
/**
|
||||
* Return secret with id [secretId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getSecret = async (req: Request, res: Response) => {
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pulled',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
workspaceId: req._secret.workspace.toString(),
|
||||
environment: req._secret.environment,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secret: req._secret
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
import to from 'await-to-js';
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response } from 'express';
|
||||
import { ISecret, Secret } from '../../models';
|
||||
import {
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS
|
||||
} from '../../variables';
|
||||
import { ValidationError } from '../../utils/errors';
|
||||
import { EESecretService, EELogService } from '../../ee/services';
|
||||
import { postHogClient } from '../../services';
|
||||
|
||||
/**
|
||||
* Create secret(s) for workspace with id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const createSecrets = async (req: Request, res: Response) => {
|
||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||
const { workspaceId, environment } = req.body;
|
||||
|
||||
let toAdd;
|
||||
if (Array.isArray(req.body.secrets)) {
|
||||
// case: create multiple secrets
|
||||
toAdd = req.body.secrets;
|
||||
} else if (typeof req.body.secrets === 'object') {
|
||||
// case: create 1 secret
|
||||
toAdd = [req.body.secrets];
|
||||
}
|
||||
|
||||
const newSecrets = await Secret.insertMany(
|
||||
toAdd.map(({
|
||||
type,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
}: {
|
||||
type: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
}) => ({
|
||||
version: 1,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type,
|
||||
user: type === SECRET_PERSONAL ? req.user : undefined,
|
||||
environment,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
}))
|
||||
);
|
||||
|
||||
// (EE) add secret versions for new secrets
|
||||
EESecretService.addSecretVersions({
|
||||
secretVersions: newSecrets.map(({
|
||||
_id,
|
||||
version,
|
||||
workspace,
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretKeyHash,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash
|
||||
}) => ({
|
||||
_id: new Types.ObjectId(),
|
||||
secret: _id,
|
||||
version,
|
||||
workspace,
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
isDeleted: false,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretKeyHash,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash
|
||||
}))
|
||||
});
|
||||
|
||||
const addAction = await EELogService.createActionSecret({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
secretIds: newSecrets.map((n) => n._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
addAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
actions: [addAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
});
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: toAdd.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: newSecrets
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return secret(s) for workspace with id [workspaceId], environment [environment] and user
|
||||
* with id [req.user._id]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getSecrets = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment } = req.query;
|
||||
|
||||
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
|
||||
if (req.user) {
|
||||
userId = req.user._id;
|
||||
}
|
||||
|
||||
if (req.serviceTokenData) {
|
||||
userId = req.serviceTokenData.user._id
|
||||
}
|
||||
|
||||
const [err, secrets] = await to(Secret.find(
|
||||
{
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
$or: [
|
||||
{ user: userId },
|
||||
{ user: { $exists: false } }
|
||||
],
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
}
|
||||
).then())
|
||||
|
||||
if (err) throw ValidationError({ message: 'Failed to get secrets', stack: err.stack });
|
||||
|
||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||
|
||||
const readAction = await EELogService.createActionSecret({
|
||||
name: ACTION_READ_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: workspaceId as string,
|
||||
secretIds: secrets.map((n: any) => n._id)
|
||||
});
|
||||
|
||||
readAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: workspaceId as string,
|
||||
actions: [readAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secrets
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secret(s) in workspace with id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const updateSecrets = async (req: Request, res: Response) => {
|
||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||
const { workspaceId, environment } = req.body;
|
||||
|
||||
// TODO: move type
|
||||
interface PatchSecret {
|
||||
id: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
secretCommentCiphertext: string;
|
||||
secretCommentIV: string;
|
||||
secretCommentTag: string;
|
||||
}
|
||||
|
||||
const ops = req.body.secrets.map((secret: PatchSecret) => {
|
||||
const {
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
} = secret;
|
||||
return ({
|
||||
updateOne: {
|
||||
filter: { _id: new Types.ObjectId(secret.id) },
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1
|
||||
},
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
...((
|
||||
secretCommentCiphertext &&
|
||||
secretCommentIV &&
|
||||
secretCommentTag
|
||||
) ? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
} : {}),
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
const b = await Secret.bulkWrite(ops);
|
||||
|
||||
let newSecretsObj: { [key: string]: PatchSecret } = {};
|
||||
req.body.secrets.forEach((secret: PatchSecret) => {
|
||||
newSecretsObj[secret.id] = secret;
|
||||
});
|
||||
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions: req.secrets.map((secret: ISecret) => {
|
||||
const {
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
} = newSecretsObj[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: ''
|
||||
})
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
// group secrets into workspaces so updated secrets can
|
||||
// be logged and snapshotted separately for each workspace
|
||||
let workspaceSecretObj: any = {};
|
||||
req.secrets.forEach((s: any) => {
|
||||
if (s.workspace.toString() in workspaceSecretObj) {
|
||||
workspaceSecretObj[s.workspace.toString()].push(s);
|
||||
} else {
|
||||
workspaceSecretObj[s.workspace.toString()] = [s]
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
||||
const updateAction = await EELogService.createActionSecret({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
updateAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
actions: [updateAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: key
|
||||
})
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const updateAction = await EELogService.createActionSecret({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
secretIds: req.secrets.map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
updateAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
actions: [updateAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
});
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets modified',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: req.secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: await Secret.find({
|
||||
_id: {
|
||||
$in: req.secrets.map((secret: ISecret) => secret._id)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secret(s) in workspace with id [workspaceId] and environment [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||
const toDelete = req.secrets.map((s: any) => s._id);
|
||||
|
||||
await Secret.deleteMany({
|
||||
_id: {
|
||||
$in: toDelete
|
||||
}
|
||||
});
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: toDelete
|
||||
});
|
||||
|
||||
// group secrets into workspaces so deleted secrets can
|
||||
// be logged and snapshotted separately for each workspace
|
||||
let workspaceSecretObj: any = {};
|
||||
req.secrets.forEach((s: any) => {
|
||||
if (s.workspace.toString() in workspaceSecretObj) {
|
||||
workspaceSecretObj[s.workspace.toString()].push(s);
|
||||
} else {
|
||||
workspaceSecretObj[s.workspace.toString()] = [s]
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
||||
const deleteAction = await EELogService.createActionSecret({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
deleteAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
actions: [deleteAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: key
|
||||
})
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
|
||||
userAgent: req.headers?.['user-agent']
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(400).send({
|
||||
secrets: req.secrets
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user