mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 20:27:12 +00:00
feat(dashboard-v3): migrated folder, imports and snapshots to use only secret path and not folder id
This commit is contained in:
@@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
|||||||
import { isValidScope } from "../../helpers";
|
import { isValidScope } from "../../helpers";
|
||||||
import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models";
|
import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models";
|
||||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||||
import { getFolderWithPathFromId } from "../../services/FolderService";
|
import { getFolderByPath, getFolderWithPathFromId } from "../../services/FolderService";
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
ResourceNotFoundError,
|
ResourceNotFoundError,
|
||||||
@@ -95,37 +95,12 @@ export const createSecretImp = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const {
|
const {
|
||||||
body: { workspaceId, environment, folderId, secretImport }
|
body: { workspaceId, environment, directory, secretImport }
|
||||||
} = await validateRequest(reqValidator.CreateSecretImportV1, req);
|
} = await validateRequest(reqValidator.CreateSecretImportV1, req);
|
||||||
|
|
||||||
const folders = await Folder.findOne({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment
|
|
||||||
}).lean();
|
|
||||||
|
|
||||||
if (!folders && folderId !== "root") {
|
|
||||||
throw ResourceNotFoundError({
|
|
||||||
message: "Failed to find folder"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let secretPath = "/";
|
|
||||||
if (folders) {
|
|
||||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, folderId);
|
|
||||||
secretPath = folderPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
// root check
|
// root check
|
||||||
let isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, directory);
|
||||||
if (!isValidScopeAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
|
||||||
}
|
|
||||||
isValidScopeAccess = isValidScope(
|
|
||||||
req.authData.authPayload,
|
|
||||||
secretImport.environment,
|
|
||||||
secretImport.secretPath
|
|
||||||
);
|
|
||||||
if (!isValidScopeAccess) {
|
if (!isValidScopeAccess) {
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
@@ -133,27 +108,31 @@ export const createSecretImp = async (req: Request, res: Response) => {
|
|||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: secretImport.environment,
|
|
||||||
secretPath: secretImport.secretPath
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const folders = await Folder.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment
|
||||||
|
}).lean();
|
||||||
|
|
||||||
|
if (!folders && directory !== "/")
|
||||||
|
throw ResourceNotFoundError({ message: "Failed to find folder" });
|
||||||
|
|
||||||
|
let folderId = "root";
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, directory);
|
||||||
|
if (!folder) throw BadRequestError({ message: "Folder not found" });
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
const importSecDoc = await SecretImport.findOne({
|
const importSecDoc = await SecretImport.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId
|
folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
const importToSecretPath = folders
|
|
||||||
? getFolderWithPathFromId(folders.nodes, folderId).folderPath
|
|
||||||
: "/";
|
|
||||||
|
|
||||||
if (!importSecDoc) {
|
if (!importSecDoc) {
|
||||||
const doc = new SecretImport({
|
const doc = new SecretImport({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
@@ -173,7 +152,7 @@ export const createSecretImp = async (req: Request, res: Response) => {
|
|||||||
importFromEnvironment: secretImport.environment,
|
importFromEnvironment: secretImport.environment,
|
||||||
importFromSecretPath: secretImport.secretPath,
|
importFromSecretPath: secretImport.secretPath,
|
||||||
importToEnvironment: environment,
|
importToEnvironment: environment,
|
||||||
importToSecretPath
|
importToSecretPath: directory
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -206,7 +185,7 @@ export const createSecretImp = async (req: Request, res: Response) => {
|
|||||||
importFromEnvironment: secretImport.environment,
|
importFromEnvironment: secretImport.environment,
|
||||||
importFromSecretPath: secretImport.secretPath,
|
importFromSecretPath: secretImport.secretPath,
|
||||||
importToEnvironment: environment,
|
importToEnvironment: environment,
|
||||||
importToSecretPath
|
importToSecretPath: directory
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -563,8 +542,38 @@ export const getSecretImports = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
query: { workspaceId, environment, folderId }
|
query: { workspaceId, environment, directory }
|
||||||
} = await validateRequest(reqValidator.GetSecretImportsV1, req);
|
} = await validateRequest(reqValidator.GetSecretImportsV1, req);
|
||||||
|
|
||||||
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, directory);
|
||||||
|
if (!isValidScopeAccess) {
|
||||||
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath: directory
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const folders = await Folder.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment
|
||||||
|
}).lean();
|
||||||
|
if (!folders && directory !== "/") throw BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
let folderId = "root";
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, directory);
|
||||||
|
if (!folder) throw BadRequestError({ message: "Folder not found" });
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
const importSecDoc = await SecretImport.findOne({
|
const importSecDoc = await SecretImport.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -575,41 +584,6 @@ export const getSecretImports = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).json({ secretImport: {} });
|
return res.status(200).json({ secretImport: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for service token validity
|
|
||||||
const folders = await Folder.findOne({
|
|
||||||
workspace: importSecDoc.workspace,
|
|
||||||
environment: importSecDoc.environment
|
|
||||||
}).lean();
|
|
||||||
|
|
||||||
let secretPath = "/";
|
|
||||||
if (folders) {
|
|
||||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
|
|
||||||
secretPath = folderPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
const isValidScopeAccess = isValidScope(
|
|
||||||
req.authData.authPayload,
|
|
||||||
importSecDoc.environment,
|
|
||||||
secretPath
|
|
||||||
);
|
|
||||||
if (!isValidScopeAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
|
||||||
req.user._id,
|
|
||||||
importSecDoc.workspace.toString()
|
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: importSecDoc.environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json({ secretImport: importSecDoc });
|
return res.status(200).json({ secretImport: importSecDoc });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -621,9 +595,39 @@ export const getSecretImports = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
query: { workspaceId, environment, folderId }
|
query: { workspaceId, environment, directory }
|
||||||
} = await validateRequest(reqValidator.GetAllSecretsFromImportV1, req);
|
} = await validateRequest(reqValidator.GetAllSecretsFromImportV1, req);
|
||||||
|
|
||||||
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
// check for service token validity
|
||||||
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, directory);
|
||||||
|
if (!isValidScopeAccess) {
|
||||||
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath: directory
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const folders = await Folder.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment
|
||||||
|
}).lean();
|
||||||
|
if (!folders && directory !== "/") throw BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
let folderId = "root";
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, directory);
|
||||||
|
if (!folder) throw BadRequestError({ message: "Folder not found" });
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
const importSecDoc = await SecretImport.findOne({
|
const importSecDoc = await SecretImport.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -634,11 +638,6 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).json({ secrets: [] });
|
return res.status(200).json({ secrets: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await Folder.findOne({
|
|
||||||
workspace: importSecDoc.workspace,
|
|
||||||
environment: importSecDoc.environment
|
|
||||||
}).lean();
|
|
||||||
|
|
||||||
let secretPath = "/";
|
let secretPath = "/";
|
||||||
if (folders) {
|
if (folders) {
|
||||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
|
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ import { Secret, ServiceTokenData } from "../../models";
|
|||||||
import { Folder } from "../../models/folder";
|
import { Folder } from "../../models/folder";
|
||||||
import {
|
import {
|
||||||
appendFolder,
|
appendFolder,
|
||||||
deleteFolderById,
|
|
||||||
generateFolderId,
|
generateFolderId,
|
||||||
getAllFolderIds,
|
getAllFolderIds,
|
||||||
getFolderByPath,
|
getFolderByPath,
|
||||||
getFolderWithPathFromId,
|
getFolderWithPathFromId,
|
||||||
getParentFromFolderId,
|
|
||||||
validateFolderName
|
validateFolderName
|
||||||
} from "../../services/FolderService";
|
} from "../../services/FolderService";
|
||||||
import {
|
import {
|
||||||
@@ -25,13 +23,9 @@ import {
|
|||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import * as reqValidator from "../../validation/folders";
|
import * as reqValidator from "../../validation/folders";
|
||||||
|
|
||||||
/**
|
const ERR_FOLDER_NOT_FOUND = BadRequestError({ message: "The folder doesn't exist" });
|
||||||
* Create folder with name [folderName] for workspace with id [workspaceId]
|
|
||||||
* and environment [environment]
|
// verify workspace id/environment
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const createFolder = async (req: Request, res: Response) => {
|
export const createFolder = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Create a folder'
|
#swagger.summary = 'Create a folder'
|
||||||
@@ -107,7 +101,7 @@ export const createFolder = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
body: { workspaceId, environment, folderName, parentFolderId }
|
body: { workspaceId, environment, folderName, directory }
|
||||||
} = await validateRequest(reqValidator.CreateFolderV1, req);
|
} = await validateRequest(reqValidator.CreateFolderV1, req);
|
||||||
|
|
||||||
if (!validateFolderName(folderName)) {
|
if (!validateFolderName(folderName)) {
|
||||||
@@ -116,33 +110,29 @@ export const createFolder = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
// token check
|
||||||
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, directory);
|
||||||
|
if (!isValidScopeAccess) {
|
||||||
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// user check
|
||||||
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const folders = await Folder.findOne({
|
const folders = await Folder.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment
|
environment
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
if (req.user) {
|
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
|
||||||
const secretPath =
|
|
||||||
folders && parentFolderId
|
|
||||||
? getFolderWithPathFromId(folders.nodes, parentFolderId).folderPath
|
|
||||||
: "/";
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// space has no folders initialized
|
// space has no folders initialized
|
||||||
|
|
||||||
if (!folders) {
|
if (!folders) {
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
if (directory !== "/") throw ERR_FOLDER_NOT_FOUND;
|
||||||
// root check
|
|
||||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, "/");
|
|
||||||
if (!isValidScopeAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = generateFolderId();
|
const id = generateFolderId();
|
||||||
const folder = new Folder({
|
const folder = new Folder({
|
||||||
@@ -186,27 +176,10 @@ export const createFolder = async (req: Request, res: Response) => {
|
|||||||
return res.json({ folder: { id, name: folderName } });
|
return res.json({ folder: { id, name: folderName } });
|
||||||
}
|
}
|
||||||
|
|
||||||
const folder = appendFolder(folders.nodes, { folderName, parentFolderId });
|
const parentFolder = getFolderByPath(folders.nodes, directory);
|
||||||
|
if (!parentFolder) throw ERR_FOLDER_NOT_FOUND;
|
||||||
await Folder.findByIdAndUpdate(folders._id, folders);
|
|
||||||
|
|
||||||
const { folder: parentFolder, folderPath: parentFolderPath } = getFolderWithPathFromId(
|
|
||||||
folders.nodes,
|
|
||||||
parentFolderId || "root"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
// root check
|
|
||||||
const isValidScopeAccess = isValidScope(
|
|
||||||
req.authData.authPayload,
|
|
||||||
environment,
|
|
||||||
parentFolderPath
|
|
||||||
);
|
|
||||||
if (!isValidScopeAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const folder = appendFolder(folders.nodes, { folderName, parentFolderId: parentFolder.id });
|
||||||
await Folder.findByIdAndUpdate(folders._id, folders);
|
await Folder.findByIdAndUpdate(folders._id, folders);
|
||||||
|
|
||||||
const folderVersion = new FolderVersion({
|
const folderVersion = new FolderVersion({
|
||||||
@@ -219,11 +192,9 @@ export const createFolder = async (req: Request, res: Response) => {
|
|||||||
await EESecretService.takeSecretSnapshot({
|
await EESecretService.takeSecretSnapshot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folderId: parentFolderId
|
folderId: parentFolder.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, folder.id);
|
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
{
|
{
|
||||||
@@ -232,7 +203,7 @@ export const createFolder = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
folderName,
|
folderName,
|
||||||
folderPath
|
folderPath: directory
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -332,8 +303,8 @@ export const updateFolderById = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
body: { workspaceId, environment, name },
|
body: { workspaceId, environment, name, directory },
|
||||||
params: { folderId }
|
params: { folderName }
|
||||||
} = await validateRequest(reqValidator.UpdateFolderV1, req);
|
} = await validateRequest(reqValidator.UpdateFolderV1, req);
|
||||||
|
|
||||||
if (!validateFolderName(name)) {
|
if (!validateFolderName(name)) {
|
||||||
@@ -342,38 +313,31 @@ export const updateFolderById = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, directory);
|
||||||
|
if (!isValidScopeAccess) {
|
||||||
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
if (!folders) {
|
if (!folders) {
|
||||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
throw BadRequestError({ message: "The folder doesn't exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentFolder = getParentFromFolderId(folders.nodes, folderId);
|
const parentFolder = getFolderByPath(folders.nodes, directory);
|
||||||
if (!parentFolder) {
|
if (!parentFolder) {
|
||||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
throw BadRequestError({ message: "The folder doesn't exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.user) {
|
const folder = parentFolder.children.find(({ name }) => name === folderName);
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
if (!folder) throw ERR_FOLDER_NOT_FOUND;
|
||||||
const secretPath = getFolderWithPathFromId(folders.nodes, parentFolder.id).folderPath;
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const folder = parentFolder.children.find(({ id }) => id === folderId);
|
|
||||||
if (!folder) {
|
|
||||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
|
|
||||||
// root check
|
|
||||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
|
||||||
if (!isValidScopeAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldFolderName = folder.name;
|
const oldFolderName = folder.name;
|
||||||
parentFolder.version += 1;
|
parentFolder.version += 1;
|
||||||
@@ -505,24 +469,12 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
params: { folderId },
|
params: { folderName },
|
||||||
body: { environment, workspaceId }
|
body: { environment, workspaceId, directory }
|
||||||
} = await validateRequest(reqValidator.DeleteFolderV1, req);
|
} = await validateRequest(reqValidator.DeleteFolderV1, req);
|
||||||
|
|
||||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
|
||||||
if (!folders) {
|
|
||||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const delOp = deleteFolderById(folders.nodes, folderId);
|
|
||||||
if (!delOp) {
|
|
||||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
|
||||||
}
|
|
||||||
const { deletedNode: delFolder, parent: parentFolder } = delOp;
|
|
||||||
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
|
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, directory);
|
||||||
if (!isValidScopeAccess) {
|
if (!isValidScopeAccess) {
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
@@ -531,12 +483,23 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
|||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
|
if (!folders) throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
|
||||||
|
const parentFolder = getFolderByPath(folders.nodes, directory);
|
||||||
|
if (!parentFolder) throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
|
||||||
|
const index = parentFolder.children.findIndex(({ name }) => name === folderName);
|
||||||
|
if (index === -1) throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
|
||||||
|
const deletedFolder = parentFolder.children.splice(index, 1)[0];
|
||||||
|
|
||||||
parentFolder.version += 1;
|
parentFolder.version += 1;
|
||||||
const delFolderIds = getAllFolderIds(delFolder);
|
const delFolderIds = getAllFolderIds(deletedFolder);
|
||||||
|
|
||||||
await Folder.findByIdAndUpdate(folders._id, folders);
|
await Folder.findByIdAndUpdate(folders._id, folders);
|
||||||
const folderVersion = new FolderVersion({
|
const folderVersion = new FolderVersion({
|
||||||
@@ -565,9 +528,9 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
|||||||
type: EventType.DELETE_FOLDER,
|
type: EventType.DELETE_FOLDER,
|
||||||
metadata: {
|
metadata: {
|
||||||
environment,
|
environment,
|
||||||
folderId,
|
folderId: deletedFolder.id,
|
||||||
folderName: delFolder.name,
|
folderName: deletedFolder.name,
|
||||||
folderPath: secretPath
|
folderPath: directory
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -575,7 +538,7 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
res.send({ message: "successfully deleted folders", folders: delFolderIds });
|
return res.send({ message: "successfully deleted folders", folders: delFolderIds });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -677,69 +640,27 @@ export const getFolders = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
query: { workspaceId, environment, parentFolderId, parentFolderPath }
|
query: { workspaceId, environment, directory }
|
||||||
} = await validateRequest(reqValidator.GetFoldersV1, req);
|
} = await validateRequest(reqValidator.GetFoldersV1, req);
|
||||||
|
|
||||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
|
||||||
|
|
||||||
if (req.user) await getUserProjectPermissions(req.user._id, workspaceId);
|
|
||||||
|
|
||||||
if (!folders) {
|
|
||||||
res.send({ folders: [], dir: [] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if instead of parentFolderId given a path like /folder1/folder2
|
|
||||||
if (parentFolderPath) {
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
const isValidScopeAccess = isValidScope(
|
|
||||||
req.authData.authPayload,
|
|
||||||
environment,
|
|
||||||
parentFolderPath
|
|
||||||
);
|
|
||||||
if (!isValidScopeAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const folder = getFolderByPath(folders.nodes, parentFolderPath);
|
|
||||||
|
|
||||||
if (!folder) {
|
|
||||||
res.send({ folders: [], dir: [] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// dir is not needed at present as this is only used in overview section of secrets
|
|
||||||
res.send({
|
|
||||||
folders: folder.children.map(({ id, name }) => ({ id, name })),
|
|
||||||
dir: [{ name: folder.name, id: folder.id }]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parentFolderId) {
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, "/");
|
|
||||||
if (!isValidScopeAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootFolders = folders.nodes.children.map(({ id, name }) => ({
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
}));
|
|
||||||
res.send({ folders: rootFolders });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { folder, folderPath, dir } = getFolderWithPathFromId(folders.nodes, parentFolderId);
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, folderPath);
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, directory);
|
||||||
if (!isValidScopeAccess) {
|
if (!isValidScopeAccess) {
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// check that user is a member of the workspace
|
||||||
|
await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send({
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
folders: folder.children.map(({ id, name }) => ({ id, name })),
|
if (!folders) {
|
||||||
dir
|
return res.send({ folders: [], dir: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const folder = getFolderByPath(folders.nodes, directory);
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
folders: folder?.children?.map(({ id, name }) => ({ id, name })) || []
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -196,7 +196,15 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
export const createSecretRaw = async (req: Request, res: Response) => {
|
export const createSecretRaw = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { secretName },
|
params: { secretName },
|
||||||
body: { secretPath, environment, workspaceId, type, secretValue, secretComment }
|
body: {
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
type,
|
||||||
|
secretValue,
|
||||||
|
secretComment,
|
||||||
|
skipMultilineEncoding
|
||||||
|
}
|
||||||
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
|
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
if (req.user?._id) {
|
||||||
@@ -249,7 +257,8 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||||
secretCommentIV: secretCommentEncrypted.iv,
|
secretCommentIV: secretCommentEncrypted.iv,
|
||||||
secretCommentTag: secretCommentEncrypted.tag
|
secretCommentTag: secretCommentEncrypted.tag,
|
||||||
|
skipMultilineEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
@@ -279,7 +288,7 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { secretName },
|
params: { secretName },
|
||||||
body: { secretValue, environment, secretPath, type, workspaceId }
|
body: { secretValue, environment, secretPath, type, workspaceId, skipMultilineEncoding }
|
||||||
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
|
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
if (req.user?._id) {
|
||||||
@@ -316,7 +325,8 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||||
secretValueIV: secretValueEncrypted.iv,
|
secretValueIV: secretValueEncrypted.iv,
|
||||||
secretValueTag: secretValueEncrypted.tag,
|
secretValueTag: secretValueEncrypted.tag,
|
||||||
secretPath
|
secretPath,
|
||||||
|
skipMultilineEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
@@ -540,7 +550,8 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretCommentCiphertext
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding
|
||||||
},
|
},
|
||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.CreateSecretV3, req);
|
} = await validateRequest(reqValidator.CreateSecretV3, req);
|
||||||
@@ -577,7 +588,8 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
metadata
|
metadata,
|
||||||
|
skipMultilineEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
@@ -610,11 +622,23 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
type,
|
type,
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
workspaceId
|
workspaceId,
|
||||||
|
tags,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretName: newSecretName,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
skipMultilineEncoding
|
||||||
},
|
},
|
||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.UpdateSecretByNameV3, req);
|
} = await validateRequest(reqValidator.UpdateSecretByNameV3, req);
|
||||||
|
|
||||||
|
if (newSecretName && (!secretKeyIV || !secretKeyTag || !secretKeyCiphertext))
|
||||||
|
throw BadRequestError({ message: "Missing encrypted key" });
|
||||||
|
|
||||||
if (req.user?._id) {
|
if (req.user?._id) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -637,10 +661,19 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
|
newSecretName,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretPath
|
secretPath,
|
||||||
|
tags,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
@@ -704,3 +737,105 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
|||||||
secret
|
secret
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createSecretByNameBatch = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
body: { secrets, secretPath, environment, workspaceId }
|
||||||
|
} = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req);
|
||||||
|
|
||||||
|
if (req.user?._id) {
|
||||||
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdSecrets = await SecretService.createSecretBatch({
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
secrets,
|
||||||
|
authData: req.authData
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets: createdSecrets
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateSecretByNameBatch = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
body: { secrets, secretPath, environment, workspaceId }
|
||||||
|
} = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req);
|
||||||
|
|
||||||
|
if (req.user?._id) {
|
||||||
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSecrets = await SecretService.updateSecretBatch({
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
secrets,
|
||||||
|
authData: req.authData
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets: updatedSecrets
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
body: { secrets, secretPath, environment, workspaceId }
|
||||||
|
} = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req);
|
||||||
|
|
||||||
|
if (req.user?._id) {
|
||||||
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedSecrets = await SecretService.deleteSecretBatch({
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
secrets,
|
||||||
|
authData: req.authData
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets: deletedSecrets
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
import { EESecretService } from "../../services";
|
import { EESecretService } from "../../services";
|
||||||
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
||||||
// import Folder, { TFolderSchema } from "../../../models/folder";
|
// import Folder, { TFolderSchema } from "../../../models/folder";
|
||||||
import { searchByFolderId } from "../../../services/FolderService";
|
import { getFolderByPath, searchByFolderId } from "../../../services/FolderService";
|
||||||
import { EEAuditLogService, EELicenseService } from "../../services";
|
import { EEAuditLogService, EELicenseService } from "../../services";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||||
import { validateRequest } from "../../../helpers/validation";
|
import { validateRequest } from "../../../helpers/validation";
|
||||||
@@ -104,7 +104,7 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
|
|||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
params: { workspaceId },
|
params: { workspaceId },
|
||||||
query: { environment, folderId, offset, limit }
|
query: { environment, directory, offset, limit }
|
||||||
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
|
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
@@ -113,10 +113,20 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
|
|||||||
ProjectPermissionSub.SecretRollback
|
ProjectPermissionSub.SecretRollback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let folderId = "root";
|
||||||
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
|
if (!folders && directory !== "/") throw BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders?.nodes, directory);
|
||||||
|
if (!folder) throw BadRequestError({ message: "Invalid folder id" });
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
const secretSnapshots = await SecretSnapshot.find({
|
const secretSnapshots = await SecretSnapshot.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId: folderId || "root"
|
folderId
|
||||||
})
|
})
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
@@ -135,7 +145,7 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
|
|||||||
export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Response) => {
|
export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { workspaceId },
|
params: { workspaceId },
|
||||||
query: { environment, folderId }
|
query: { environment, directory }
|
||||||
} = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
|
} = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
@@ -144,10 +154,20 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
|
|||||||
ProjectPermissionSub.SecretRollback
|
ProjectPermissionSub.SecretRollback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let folderId = "root";
|
||||||
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
|
if (!folders && directory !== "/") throw BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders?.nodes, directory);
|
||||||
|
if (!folder) throw BadRequestError({ message: "Invalid folder id" });
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
const count = await SecretSnapshot.countDocuments({
|
const count = await SecretSnapshot.countDocuments({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId: folderId || "root"
|
folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@@ -215,7 +235,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
params: { workspaceId },
|
params: { workspaceId },
|
||||||
body: { folderId, environment, version }
|
body: { directory, environment, version }
|
||||||
} = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req);
|
} = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req);
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
@@ -224,6 +244,16 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
ProjectPermissionSub.SecretRollback
|
ProjectPermissionSub.SecretRollback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let folderId = "root";
|
||||||
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
|
if (!folders && directory !== "/") throw BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders?.nodes, directory);
|
||||||
|
if (!folder) throw BadRequestError({ message: "Invalid folder id" });
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
// validate secret snapshot
|
// validate secret snapshot
|
||||||
const secretSnapshot = await SecretSnapshot.findOne({
|
const secretSnapshot = await SecretSnapshot.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
|
|||||||
@@ -1,47 +1,50 @@
|
|||||||
export enum ActorType {
|
export enum ActorType {
|
||||||
USER = "user",
|
USER = "user",
|
||||||
SERVICE = "service"
|
SERVICE = "service"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserAgentType {
|
export enum UserAgentType {
|
||||||
WEB = "web",
|
WEB = "web",
|
||||||
CLI = "cli",
|
CLI = "cli",
|
||||||
K8_OPERATOR = "k8-operator",
|
K8_OPERATOR = "k8-operator",
|
||||||
OTHER = "other"
|
OTHER = "other"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
GET_SECRETS = "get-secrets",
|
GET_SECRETS = "get-secrets",
|
||||||
GET_SECRET = "get-secret",
|
GET_SECRET = "get-secret",
|
||||||
REVEAL_SECRET = "reveal-secret",
|
REVEAL_SECRET = "reveal-secret",
|
||||||
CREATE_SECRET = "create-secret",
|
CREATE_SECRET = "create-secret",
|
||||||
UPDATE_SECRET = "update-secret",
|
CREATE_SECRETS = "create-secrets",
|
||||||
DELETE_SECRET = "delete-secret",
|
UPDATE_SECRET = "update-secret",
|
||||||
GET_WORKSPACE_KEY = "get-workspace-key",
|
UPDATE_SECRETS = "update-secrets",
|
||||||
AUTHORIZE_INTEGRATION = "authorize-integration",
|
DELETE_SECRET = "delete-secret",
|
||||||
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
DELETE_SECRETS = "delete-secrets",
|
||||||
CREATE_INTEGRATION = "create-integration",
|
GET_WORKSPACE_KEY = "get-workspace-key",
|
||||||
DELETE_INTEGRATION = "delete-integration",
|
AUTHORIZE_INTEGRATION = "authorize-integration",
|
||||||
ADD_TRUSTED_IP = "add-trusted-ip",
|
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
||||||
UPDATE_TRUSTED_IP = "update-trusted-ip",
|
CREATE_INTEGRATION = "create-integration",
|
||||||
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
DELETE_INTEGRATION = "delete-integration",
|
||||||
CREATE_SERVICE_TOKEN = "create-service-token",
|
ADD_TRUSTED_IP = "add-trusted-ip",
|
||||||
DELETE_SERVICE_TOKEN = "delete-service-token",
|
UPDATE_TRUSTED_IP = "update-trusted-ip",
|
||||||
CREATE_ENVIRONMENT = "create-environment",
|
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
||||||
UPDATE_ENVIRONMENT = "update-environment",
|
CREATE_SERVICE_TOKEN = "create-service-token",
|
||||||
DELETE_ENVIRONMENT = "delete-environment",
|
DELETE_SERVICE_TOKEN = "delete-service-token",
|
||||||
ADD_WORKSPACE_MEMBER = "add-workspace-member",
|
CREATE_ENVIRONMENT = "create-environment",
|
||||||
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
|
UPDATE_ENVIRONMENT = "update-environment",
|
||||||
CREATE_FOLDER = "create-folder",
|
DELETE_ENVIRONMENT = "delete-environment",
|
||||||
UPDATE_FOLDER = "update-folder",
|
ADD_WORKSPACE_MEMBER = "add-workspace-member",
|
||||||
DELETE_FOLDER = "delete-folder",
|
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
|
||||||
CREATE_WEBHOOK = "create-webhook",
|
CREATE_FOLDER = "create-folder",
|
||||||
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
UPDATE_FOLDER = "update-folder",
|
||||||
DELETE_WEBHOOK = "delete-webhook",
|
DELETE_FOLDER = "delete-folder",
|
||||||
GET_SECRET_IMPORTS = "get-secret-imports",
|
CREATE_WEBHOOK = "create-webhook",
|
||||||
CREATE_SECRET_IMPORT = "create-secret-import",
|
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
||||||
UPDATE_SECRET_IMPORT = "update-secret-import",
|
DELETE_WEBHOOK = "delete-webhook",
|
||||||
DELETE_SECRET_IMPORT = "delete-secret-import",
|
GET_SECRET_IMPORTS = "get-secret-imports",
|
||||||
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
|
CREATE_SECRET_IMPORT = "create-secret-import",
|
||||||
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions"
|
UPDATE_SECRET_IMPORT = "update-secret-import",
|
||||||
}
|
DELETE_SECRET_IMPORT = "delete-secret-import",
|
||||||
|
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
|
||||||
|
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,403 +1,428 @@
|
|||||||
import {
|
import { ActorType, EventType } from "./enums";
|
||||||
ActorType,
|
|
||||||
EventType
|
|
||||||
} from "./enums";
|
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServiceActorMetadata {
|
interface ServiceActorMetadata {
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserActor {
|
export interface UserActor {
|
||||||
type: ActorType.USER;
|
type: ActorType.USER;
|
||||||
metadata: UserActorMetadata;
|
metadata: UserActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceActor {
|
export interface ServiceActor {
|
||||||
type: ActorType.SERVICE;
|
type: ActorType.SERVICE;
|
||||||
metadata: ServiceActorMetadata;
|
metadata: ServiceActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Actor =
|
export type Actor = UserActor | ServiceActor;
|
||||||
| UserActor
|
|
||||||
| ServiceActor;
|
|
||||||
|
|
||||||
interface GetSecretsEvent {
|
interface GetSecretsEvent {
|
||||||
type: EventType.GET_SECRETS;
|
type: EventType.GET_SECRETS;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
numberOfSecrets: number;
|
numberOfSecrets: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetSecretEvent {
|
interface GetSecretEvent {
|
||||||
type: EventType.GET_SECRET;
|
type: EventType.GET_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateSecretEvent {
|
interface CreateSecretEvent {
|
||||||
type: EventType.CREATE_SECRET;
|
type: EventType.CREATE_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateSecretBatchEvent {
|
||||||
|
type: EventType.CREATE_SECRETS;
|
||||||
|
metadata: {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSecretEvent {
|
interface UpdateSecretEvent {
|
||||||
type: EventType.UPDATE_SECRET;
|
type: EventType.UPDATE_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSecretBatchEvent {
|
||||||
|
type: EventType.UPDATE_SECRETS;
|
||||||
|
metadata: {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteSecretEvent {
|
interface DeleteSecretEvent {
|
||||||
type: EventType.DELETE_SECRET;
|
type: EventType.DELETE_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSecretBatchEvent {
|
||||||
|
type: EventType.DELETE_SECRETS;
|
||||||
|
metadata: {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetWorkspaceKeyEvent {
|
interface GetWorkspaceKeyEvent {
|
||||||
type: EventType.GET_WORKSPACE_KEY,
|
type: EventType.GET_WORKSPACE_KEY;
|
||||||
metadata: {
|
metadata: {
|
||||||
keyId: string;
|
keyId: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthorizeIntegrationEvent {
|
interface AuthorizeIntegrationEvent {
|
||||||
type: EventType.AUTHORIZE_INTEGRATION;
|
type: EventType.AUTHORIZE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integration: string;
|
integration: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UnauthorizeIntegrationEvent {
|
interface UnauthorizeIntegrationEvent {
|
||||||
type: EventType.UNAUTHORIZE_INTEGRATION;
|
type: EventType.UNAUTHORIZE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integration: string;
|
integration: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateIntegrationEvent {
|
interface CreateIntegrationEvent {
|
||||||
type: EventType.CREATE_INTEGRATION;
|
type: EventType.CREATE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integrationId: string;
|
integrationId: string;
|
||||||
integration: string; // TODO: fix type
|
integration: string; // TODO: fix type
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
app?: string;
|
app?: string;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
targetEnvironmentId?: string;
|
targetEnvironmentId?: string;
|
||||||
targetService?: string;
|
targetService?: string;
|
||||||
targetServiceId?: string;
|
targetServiceId?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteIntegrationEvent {
|
interface DeleteIntegrationEvent {
|
||||||
type: EventType.DELETE_INTEGRATION;
|
type: EventType.DELETE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
integrationId: string;
|
integrationId: string;
|
||||||
integration: string; // TODO: fix type
|
integration: string; // TODO: fix type
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
app?: string;
|
app?: string;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
targetEnvironmentId?: string;
|
targetEnvironmentId?: string;
|
||||||
targetService?: string;
|
targetService?: string;
|
||||||
targetServiceId?: string;
|
targetServiceId?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddTrustedIPEvent {
|
interface AddTrustedIPEvent {
|
||||||
type: EventType.ADD_TRUSTED_IP;
|
type: EventType.ADD_TRUSTED_IP;
|
||||||
metadata: {
|
metadata: {
|
||||||
trustedIpId: string;
|
trustedIpId: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
prefix?: number;
|
prefix?: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateTrustedIPEvent {
|
interface UpdateTrustedIPEvent {
|
||||||
type: EventType.UPDATE_TRUSTED_IP;
|
type: EventType.UPDATE_TRUSTED_IP;
|
||||||
metadata: {
|
metadata: {
|
||||||
trustedIpId: string;
|
trustedIpId: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
prefix?: number;
|
prefix?: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteTrustedIPEvent {
|
interface DeleteTrustedIPEvent {
|
||||||
type: EventType.DELETE_TRUSTED_IP;
|
type: EventType.DELETE_TRUSTED_IP;
|
||||||
metadata: {
|
metadata: {
|
||||||
trustedIpId: string;
|
trustedIpId: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
prefix?: number;
|
prefix?: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateServiceTokenEvent {
|
interface CreateServiceTokenEvent {
|
||||||
type: EventType.CREATE_SERVICE_TOKEN;
|
type: EventType.CREATE_SERVICE_TOKEN;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
scopes: Array<{
|
scopes: Array<{
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteServiceTokenEvent {
|
interface DeleteServiceTokenEvent {
|
||||||
type: EventType.DELETE_SERVICE_TOKEN;
|
type: EventType.DELETE_SERVICE_TOKEN;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
scopes: Array<{
|
scopes: Array<{
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateEnvironmentEvent {
|
interface CreateEnvironmentEvent {
|
||||||
type: EventType.CREATE_ENVIRONMENT;
|
type: EventType.CREATE_ENVIRONMENT;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateEnvironmentEvent {
|
interface UpdateEnvironmentEvent {
|
||||||
type: EventType.UPDATE_ENVIRONMENT;
|
type: EventType.UPDATE_ENVIRONMENT;
|
||||||
metadata: {
|
metadata: {
|
||||||
oldName: string;
|
oldName: string;
|
||||||
newName: string;
|
newName: string;
|
||||||
oldSlug: string;
|
oldSlug: string;
|
||||||
newSlug: string;
|
newSlug: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteEnvironmentEvent {
|
interface DeleteEnvironmentEvent {
|
||||||
type: EventType.DELETE_ENVIRONMENT;
|
type: EventType.DELETE_ENVIRONMENT;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddWorkspaceMemberEvent {
|
interface AddWorkspaceMemberEvent {
|
||||||
type: EventType.ADD_WORKSPACE_MEMBER;
|
type: EventType.ADD_WORKSPACE_MEMBER;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoveWorkspaceMemberEvent {
|
interface RemoveWorkspaceMemberEvent {
|
||||||
type: EventType.REMOVE_WORKSPACE_MEMBER;
|
type: EventType.REMOVE_WORKSPACE_MEMBER;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateFolderEvent {
|
interface CreateFolderEvent {
|
||||||
type: EventType.CREATE_FOLDER;
|
type: EventType.CREATE_FOLDER;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
folderName: string;
|
folderName: string;
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateFolderEvent {
|
interface UpdateFolderEvent {
|
||||||
type: EventType.UPDATE_FOLDER;
|
type: EventType.UPDATE_FOLDER;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
oldFolderName: string;
|
oldFolderName: string;
|
||||||
newFolderName: string;
|
newFolderName: string;
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteFolderEvent {
|
interface DeleteFolderEvent {
|
||||||
type: EventType.DELETE_FOLDER;
|
type: EventType.DELETE_FOLDER;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
folderName: string;
|
folderName: string;
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateWebhookEvent {
|
interface CreateWebhookEvent {
|
||||||
type: EventType.CREATE_WEBHOOK,
|
type: EventType.CREATE_WEBHOOK;
|
||||||
metadata: {
|
metadata: {
|
||||||
webhookId: string;
|
webhookId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateWebhookStatusEvent {
|
interface UpdateWebhookStatusEvent {
|
||||||
type: EventType.UPDATE_WEBHOOK_STATUS,
|
type: EventType.UPDATE_WEBHOOK_STATUS;
|
||||||
metadata: {
|
metadata: {
|
||||||
webhookId: string;
|
webhookId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteWebhookEvent {
|
interface DeleteWebhookEvent {
|
||||||
type: EventType.DELETE_WEBHOOK,
|
type: EventType.DELETE_WEBHOOK;
|
||||||
metadata: {
|
metadata: {
|
||||||
webhookId: string;
|
webhookId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetSecretImportsEvent {
|
interface GetSecretImportsEvent {
|
||||||
type: EventType.GET_SECRET_IMPORTS,
|
type: EventType.GET_SECRET_IMPORTS;
|
||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
numberOfImports: number;
|
numberOfImports: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateSecretImportEvent {
|
interface CreateSecretImportEvent {
|
||||||
type: EventType.CREATE_SECRET_IMPORT,
|
type: EventType.CREATE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
importFromEnvironment: string;
|
importFromEnvironment: string;
|
||||||
importFromSecretPath: string;
|
importFromSecretPath: string;
|
||||||
importToEnvironment: string;
|
importToEnvironment: string;
|
||||||
importToSecretPath: string;
|
importToSecretPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSecretImportEvent {
|
interface UpdateSecretImportEvent {
|
||||||
type: EventType.UPDATE_SECRET_IMPORT,
|
type: EventType.UPDATE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
importToEnvironment: string;
|
importToEnvironment: string;
|
||||||
importToSecretPath: string;
|
importToSecretPath: string;
|
||||||
orderBefore: {
|
orderBefore: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}[],
|
}[];
|
||||||
orderAfter: {
|
orderAfter: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}[]
|
}[];
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteSecretImportEvent {
|
interface DeleteSecretImportEvent {
|
||||||
type: EventType.DELETE_SECRET_IMPORT,
|
type: EventType.DELETE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
secretImportId: string;
|
secretImportId: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
importFromEnvironment: string;
|
importFromEnvironment: string;
|
||||||
importFromSecretPath: string;
|
importFromSecretPath: string;
|
||||||
importToEnvironment: string;
|
importToEnvironment: string;
|
||||||
importToSecretPath: string;
|
importToSecretPath: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateUserRole {
|
interface UpdateUserRole {
|
||||||
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
|
type: EventType.UPDATE_USER_WORKSPACE_ROLE;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
oldRole: string;
|
oldRole: string;
|
||||||
newRole: string;
|
newRole: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateUserDeniedPermissions {
|
interface UpdateUserDeniedPermissions {
|
||||||
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS,
|
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
deniedPermissions: {
|
deniedPermissions: {
|
||||||
environmentSlug: string;
|
environmentSlug: string;
|
||||||
ability: string;
|
ability: string;
|
||||||
}[]
|
}[];
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
| CreateSecretEvent
|
| CreateSecretEvent
|
||||||
| UpdateSecretEvent
|
| CreateSecretBatchEvent
|
||||||
| DeleteSecretEvent
|
| UpdateSecretEvent
|
||||||
| GetWorkspaceKeyEvent
|
| UpdateSecretBatchEvent
|
||||||
| AuthorizeIntegrationEvent
|
| DeleteSecretEvent
|
||||||
| UnauthorizeIntegrationEvent
|
| DeleteSecretBatchEvent
|
||||||
| CreateIntegrationEvent
|
| GetWorkspaceKeyEvent
|
||||||
| DeleteIntegrationEvent
|
| AuthorizeIntegrationEvent
|
||||||
| AddTrustedIPEvent
|
| UnauthorizeIntegrationEvent
|
||||||
| UpdateTrustedIPEvent
|
| CreateIntegrationEvent
|
||||||
| DeleteTrustedIPEvent
|
| DeleteIntegrationEvent
|
||||||
| CreateServiceTokenEvent
|
| AddTrustedIPEvent
|
||||||
| DeleteServiceTokenEvent
|
| UpdateTrustedIPEvent
|
||||||
| CreateEnvironmentEvent
|
| DeleteTrustedIPEvent
|
||||||
| UpdateEnvironmentEvent
|
| CreateServiceTokenEvent
|
||||||
| DeleteEnvironmentEvent
|
| DeleteServiceTokenEvent
|
||||||
| AddWorkspaceMemberEvent
|
| CreateEnvironmentEvent
|
||||||
| RemoveWorkspaceMemberEvent
|
| UpdateEnvironmentEvent
|
||||||
| CreateFolderEvent
|
| DeleteEnvironmentEvent
|
||||||
| UpdateFolderEvent
|
| AddWorkspaceMemberEvent
|
||||||
| DeleteFolderEvent
|
| RemoveWorkspaceMemberEvent
|
||||||
| CreateWebhookEvent
|
| CreateFolderEvent
|
||||||
| UpdateWebhookStatusEvent
|
| UpdateFolderEvent
|
||||||
| DeleteWebhookEvent
|
| DeleteFolderEvent
|
||||||
| GetSecretImportsEvent
|
| CreateWebhookEvent
|
||||||
| CreateSecretImportEvent
|
| UpdateWebhookStatusEvent
|
||||||
| UpdateSecretImportEvent
|
| DeleteWebhookEvent
|
||||||
| DeleteSecretImportEvent
|
| GetSecretImportsEvent
|
||||||
| UpdateUserRole
|
| CreateSecretImportEvent
|
||||||
| UpdateUserDeniedPermissions;
|
| UpdateSecretImportEvent
|
||||||
|
| DeleteSecretImportEvent
|
||||||
|
| UpdateUserRole
|
||||||
|
| UpdateUserDeniedPermissions;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
ENCODING_SCHEME_BASE64,
|
ENCODING_SCHEME_BASE64,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
|
|
||||||
export interface ISecretVersion {
|
export interface ISecretVersion {
|
||||||
@@ -23,6 +23,7 @@ export interface ISecretVersion {
|
|||||||
secretValueCiphertext: string;
|
secretValueCiphertext: string;
|
||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
|
skipMultilineEncoding?: boolean;
|
||||||
algorithm: "aes-256-gcm";
|
algorithm: "aes-256-gcm";
|
||||||
keyEncoding: "utf8" | "base64";
|
keyEncoding: "utf8" | "base64";
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -36,95 +37,96 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
|||||||
// could be deleted
|
// could be deleted
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "Secret",
|
ref: "Secret",
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "Workspace",
|
ref: "Workspace",
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [SECRET_SHARED, SECRET_PERSONAL],
|
enum: [SECRET_SHARED, SECRET_PERSONAL],
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
// user associated with the personal secret
|
// user associated with the personal secret
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "User",
|
ref: "User"
|
||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
isDeleted: {
|
isDeleted: {
|
||||||
// consider removing field
|
// consider removing field
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretBlindIndex: {
|
secretBlindIndex: {
|
||||||
type: String,
|
type: String,
|
||||||
select: false,
|
select: false
|
||||||
},
|
},
|
||||||
secretKeyCiphertext: {
|
secretKeyCiphertext: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretKeyIV: {
|
secretKeyIV: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretKeyTag: {
|
secretKeyTag: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretValueCiphertext: {
|
secretValueCiphertext: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretValueIV: {
|
secretValueIV: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretValueTag: {
|
secretValueTag: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
|
},
|
||||||
|
skipMultilineEncoding: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
},
|
},
|
||||||
algorithm: {
|
algorithm: {
|
||||||
// the encryption algorithm used
|
// the encryption algorithm used
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ALGORITHM_AES_256_GCM],
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
required: true,
|
required: true,
|
||||||
default: ALGORITHM_AES_256_GCM,
|
default: ALGORITHM_AES_256_GCM
|
||||||
},
|
},
|
||||||
keyEncoding: {
|
keyEncoding: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
||||||
required: true,
|
required: true,
|
||||||
default: ENCODING_SCHEME_UTF8,
|
default: ENCODING_SCHEME_UTF8
|
||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
ref: "Tag",
|
ref: "Tag",
|
||||||
type: [Schema.Types.ObjectId],
|
type: [Schema.Types.ObjectId],
|
||||||
default: [],
|
default: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SecretVersion = model<ISecretVersion>(
|
export const SecretVersion = model<ISecretVersion>("SecretVersion", secretVersionSchema);
|
||||||
"SecretVersion",
|
|
||||||
secretVersionSchema
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
|
CreateSecretBatchParams,
|
||||||
CreateSecretParams,
|
CreateSecretParams,
|
||||||
|
DeleteSecretBatchParams,
|
||||||
DeleteSecretParams,
|
DeleteSecretParams,
|
||||||
GetSecretParams,
|
GetSecretParams,
|
||||||
GetSecretsParams,
|
GetSecretsParams,
|
||||||
|
UpdateSecretBatchParams,
|
||||||
UpdateSecretParams
|
UpdateSecretParams
|
||||||
} from "../interfaces/services/SecretService";
|
} from "../interfaces/services/SecretService";
|
||||||
import {
|
import {
|
||||||
@@ -71,6 +74,8 @@ export function containsGlobPatterns(secretPath: string) {
|
|||||||
return globChars.some((char) => normalizedPath.includes(char));
|
return globChars.some((char) => normalizedPath.includes(char));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ERR_FOLDER_NOT_FOUND = BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
|
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
|
||||||
*
|
*
|
||||||
@@ -330,7 +335,8 @@ export const createSecretHelper = async ({
|
|||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
secretPath = "/",
|
secretPath = "/",
|
||||||
metadata
|
metadata,
|
||||||
|
skipMultilineEncoding
|
||||||
}: CreateSecretParams) => {
|
}: CreateSecretParams) => {
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -394,6 +400,7 @@ export const createSecretHelper = async ({
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
|
skipMultilineEncoding,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
@@ -416,6 +423,7 @@ export const createSecretHelper = async ({
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
|
skipMultilineEncoding,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
@@ -740,19 +748,50 @@ export const updateSecretHelper = async ({
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
authData,
|
authData,
|
||||||
|
newSecretName,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretPath
|
secretPath,
|
||||||
|
tags,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
skipMultilineEncoding
|
||||||
}: UpdateSecretParams) => {
|
}: UpdateSecretParams) => {
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
// get secret blind index salt
|
||||||
secretName,
|
const salt = await getSecretBlindIndexSaltHelper({
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||||
|
secretName,
|
||||||
|
salt
|
||||||
|
});
|
||||||
|
|
||||||
let secret: ISecret | null = null;
|
let secret: ISecret | null = null;
|
||||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
|
|
||||||
|
let newSecretNameBlindIndex = undefined;
|
||||||
|
if (newSecretName) {
|
||||||
|
newSecretNameBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||||
|
secretName,
|
||||||
|
salt
|
||||||
|
});
|
||||||
|
const doesSecretAlreadyExist = await Secret.exists({
|
||||||
|
secretBlindIndex: newSecretNameBlindIndex,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
folder: folderId,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
if (!doesSecretAlreadyExist)
|
||||||
|
throw BadRequestError({ message: "Secret with the provided name already exist" });
|
||||||
|
}
|
||||||
|
|
||||||
if (type === SECRET_SHARED) {
|
if (type === SECRET_SHARED) {
|
||||||
// case: update shared secret
|
// case: update shared secret
|
||||||
secret = await Secret.findOneAndUpdate(
|
secret = await Secret.findOneAndUpdate(
|
||||||
@@ -767,6 +806,15 @@ export const updateSecretHelper = async ({
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretBlindIndex: newSecretNameBlindIndex,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
tags,
|
||||||
$inc: { version: 1 }
|
$inc: { version: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -782,6 +830,9 @@ export const updateSecretHelper = async ({
|
|||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
...getAuthDataPayloadUserObj(authData)
|
...getAuthDataPayloadUserObj(authData)
|
||||||
},
|
},
|
||||||
@@ -789,6 +840,9 @@ export const updateSecretHelper = async ({
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
|
tags,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretBlindIndex: newSecretNameBlindIndex,
|
||||||
$inc: { version: 1 }
|
$inc: { version: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -805,16 +859,18 @@ export const updateSecretHelper = async ({
|
|||||||
workspace: secret.workspace,
|
workspace: secret.workspace,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
type,
|
type,
|
||||||
|
tags,
|
||||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||||
environment: secret.environment,
|
environment: secret.environment,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
secretBlindIndex,
|
secretBlindIndex: newSecretName ? newSecretNameBlindIndex : secretBlindIndex,
|
||||||
secretKeyCiphertext: secret.secretKeyCiphertext,
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
secretKeyIV: secret.secretKeyIV,
|
secretKeyIV: secret.secretKeyIV,
|
||||||
secretKeyTag: secret.secretKeyTag,
|
secretKeyTag: secret.secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
|
skipMultilineEncoding,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
@@ -1185,3 +1241,550 @@ export const expandSecrets = async (
|
|||||||
|
|
||||||
return secrets;
|
return secrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createSecretBatchHelper = async ({
|
||||||
|
secrets,
|
||||||
|
workspaceId,
|
||||||
|
authData,
|
||||||
|
secretPath,
|
||||||
|
environment
|
||||||
|
}: CreateSecretBatchParams) => {
|
||||||
|
let folderId = "root";
|
||||||
|
const folders = await Folder.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!folders && secretPath !== "/") throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||||
|
if (!folder) throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get secret blind index salt
|
||||||
|
const salt = await getSecretBlindIndexSaltHelper({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point
|
||||||
|
const secretBlindIndexes = await Promise.all(
|
||||||
|
secrets.map(({ secretName }) =>
|
||||||
|
generateSecretBlindIndexWithSaltHelper({
|
||||||
|
secretName,
|
||||||
|
salt
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).then((blindIndexes) =>
|
||||||
|
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
|
||||||
|
prev[secrets[i].secretName] = curr;
|
||||||
|
secretBlindIndexToKey[curr] = secrets[i].secretName;
|
||||||
|
return prev;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
|
const exists = await Secret.exists({
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
folder: folderId,
|
||||||
|
environment
|
||||||
|
})
|
||||||
|
.or(
|
||||||
|
secrets.map(({ secretName, type }) => ({
|
||||||
|
secretBlindIndex: secretBlindIndexes[secretName],
|
||||||
|
type: type,
|
||||||
|
...getAuthDataPayloadUserObj(authData)
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
throw BadRequestError({
|
||||||
|
message: "Failed to create secret that already exists"
|
||||||
|
});
|
||||||
|
|
||||||
|
// create secret
|
||||||
|
const newlyCreatedSecrets: ISecret[] = await Secret.insertMany(
|
||||||
|
secrets.map(
|
||||||
|
({
|
||||||
|
type,
|
||||||
|
secretName,
|
||||||
|
secretKeyIV,
|
||||||
|
metadata,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding
|
||||||
|
}) => ({
|
||||||
|
version: 1,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
type,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
folder: folderId,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
|
metadata,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretBlindIndex: secretBlindIndexes[secretName],
|
||||||
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: newlyCreatedSecrets.map(
|
||||||
|
(secret) =>
|
||||||
|
new SecretVersion({
|
||||||
|
secret: secret._id,
|
||||||
|
version: secret.version,
|
||||||
|
workspace: secret.workspace,
|
||||||
|
type: secret.type,
|
||||||
|
folder: folderId,
|
||||||
|
skipMultilineEncoding: secret?.skipMultilineEncoding,
|
||||||
|
...(secret.type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||||
|
environment: secret.environment,
|
||||||
|
isDeleted: false,
|
||||||
|
secretBlindIndex: secret.secretBlindIndex,
|
||||||
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
|
secretKeyIV: secret.secretKeyIV,
|
||||||
|
secretKeyTag: secret.secretKeyTag,
|
||||||
|
secretValueCiphertext: secret.secretValueCiphertext,
|
||||||
|
secretValueIV: secret.secretValueIV,
|
||||||
|
secretValueTag: secret.secretValueTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// (EE) create (audit) log
|
||||||
|
const action = await EELogService.createAction({
|
||||||
|
name: ACTION_ADD_SECRETS,
|
||||||
|
...getAuthDataPayloadIdObj(authData),
|
||||||
|
workspaceId,
|
||||||
|
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||||
|
});
|
||||||
|
|
||||||
|
action &&
|
||||||
|
(await EELogService.createLog({
|
||||||
|
...getAuthDataPayloadIdObj(authData),
|
||||||
|
workspaceId,
|
||||||
|
actions: [action],
|
||||||
|
channel: authData.userAgentType,
|
||||||
|
ipAddress: authData.ipAddress
|
||||||
|
}));
|
||||||
|
|
||||||
|
await EEAuditLogService.createAuditLog(
|
||||||
|
authData,
|
||||||
|
{
|
||||||
|
type: EventType.CREATE_SECRETS,
|
||||||
|
metadata: {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secrets: newlyCreatedSecrets.map(({ secretBlindIndex, version, _id }) => ({
|
||||||
|
secretId: _id.toString(),
|
||||||
|
secretKey: secretBlindIndexToKey[secretBlindIndex || ""],
|
||||||
|
secretVersion: version
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
workspaceId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: "secrets added",
|
||||||
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
|
authData
|
||||||
|
}),
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: 1,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
folderId,
|
||||||
|
channel: authData.userAgentType,
|
||||||
|
userAgent: authData.userAgent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newlyCreatedSecrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateSecretBatchHelper = async ({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
authData,
|
||||||
|
secretPath,
|
||||||
|
secrets
|
||||||
|
}: UpdateSecretBatchParams) => {
|
||||||
|
let folderId = "root";
|
||||||
|
const folders = await Folder.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!folders && secretPath !== "/") throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||||
|
if (!folder) throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get secret blind index salt
|
||||||
|
const salt = await getSecretBlindIndexSaltHelper({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point
|
||||||
|
const secretBlindIndexes = await Promise.all(
|
||||||
|
secrets.map(({ secretName }) =>
|
||||||
|
generateSecretBlindIndexWithSaltHelper({
|
||||||
|
secretName,
|
||||||
|
salt
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).then((blindIndexes) =>
|
||||||
|
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
|
||||||
|
prev[secrets[i].secretName] = curr;
|
||||||
|
secretBlindIndexToKey[curr] = secrets[i].secretName;
|
||||||
|
return prev;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretsToBeUpdated = await Secret.find({
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
folder: folderId,
|
||||||
|
environment
|
||||||
|
})
|
||||||
|
.select("+secretBlindIndex")
|
||||||
|
.or(
|
||||||
|
secrets.map(({ secretName, type }) => ({
|
||||||
|
secretBlindIndex: secretBlindIndexes[secretName],
|
||||||
|
type: type,
|
||||||
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
if (secretsToBeUpdated.length !== secrets.length)
|
||||||
|
throw BadRequestError({ message: "Some secrets not found" });
|
||||||
|
|
||||||
|
await Secret.bulkWrite(
|
||||||
|
secrets.map(
|
||||||
|
({
|
||||||
|
type,
|
||||||
|
secretName,
|
||||||
|
tags,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding
|
||||||
|
}) => ({
|
||||||
|
updateOne: {
|
||||||
|
filter: {
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
folder: folderId,
|
||||||
|
secretBlindIndex: secretBlindIndexes[secretName],
|
||||||
|
type,
|
||||||
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
$inc: {
|
||||||
|
version: 1
|
||||||
|
},
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
|
tags,
|
||||||
|
skipMultilineEncoding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretsGroupedByBlindIndex = secretsToBeUpdated.reduce<Record<string, ISecret>>(
|
||||||
|
(prev, curr) => {
|
||||||
|
if (curr.secretBlindIndex) prev[curr.secretBlindIndex] = curr;
|
||||||
|
return prev;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: secrets.map((secret) => {
|
||||||
|
const {
|
||||||
|
_id,
|
||||||
|
version,
|
||||||
|
workspace,
|
||||||
|
type,
|
||||||
|
secretBlindIndex,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
skipMultilineEncoding
|
||||||
|
} = secretsGroupedByBlindIndex[secretBlindIndexes[secret.secretName]];
|
||||||
|
|
||||||
|
return new SecretVersion({
|
||||||
|
secret: _id,
|
||||||
|
version: version + 1,
|
||||||
|
workspace: workspace,
|
||||||
|
type,
|
||||||
|
folder: folderId,
|
||||||
|
...(secret.type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||||
|
environment,
|
||||||
|
isDeleted: false,
|
||||||
|
secretBlindIndex: secretBlindIndex,
|
||||||
|
secretKeyCiphertext: secretKeyCiphertext,
|
||||||
|
secretKeyIV: secretKeyIV,
|
||||||
|
secretKeyTag: secretKeyTag,
|
||||||
|
secretValueCiphertext: secret.secretValueCiphertext,
|
||||||
|
secretValueIV: secret.secretValueIV,
|
||||||
|
secretValueTag: secret.secretValueTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
|
skipMultilineEncoding
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// (EE) create (audit) log
|
||||||
|
const action = await EELogService.createAction({
|
||||||
|
name: ACTION_UPDATE_SECRETS,
|
||||||
|
...getAuthDataPayloadIdObj(authData),
|
||||||
|
workspaceId,
|
||||||
|
secretIds: secretsToBeUpdated.map(({ _id }) => _id)
|
||||||
|
});
|
||||||
|
|
||||||
|
action &&
|
||||||
|
(await EELogService.createLog({
|
||||||
|
...getAuthDataPayloadIdObj(authData),
|
||||||
|
workspaceId,
|
||||||
|
actions: [action],
|
||||||
|
channel: authData.userAgentType,
|
||||||
|
ipAddress: authData.ipAddress
|
||||||
|
}));
|
||||||
|
|
||||||
|
await EEAuditLogService.createAuditLog(
|
||||||
|
authData,
|
||||||
|
{
|
||||||
|
type: EventType.UPDATE_SECRETS,
|
||||||
|
metadata: {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secrets: secretsToBeUpdated.map(({ _id, version, secretBlindIndex }) => ({
|
||||||
|
secretId: _id.toString(),
|
||||||
|
secretKey: secretBlindIndexToKey[secretBlindIndex || ""],
|
||||||
|
secretVersion: version + 1
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
workspaceId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: "secrets modified",
|
||||||
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
|
authData
|
||||||
|
}),
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: 1,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
folderId,
|
||||||
|
channel: authData.userAgentType,
|
||||||
|
userAgent: authData.userAgent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSecretBatchHelper = async ({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
authData,
|
||||||
|
secretPath = "/",
|
||||||
|
secrets
|
||||||
|
}: DeleteSecretBatchParams) => {
|
||||||
|
let folderId = "root";
|
||||||
|
const folders = await Folder.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!folders && secretPath !== "/") throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||||
|
if (!folder) throw ERR_FOLDER_NOT_FOUND;
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get secret blind index salt
|
||||||
|
const salt = await getSecretBlindIndexSaltHelper({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point
|
||||||
|
const secretBlindIndexes = await Promise.all(
|
||||||
|
secrets.map(({ secretName }) =>
|
||||||
|
generateSecretBlindIndexWithSaltHelper({
|
||||||
|
secretName,
|
||||||
|
salt
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).then((blindIndexes) =>
|
||||||
|
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
|
||||||
|
prev[secrets[i].secretName] = curr;
|
||||||
|
secretBlindIndexToKey[curr] = secrets[i].secretName;
|
||||||
|
return prev;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletedSecrets = await Secret.find({
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
folder: folderId,
|
||||||
|
environment
|
||||||
|
})
|
||||||
|
.or(
|
||||||
|
secrets.map(({ secretName, type }) => ({
|
||||||
|
secretBlindIndex: secretBlindIndexes[secretName],
|
||||||
|
type: type === "shared" ? { $in: ["shared", "personal"] } : type,
|
||||||
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.select({ secretBlindIndexes: 1 })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
await Secret.deleteMany({
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
folder: folderId,
|
||||||
|
environment
|
||||||
|
})
|
||||||
|
.or(
|
||||||
|
secrets.map(({ secretName, type }) => ({
|
||||||
|
secretBlindIndex: secretBlindIndexes[secretName],
|
||||||
|
type: type === "shared" ? { $in: ["shared", "personal"] } : type,
|
||||||
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
await EESecretService.markDeletedSecretVersions({
|
||||||
|
secretIds: deletedSecrets.map((secret) => secret._id)
|
||||||
|
});
|
||||||
|
|
||||||
|
// (EE) create (audit) log
|
||||||
|
const action = await EELogService.createAction({
|
||||||
|
name: ACTION_DELETE_SECRETS,
|
||||||
|
...getAuthDataPayloadIdObj(authData),
|
||||||
|
workspaceId,
|
||||||
|
secretIds: deletedSecrets.map((secret) => secret._id)
|
||||||
|
});
|
||||||
|
|
||||||
|
action &&
|
||||||
|
(await EELogService.createLog({
|
||||||
|
...getAuthDataPayloadIdObj(authData),
|
||||||
|
workspaceId,
|
||||||
|
actions: [action],
|
||||||
|
channel: authData.userAgentType,
|
||||||
|
ipAddress: authData.ipAddress
|
||||||
|
}));
|
||||||
|
|
||||||
|
await EEAuditLogService.createAuditLog(
|
||||||
|
authData,
|
||||||
|
{
|
||||||
|
type: EventType.DELETE_SECRETS,
|
||||||
|
metadata: {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secrets: deletedSecrets.map(({ _id, version, secretBlindIndex }) => ({
|
||||||
|
secretId: _id.toString(),
|
||||||
|
secretKey: secretBlindIndexToKey[secretBlindIndex || ""],
|
||||||
|
secretVersion: version
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
workspaceId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: "secrets deleted",
|
||||||
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
|
authData
|
||||||
|
}),
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
folderId,
|
||||||
|
channel: authData.userAgentType,
|
||||||
|
userAgent: authData.userAgent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
secrets: deletedSecrets
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface CreateSecretParams {
|
|||||||
secretCommentCiphertext?: string;
|
secretCommentCiphertext?: string;
|
||||||
secretCommentIV?: string;
|
secretCommentIV?: string;
|
||||||
secretCommentTag?: string;
|
secretCommentTag?: string;
|
||||||
|
skipMultilineEncoding?: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
source?: string;
|
source?: string;
|
||||||
@@ -42,6 +43,10 @@ export interface GetSecretParams {
|
|||||||
|
|
||||||
export interface UpdateSecretParams {
|
export interface UpdateSecretParams {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
|
newSecretName?: string;
|
||||||
|
secretKeyCiphertext?: string;
|
||||||
|
secretKeyIV?: string;
|
||||||
|
secretKeyTag?: string;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
type: "shared" | "personal";
|
type: "shared" | "personal";
|
||||||
@@ -50,6 +55,11 @@ export interface UpdateSecretParams {
|
|||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
skipMultilineEncoding?: boolean;
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeleteSecretParams {
|
export interface DeleteSecretParams {
|
||||||
@@ -60,3 +70,57 @@ export interface DeleteSecretParams {
|
|||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateSecretBatchParams {
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
authData: AuthData;
|
||||||
|
secretPath: string;
|
||||||
|
secrets: Array<{
|
||||||
|
secretName: string;
|
||||||
|
type: "shared" | "personal";
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
skipMultilineEncoding?: boolean;
|
||||||
|
metadata?: {
|
||||||
|
source?: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateSecretBatchParams {
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
authData: AuthData;
|
||||||
|
secretPath: string;
|
||||||
|
secrets: Array<{
|
||||||
|
secretName: string;
|
||||||
|
type: "shared" | "personal";
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
skipMultilineEncoding?: boolean;
|
||||||
|
tags?: string[];
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteSecretBatchParams {
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
authData: AuthData;
|
||||||
|
secretPath: string;
|
||||||
|
secrets: Array<{
|
||||||
|
secretName: string;
|
||||||
|
type: "shared" | "personal";
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
ENCODING_SCHEME_BASE64,
|
ENCODING_SCHEME_BASE64,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
export interface ISecret {
|
export interface ISecret {
|
||||||
@@ -12,7 +12,7 @@ export interface ISecret {
|
|||||||
version: number;
|
version: number;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
type: string;
|
type: string;
|
||||||
user: Types.ObjectId;
|
user?: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretBlindIndex?: string;
|
secretBlindIndex?: string;
|
||||||
secretKeyCiphertext: string;
|
secretKeyCiphertext: string;
|
||||||
@@ -27,13 +27,14 @@ export interface ISecret {
|
|||||||
secretCommentIV?: string;
|
secretCommentIV?: string;
|
||||||
secretCommentTag?: string;
|
secretCommentTag?: string;
|
||||||
secretCommentHash?: string;
|
secretCommentHash?: string;
|
||||||
|
skipMultilineEncoding?: boolean;
|
||||||
algorithm: "aes-256-gcm";
|
algorithm: "aes-256-gcm";
|
||||||
keyEncoding: "utf8" | "base64";
|
keyEncoding: "utf8" | "base64";
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
folder?: string;
|
folder?: string;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretSchema = new Schema<ISecret>(
|
const secretSchema = new Schema<ISecret>(
|
||||||
@@ -41,108 +42,112 @@ const secretSchema = new Schema<ISecret>(
|
|||||||
version: {
|
version: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
default: 1,
|
default: 1
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "Workspace",
|
ref: "Workspace",
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [SECRET_SHARED, SECRET_PERSONAL],
|
enum: [SECRET_SHARED, SECRET_PERSONAL],
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
// user associated with the personal secret
|
// user associated with the personal secret
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "User",
|
ref: "User"
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
ref: "Tag",
|
ref: "Tag",
|
||||||
type: [Schema.Types.ObjectId],
|
type: [Schema.Types.ObjectId],
|
||||||
default: [],
|
default: []
|
||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretBlindIndex: {
|
secretBlindIndex: {
|
||||||
type: String,
|
type: String,
|
||||||
select: false,
|
select: false
|
||||||
},
|
},
|
||||||
secretKeyCiphertext: {
|
secretKeyCiphertext: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretKeyIV: {
|
secretKeyIV: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretKeyTag: {
|
secretKeyTag: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretKeyHash: {
|
secretKeyHash: {
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
secretValueCiphertext: {
|
secretValueCiphertext: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretValueIV: {
|
secretValueIV: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretValueTag: {
|
secretValueTag: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
secretValueHash: {
|
secretValueHash: {
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
secretCommentCiphertext: {
|
secretCommentCiphertext: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false
|
||||||
},
|
},
|
||||||
secretCommentIV: {
|
secretCommentIV: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: false,
|
required: false
|
||||||
},
|
},
|
||||||
secretCommentTag: {
|
secretCommentTag: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: false,
|
required: false
|
||||||
},
|
},
|
||||||
secretCommentHash: {
|
secretCommentHash: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false
|
||||||
|
},
|
||||||
|
skipMultilineEncoding: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
},
|
},
|
||||||
algorithm: {
|
algorithm: {
|
||||||
// the encryption algorithm used
|
// the encryption algorithm used
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ALGORITHM_AES_256_GCM],
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
required: true,
|
required: true,
|
||||||
default: ALGORITHM_AES_256_GCM,
|
default: ALGORITHM_AES_256_GCM
|
||||||
},
|
},
|
||||||
keyEncoding: {
|
keyEncoding: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
||||||
required: true,
|
required: true,
|
||||||
default: ENCODING_SCHEME_UTF8,
|
default: ENCODING_SCHEME_UTF8
|
||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "root",
|
default: "root"
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
type: Schema.Types.Mixed
|
type: Schema.Types.Mixed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
secretSchema.index({ tags: 1 }, { background: true });
|
secretSchema.index({ tags: 1 }, { background: true });
|
||||||
|
|
||||||
export const Secret = model<ISecret>("Secret", secretSchema);
|
export const Secret = model<ISecret>("Secret", secretSchema);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:folderId",
|
"/:folderName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
@@ -26,7 +26,7 @@ router.patch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:folderId",
|
"/:folderName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import { requireAuth, requireBlindIndicesEnabled, requireE2EEOff } from "../../middleware";
|
||||||
requireAuth,
|
|
||||||
requireBlindIndicesEnabled,
|
|
||||||
requireE2EEOff
|
|
||||||
} from "../../middleware";
|
|
||||||
import { secretsController } from "../../controllers/v3";
|
import { secretsController } from "../../controllers/v3";
|
||||||
import {
|
import { AuthMode } from "../../variables";
|
||||||
AuthMode
|
|
||||||
} from "../../variables";
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/raw",
|
"/raw",
|
||||||
@@ -85,6 +79,40 @@ router.get(
|
|||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// akhilmhdh: dont put batch router below the individual operation as those have arbitory name as params
|
||||||
|
router.post(
|
||||||
|
"/batch",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
||||||
|
}),
|
||||||
|
requireBlindIndicesEnabled({
|
||||||
|
locationWorkspaceId: "body"
|
||||||
|
}),
|
||||||
|
secretsController.createSecretByNameBatch
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/batch",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
||||||
|
}),
|
||||||
|
requireBlindIndicesEnabled({
|
||||||
|
locationWorkspaceId: "body"
|
||||||
|
}),
|
||||||
|
secretsController.updateSecretByNameBatch
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/batch",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
||||||
|
}),
|
||||||
|
requireBlindIndicesEnabled({
|
||||||
|
locationWorkspaceId: "body"
|
||||||
|
}),
|
||||||
|
secretsController.deleteSecretByNameBatch
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -108,14 +108,6 @@ export const getAllImportedSecrets = async (
|
|||||||
type: "shared"
|
type: "shared"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
$lookup: {
|
|
||||||
from: "tags", // note this is the name of the collection in the database, not the Mongoose model name
|
|
||||||
localField: "tags",
|
|
||||||
foreignField: "_id",
|
|
||||||
as: "tags"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
$group: {
|
$group: {
|
||||||
_id: {
|
_id: {
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
CreateSecretParams,
|
CreateSecretBatchParams,
|
||||||
DeleteSecretParams,
|
CreateSecretParams,
|
||||||
GetSecretParams,
|
DeleteSecretBatchParams,
|
||||||
GetSecretsParams,
|
DeleteSecretParams,
|
||||||
UpdateSecretParams,
|
GetSecretParams,
|
||||||
|
GetSecretsParams,
|
||||||
|
UpdateSecretBatchParams,
|
||||||
|
UpdateSecretParams
|
||||||
} from "../interfaces/services/SecretService";
|
} from "../interfaces/services/SecretService";
|
||||||
import {
|
import {
|
||||||
createSecretBlindIndexDataHelper,
|
createSecretBatchHelper,
|
||||||
createSecretHelper,
|
createSecretBlindIndexDataHelper,
|
||||||
deleteSecretHelper,
|
createSecretHelper,
|
||||||
generateSecretBlindIndexHelper,
|
deleteSecretBatchHelper,
|
||||||
generateSecretBlindIndexWithSaltHelper,
|
deleteSecretHelper,
|
||||||
getSecretBlindIndexSaltHelper,
|
generateSecretBlindIndexHelper,
|
||||||
getSecretHelper,
|
generateSecretBlindIndexWithSaltHelper,
|
||||||
getSecretsHelper,
|
getSecretBlindIndexSaltHelper,
|
||||||
updateSecretHelper,
|
getSecretHelper,
|
||||||
|
getSecretsHelper,
|
||||||
|
updateSecretBatchHelper,
|
||||||
|
updateSecretHelper
|
||||||
} from "../helpers/secrets";
|
} from "../helpers/secrets";
|
||||||
|
|
||||||
class SecretService {
|
class SecretService {
|
||||||
@@ -26,13 +32,9 @@ class SecretService {
|
|||||||
* @param {Buffer} obj.salt - 16-byte random salt
|
* @param {Buffer} obj.salt - 16-byte random salt
|
||||||
* @param {Types.ObjectId} obj.workspaceId
|
* @param {Types.ObjectId} obj.workspaceId
|
||||||
*/
|
*/
|
||||||
static async createSecretBlindIndexData({
|
static async createSecretBlindIndexData({ workspaceId }: { workspaceId: Types.ObjectId }) {
|
||||||
workspaceId,
|
|
||||||
}: {
|
|
||||||
workspaceId: Types.ObjectId;
|
|
||||||
}) {
|
|
||||||
return await createSecretBlindIndexDataHelper({
|
return await createSecretBlindIndexDataHelper({
|
||||||
workspaceId,
|
workspaceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,13 +44,9 @@ class SecretService {
|
|||||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
static async getSecretBlindIndexSalt({
|
static async getSecretBlindIndexSalt({ workspaceId }: { workspaceId: Types.ObjectId }) {
|
||||||
workspaceId,
|
|
||||||
}: {
|
|
||||||
workspaceId: Types.ObjectId;
|
|
||||||
}) {
|
|
||||||
return await getSecretBlindIndexSaltHelper({
|
return await getSecretBlindIndexSaltHelper({
|
||||||
workspaceId,
|
workspaceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,14 +59,14 @@ class SecretService {
|
|||||||
*/
|
*/
|
||||||
static async generateSecretBlindIndexWithSalt({
|
static async generateSecretBlindIndexWithSalt({
|
||||||
secretName,
|
secretName,
|
||||||
salt,
|
salt
|
||||||
}: {
|
}: {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
salt: string;
|
salt: string;
|
||||||
}) {
|
}) {
|
||||||
return await generateSecretBlindIndexWithSaltHelper({
|
return await generateSecretBlindIndexWithSaltHelper({
|
||||||
secretName,
|
secretName,
|
||||||
salt,
|
salt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,14 +79,14 @@ class SecretService {
|
|||||||
*/
|
*/
|
||||||
static async generateSecretBlindIndex({
|
static async generateSecretBlindIndex({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId,
|
workspaceId
|
||||||
}: {
|
}: {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) {
|
}) {
|
||||||
return await generateSecretBlindIndexHelper({
|
return await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId,
|
workspaceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +161,18 @@ class SecretService {
|
|||||||
static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
|
static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
|
||||||
return await deleteSecretHelper(deleteSecretParams);
|
return await deleteSecretHelper(deleteSecretParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async createSecretBatch(createSecretParams: CreateSecretBatchParams) {
|
||||||
|
return await createSecretBatchHelper(createSecretParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateSecretBatch(updateSecretParams: UpdateSecretBatchParams) {
|
||||||
|
return await updateSecretBatchHelper(updateSecretParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteSecretBatch(deleteSecretParams: DeleteSecretBatchParams) {
|
||||||
|
return await deleteSecretBatchHelper(deleteSecretParams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SecretService;
|
export default SecretService;
|
||||||
|
|||||||
@@ -5,28 +5,30 @@ export const CreateFolderV1 = z.object({
|
|||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
folderName: z.string().trim(),
|
folderName: z.string().trim(),
|
||||||
parentFolderId: z.string().trim().optional()
|
directory: z.string().trim().default("/")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateFolderV1 = z.object({
|
export const UpdateFolderV1 = z.object({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
folderId: z.string().trim()
|
folderName: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
name: z.string().trim()
|
name: z.string().trim(),
|
||||||
|
directory: z.string().trim().default("/")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DeleteFolderV1 = z.object({
|
export const DeleteFolderV1 = z.object({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
folderId: z.string().trim()
|
folderName: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim()
|
environment: z.string().trim(),
|
||||||
|
directory: z.string().trim().default("/")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,7 +36,6 @@ export const GetFoldersV1 = z.object({
|
|||||||
query: z.object({
|
query: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
parentFolderId: z.string().trim().optional(),
|
directory: z.string().trim().default("/")
|
||||||
parentFolderPath: z.string().trim().optional()
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const CreateSecretImportV1 = z.object({
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
folderId: z.string().trim().default("root"),
|
directory: z.string().trim().default("/"),
|
||||||
secretImport: z.object({
|
secretImport: z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretPath: z.string().trim()
|
secretPath: z.string().trim()
|
||||||
@@ -40,7 +40,7 @@ export const GetSecretImportsV1 = z.object({
|
|||||||
query: z.object({
|
query: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
folderId: z.string().trim().default("root")
|
directory: z.string().trim().default("/")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,6 +48,6 @@ export const GetAllSecretsFromImportV1 = z.object({
|
|||||||
query: z.object({
|
query: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
folderId: z.string().trim().default("root")
|
directory: z.string().trim().default("/")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ export const CreateSecretRawV3 = z.object({
|
|||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
secretValue: z.string().trim(),
|
secretValue: z.string().trim(),
|
||||||
secretComment: z.string().trim(),
|
secretComment: z.string().trim(),
|
||||||
|
skipMultilineEncoding: z.boolean().optional(),
|
||||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -275,6 +276,7 @@ export const UpdateSecretByNameRawV3 = z.object({
|
|||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretValue: z.string().trim(),
|
secretValue: z.string().trim(),
|
||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
|
skipMultilineEncoding: z.boolean().optional(),
|
||||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -335,7 +337,8 @@ export const CreateSecretV3 = z.object({
|
|||||||
secretCommentCiphertext: z.string().trim().optional(),
|
secretCommentCiphertext: z.string().trim().optional(),
|
||||||
secretCommentIV: z.string().trim().optional(),
|
secretCommentIV: z.string().trim().optional(),
|
||||||
secretCommentTag: z.string().trim().optional(),
|
secretCommentTag: z.string().trim().optional(),
|
||||||
metadata: z.record(z.string()).optional()
|
metadata: z.record(z.string()).optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().optional()
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretName: z.string().trim()
|
secretName: z.string().trim()
|
||||||
@@ -350,7 +353,17 @@ export const UpdateSecretByNameV3 = z.object({
|
|||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
secretValueCiphertext: z.string().trim(),
|
secretValueCiphertext: z.string().trim(),
|
||||||
secretValueIV: z.string().trim(),
|
secretValueIV: z.string().trim(),
|
||||||
secretValueTag: z.string().trim()
|
secretValueTag: z.string().trim(),
|
||||||
|
secretCommentCiphertext: z.string().trim().optional(),
|
||||||
|
secretCommentIV: z.string().trim().optional(),
|
||||||
|
secretCommentTag: z.string().trim().optional(),
|
||||||
|
tags: z.string().array().optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().optional(),
|
||||||
|
// to update secret name
|
||||||
|
secretName: z.string().trim().optional(),
|
||||||
|
secretKeyIV: z.string().trim().optional(),
|
||||||
|
secretKeyTag: z.string().trim().optional(),
|
||||||
|
secretKeyCiphertext: z.string().trim().optional()
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretName: z.string()
|
secretName: z.string()
|
||||||
@@ -368,3 +381,67 @@ export const DeleteSecretByNameV3 = z.object({
|
|||||||
secretName: z.string()
|
secretName: z.string()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const CreateSecretByNameBatchV3 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
secretPath: z.string().trim().default("/"),
|
||||||
|
secrets: z
|
||||||
|
.object({
|
||||||
|
secretName: z.string().trim(),
|
||||||
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
|
||||||
|
secretKeyCiphertext: z.string().trim(),
|
||||||
|
secretKeyIV: z.string().trim(),
|
||||||
|
secretKeyTag: z.string().trim(),
|
||||||
|
secretValueCiphertext: z.string().trim(),
|
||||||
|
secretValueIV: z.string().trim(),
|
||||||
|
secretValueTag: z.string().trim(),
|
||||||
|
secretCommentCiphertext: z.string().trim().optional(),
|
||||||
|
secretCommentIV: z.string().trim().optional(),
|
||||||
|
secretCommentTag: z.string().trim().optional(),
|
||||||
|
metadata: z.record(z.string()).optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateSecretByNameBatchV3 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
secretPath: z.string().trim().default("/"),
|
||||||
|
secrets: z
|
||||||
|
.object({
|
||||||
|
secretName: z.string().trim(),
|
||||||
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
|
||||||
|
secretValueCiphertext: z.string().trim(),
|
||||||
|
secretValueIV: z.string().trim(),
|
||||||
|
secretValueTag: z.string().trim(),
|
||||||
|
secretCommentCiphertext: z.string().trim().optional(),
|
||||||
|
secretCommentIV: z.string().trim().optional(),
|
||||||
|
secretCommentTag: z.string().trim().optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().optional(),
|
||||||
|
tags: z.string().array().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteSecretByNameBatchV3 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
secretPath: z.string().trim().default("/"),
|
||||||
|
secrets: z
|
||||||
|
.object({
|
||||||
|
secretName: z.string().trim(),
|
||||||
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ export const validateClientForWorkspace = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const workspace = await Workspace.findById(workspaceId);
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
|
|
||||||
if (!workspace) throw WorkspaceNotFoundError({
|
if (!workspace)
|
||||||
message: "Failed to find workspace"
|
throw WorkspaceNotFoundError({
|
||||||
});
|
message: "Failed to find workspace"
|
||||||
|
});
|
||||||
|
|
||||||
let membership;
|
let membership;
|
||||||
switch (authData.actor.type) {
|
switch (authData.actor.type) {
|
||||||
@@ -67,7 +68,7 @@ export const GetWorkspaceSecretSnapshotsV1 = z.object({
|
|||||||
}),
|
}),
|
||||||
query: z.object({
|
query: z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
folderId: z.string().trim().default("root"),
|
directory: z.string().trim().default("/"),
|
||||||
offset: z.coerce.number(),
|
offset: z.coerce.number(),
|
||||||
limit: z.coerce.number()
|
limit: z.coerce.number()
|
||||||
})
|
})
|
||||||
@@ -79,7 +80,7 @@ export const GetWorkspaceSecretSnapshotsCountV1 = z.object({
|
|||||||
}),
|
}),
|
||||||
query: z.object({
|
query: z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
folderId: z.string().trim().default("root")
|
directory: z.string().trim().default("/")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ export const RollbackWorkspaceSecretSnapshotV1 = z.object({
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
folderId: z.string().trim().default("root"),
|
directory: z.string().trim().default("/"),
|
||||||
version: z.number()
|
version: z.number()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// secrets
|
// secrets
|
||||||
export const SECRET_SHARED = "shared";
|
export const SECRET_SHARED = "shared";
|
||||||
export const SECRET_PERSONAL = "personal";
|
export const SECRET_PERSONAL = "personal";
|
||||||
|
|||||||
Reference in New Issue
Block a user