mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
4 Commits
daniel/upd
...
default-no
Author | SHA1 | Date | |
---|---|---|---|
caa0dc6e3f | |||
4d28a66572 | |||
7346b2ff34 | |||
ab361b1315 |
@ -3,130 +3,24 @@ import { Types } from "mongoose";
|
|||||||
import { EventService, SecretService } from "../../services";
|
import { EventService, SecretService } from "../../services";
|
||||||
import { eventPushSecrets } from "../../events";
|
import { eventPushSecrets } from "../../events";
|
||||||
import { BotService } from "../../services";
|
import { BotService } from "../../services";
|
||||||
import { containsGlobPatterns, isValidScopeV3, repackageSecretToRaw } from "../../helpers/secrets";
|
import {
|
||||||
|
checkSecretsPermission,
|
||||||
|
containsGlobPatterns,
|
||||||
|
repackageSecretV3ToRaw
|
||||||
|
} from "../../helpers/secrets";
|
||||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||||
import { Folder, IMembership, IServiceTokenData, IServiceTokenDataV3 } from "../../models";
|
import { Folder, IServiceTokenData } from "../../models";
|
||||||
import { Permission } from "../../models/serviceTokenDataV3";
|
|
||||||
import { getFolderByPath } from "../../services/FolderService";
|
import { getFolderByPath } from "../../services/FolderService";
|
||||||
import { BadRequestError } from "../../utils/errors";
|
import { BadRequestError } from "../../utils/errors";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import * as reqValidator from "../../validation/secrets";
|
import * as reqValidator from "../../validation/secrets";
|
||||||
import {
|
import { ProjectPermissionActions } from "../../ee/services/ProjectRoleService";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSub,
|
|
||||||
getUserProjectPermissions
|
|
||||||
} from "../../ee/services/ProjectRoleService";
|
|
||||||
import { ForbiddenError, subject } from "@casl/ability";
|
|
||||||
import {
|
|
||||||
validateServiceTokenDataClientForWorkspace,
|
|
||||||
validateServiceTokenDataV3ClientForWorkspace
|
|
||||||
} from "../../validation";
|
|
||||||
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
|
|
||||||
import { ActorType } from "../../ee/models";
|
|
||||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
|
||||||
import { AuthData } from "../../interfaces/middleware";
|
|
||||||
import {
|
import {
|
||||||
generateSecretApprovalRequest,
|
generateSecretApprovalRequest,
|
||||||
getSecretPolicyOfBoard
|
getSecretPolicyOfBoard
|
||||||
} from "../../ee/services/SecretApprovalService";
|
} from "../../ee/services/SecretApprovalService";
|
||||||
import { CommitType } from "../../ee/models/secretApprovalRequest";
|
import { CommitType } from "../../ee/models/secretApprovalRequest";
|
||||||
import { IRole } from "../../ee/models/role";
|
|
||||||
|
|
||||||
const checkSecretsPermission = async ({
|
|
||||||
authData,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secretAction
|
|
||||||
}: {
|
|
||||||
authData: AuthData;
|
|
||||||
workspaceId: string;
|
|
||||||
environment: string;
|
|
||||||
secretPath: string;
|
|
||||||
secretAction: ProjectPermissionActions; // CRUD
|
|
||||||
}): Promise<{
|
|
||||||
authVerifier: (env: string, secPath: string) => boolean;
|
|
||||||
membership?: Omit<IMembership, "customRole"> & { customRole: IRole };
|
|
||||||
}> => {
|
|
||||||
let STV2RequiredPermissions = [];
|
|
||||||
let STV3RequiredPermissions: Permission[] = [];
|
|
||||||
|
|
||||||
switch (secretAction) {
|
|
||||||
case ProjectPermissionActions.Create:
|
|
||||||
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
|
||||||
STV3RequiredPermissions = [Permission.WRITE];
|
|
||||||
break;
|
|
||||||
case ProjectPermissionActions.Read:
|
|
||||||
STV2RequiredPermissions = [PERMISSION_READ_SECRETS];
|
|
||||||
STV3RequiredPermissions = [Permission.READ];
|
|
||||||
break;
|
|
||||||
case ProjectPermissionActions.Edit:
|
|
||||||
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
|
||||||
STV3RequiredPermissions = [Permission.WRITE];
|
|
||||||
break;
|
|
||||||
case ProjectPermissionActions.Delete:
|
|
||||||
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
|
||||||
STV3RequiredPermissions = [Permission.WRITE];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (authData.actor.type) {
|
|
||||||
case ActorType.USER: {
|
|
||||||
const { permission, membership } = await getUserProjectPermissions(
|
|
||||||
authData.actor.metadata.userId,
|
|
||||||
workspaceId
|
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
secretAction,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
authVerifier: (env: string, secPath: string) =>
|
|
||||||
permission.can(
|
|
||||||
secretAction,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: env,
|
|
||||||
secretPath: secPath
|
|
||||||
})
|
|
||||||
),
|
|
||||||
membership
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: STV2RequiredPermissions
|
|
||||||
});
|
|
||||||
return { authVerifier: () => true };
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData,
|
|
||||||
serviceTokenData: authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: STV3RequiredPermissions
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
authVerifier: (env: string, secPath: string) =>
|
|
||||||
isValidScopeV3({
|
|
||||||
authPayload: authData.authPayload as IServiceTokenDataV3,
|
|
||||||
environment: env,
|
|
||||||
secretPath: secPath,
|
|
||||||
requiredPermissions: STV3RequiredPermissions
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw UnauthorizedRequestError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return secrets for workspace with id [workspaceId] and environment
|
* Return secrets for workspace with id [workspaceId] and environment
|
||||||
@ -253,21 +147,21 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
|||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: secrets.map((secret) =>
|
secrets: secrets.map((secret) =>
|
||||||
repackageSecretToRaw({
|
repackageSecretV3ToRaw({
|
||||||
secret,
|
secret,
|
||||||
key
|
key
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
imports: importedSecrets.map((el) => ({
|
imports: importedSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
secrets: el.secrets.map((secret) => repackageSecretToRaw({ secret, key }))
|
secrets: el.secrets.map((secret) => repackageSecretV3ToRaw({ secret, key }))
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: secrets.map((secret) => {
|
secrets: secrets.map((secret) => {
|
||||||
const rep = repackageSecretToRaw({
|
const rep = repackageSecretV3ToRaw({
|
||||||
secret,
|
secret,
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
@ -376,7 +270,7 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretV3ToRaw({
|
||||||
secret,
|
secret,
|
||||||
key
|
key
|
||||||
})
|
})
|
||||||
@ -522,19 +416,11 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
skipMultilineEncoding
|
skipMultilineEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretWithoutBlindIndex = secret.toObject();
|
const secretWithoutBlindIndex = secret.toObject();
|
||||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretV3ToRaw({
|
||||||
secret: secretWithoutBlindIndex,
|
secret: secretWithoutBlindIndex,
|
||||||
key
|
key
|
||||||
})
|
})
|
||||||
@ -651,16 +537,8 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
skipMultilineEncoding
|
skipMultilineEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretV3ToRaw({
|
||||||
secret,
|
secret,
|
||||||
key
|
key
|
||||||
})
|
})
|
||||||
@ -773,7 +651,7 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretV3ToRaw({
|
||||||
secret,
|
secret,
|
||||||
key
|
key
|
||||||
})
|
})
|
||||||
@ -960,14 +838,6 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
skipMultilineEncoding
|
skipMultilineEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretWithoutBlindIndex = secret.toObject();
|
const secretWithoutBlindIndex = secret.toObject();
|
||||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||||
|
|
||||||
@ -1073,14 +943,6 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
secretKeyIV
|
secretKeyIV
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret
|
secret
|
||||||
});
|
});
|
||||||
@ -1137,14 +999,6 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
|||||||
secretPath
|
secretPath
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret
|
secret
|
||||||
});
|
});
|
||||||
@ -1189,14 +1043,6 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData
|
authData: req.authData
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: createdSecrets
|
secrets: createdSecrets
|
||||||
});
|
});
|
||||||
@ -1241,14 +1087,6 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData
|
authData: req.authData
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: updatedSecrets
|
secrets: updatedSecrets
|
||||||
});
|
});
|
||||||
@ -1293,14 +1131,6 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData
|
authData: req.authData
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: deletedSecrets
|
secrets: deletedSecrets
|
||||||
});
|
});
|
||||||
|
42
backend/src/controllers/v4/environmentsController.ts
Normal file
42
backend/src/controllers/v4/environmentsController.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
// import { validateRequest } from "../../helpers/validation";
|
||||||
|
// import * as reqValidator from "../../validation/environments";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create environment with name [environmentName] and slug [environmentSlug]
|
||||||
|
* in project with id [projectId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const createEnvironment = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of environments in project with id [projectId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getEnvironments = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete environment with name [environmentName] and slug [environmentSlug]
|
||||||
|
* in project with id [projectId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const updateEnvironment = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete environment with name [environmentName] and slug [environmentSlug]
|
||||||
|
* in project with id [projectId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const deleteEnvironment = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
87
backend/src/controllers/v4/foldersController.ts
Normal file
87
backend/src/controllers/v4/foldersController.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
// import { Types } from "mongoose";
|
||||||
|
// import { FolderVersion } from "../../ee/models";
|
||||||
|
// import { Folder } from "../../models";
|
||||||
|
// import { appendFolder } from "../../services/FolderService";
|
||||||
|
// import { validateRequest } from "../../helpers/validation";
|
||||||
|
// import * as reqValidator from "../../validation/folders";
|
||||||
|
// import { ResourceNotFoundError } from "../../utils/errors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Return list of folders in project with id [projectId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getFolders = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Create folder with name [folderName] in project with id [projectId]
|
||||||
|
* under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createFolder = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// body: {
|
||||||
|
// folderName,
|
||||||
|
// projectId,
|
||||||
|
// environmentSlug,
|
||||||
|
// path
|
||||||
|
// }
|
||||||
|
// } = await validateRequest(reqValidator.CreateFolderV2, req);
|
||||||
|
|
||||||
|
// // get folder
|
||||||
|
|
||||||
|
// const folders = await Folder.findOne({
|
||||||
|
// workspace: new Types.ObjectId(projectId),
|
||||||
|
// environment: environmentSlug
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (!folders) throw ResourceNotFoundError();
|
||||||
|
|
||||||
|
// const { parent, child: folder, hasCreated } = appendFolder(folders.nodes, { folderName, directory: path });
|
||||||
|
|
||||||
|
// if (!hasCreated) return res.json({ folder });
|
||||||
|
|
||||||
|
// await Folder.findByIdAndUpdate(folders._id, folders);
|
||||||
|
|
||||||
|
// await new FolderVersion({
|
||||||
|
// project: new Types.ObjectId(projectId),
|
||||||
|
// nodes: parent
|
||||||
|
// }).save();
|
||||||
|
|
||||||
|
// return res.status(200).send({
|
||||||
|
// folder
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Update folder with name [folderName] in project with id [projectId]
|
||||||
|
* under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateFolder = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete folder with name [folderName] in project with id [projectId]
|
||||||
|
* under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteFolder = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
9
backend/src/controllers/v4/index.ts
Normal file
9
backend/src/controllers/v4/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import * as secretsController from "./secretsController";
|
||||||
|
import * as environmentsController from "./environmentsController";
|
||||||
|
import * as foldersController from "./foldersController";
|
||||||
|
|
||||||
|
export {
|
||||||
|
secretsController,
|
||||||
|
environmentsController,
|
||||||
|
foldersController
|
||||||
|
}
|
353
backend/src/controllers/v4/secretsController.ts
Normal file
353
backend/src/controllers/v4/secretsController.ts
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { BotService, SecretService } from "../../services";
|
||||||
|
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||||
|
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||||
|
import { ProjectPermissionActions } from "../../ee/services/ProjectRoleService";
|
||||||
|
import { checkSecretsPermission, packageSecretV4 } from "../../helpers";
|
||||||
|
import { validateRequest } from "../../helpers/validation";
|
||||||
|
import * as reqValidator from "../../validation/secrets";
|
||||||
|
import { getFolderIdFromServiceToken } from "../../services/FolderService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get secrets in project with id
|
||||||
|
* [projectId]under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getSecrets = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
query: {
|
||||||
|
projectId,
|
||||||
|
environmentSlug,
|
||||||
|
path,
|
||||||
|
includeImports
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.GetSecretsV4, req);
|
||||||
|
|
||||||
|
await checkSecretsPermission({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: projectId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
secretAction: ProjectPermissionActions.Read
|
||||||
|
});
|
||||||
|
|
||||||
|
const secrets = await SecretService.getSecrets({
|
||||||
|
workspaceId: new Types.ObjectId(projectId),
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
authData: req.authData
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
|
workspaceId: new Types.ObjectId(projectId)
|
||||||
|
});
|
||||||
|
|
||||||
|
let packagedSecrets = secrets.map((secret) => packageSecretV4({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
}));
|
||||||
|
|
||||||
|
const folderId = await getFolderIdFromServiceToken(projectId, environmentSlug, path);
|
||||||
|
|
||||||
|
if (includeImports) {
|
||||||
|
const importGroups = await getAllImportedSecrets(
|
||||||
|
projectId,
|
||||||
|
environmentSlug,
|
||||||
|
folderId,
|
||||||
|
() => true
|
||||||
|
);
|
||||||
|
|
||||||
|
importGroups.forEach((importGroup) => {
|
||||||
|
packagedSecrets = packagedSecrets.concat(
|
||||||
|
importGroup.secrets.map((secret) => packageSecretV4({ secret, key }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets: packagedSecrets
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get secret named [secretName] in project with id
|
||||||
|
* [projectId]under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getSecret = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: {
|
||||||
|
secretName
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
projectId,
|
||||||
|
environmentSlug,
|
||||||
|
path,
|
||||||
|
type,
|
||||||
|
includeImports
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.GetSecretV4, req);
|
||||||
|
|
||||||
|
await checkSecretsPermission({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: projectId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
secretAction: ProjectPermissionActions.Read
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await SecretService.getSecret({
|
||||||
|
secretName,
|
||||||
|
workspaceId: new Types.ObjectId(projectId),
|
||||||
|
environment: environmentSlug,
|
||||||
|
type,
|
||||||
|
secretPath: path,
|
||||||
|
authData: req.authData,
|
||||||
|
include_imports: includeImports
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
|
workspaceId: new Types.ObjectId(projectId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const packagedSecret = packageSecretV4({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secret: packagedSecret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create secret named [secretName] in project with id
|
||||||
|
* [projectId]under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createSecret = async (req: Request, res: Response) =>{
|
||||||
|
const {
|
||||||
|
params: {
|
||||||
|
secretName
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
projectId,
|
||||||
|
environmentSlug,
|
||||||
|
path,
|
||||||
|
type,
|
||||||
|
secretValue,
|
||||||
|
secretComment,
|
||||||
|
skipMultilineEncoding
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.CreateSecretV4, req);
|
||||||
|
|
||||||
|
await checkSecretsPermission({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: projectId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
secretAction: ProjectPermissionActions.Create
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
|
workspaceId: new Types.ObjectId(projectId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: secretKeyCiphertext,
|
||||||
|
iv: secretKeyIV,
|
||||||
|
tag: secretKeyTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: secretName,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: secretValueCiphertext,
|
||||||
|
iv: secretValueIV,
|
||||||
|
tag: secretValueTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: secretValue,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: secretCommentCiphertext,
|
||||||
|
iv: secretCommentIV,
|
||||||
|
tag: secretCommentTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: secretComment,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await SecretService.createSecret({
|
||||||
|
secretName,
|
||||||
|
workspaceId: new Types.ObjectId(projectId),
|
||||||
|
environment: environmentSlug,
|
||||||
|
type,
|
||||||
|
authData: req.authData,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretPath: path,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
skipMultilineEncoding
|
||||||
|
});
|
||||||
|
|
||||||
|
const packagedSecret = packageSecretV4({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secret: packagedSecret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create secret named [secretName] in project with id
|
||||||
|
* [projectId]under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateSecret = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: {
|
||||||
|
secretName
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
projectId,
|
||||||
|
environmentSlug,
|
||||||
|
path,
|
||||||
|
type,
|
||||||
|
secretValue,
|
||||||
|
secretComment,
|
||||||
|
skipMultilineEncoding
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.UpdateSecretV4, req);
|
||||||
|
|
||||||
|
await checkSecretsPermission({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: projectId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
secretAction: ProjectPermissionActions.Edit
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
|
workspaceId: new Types.ObjectId(projectId)
|
||||||
|
});
|
||||||
|
|
||||||
|
let secretValueCiphertext, secretValueIV, secretValueTag;
|
||||||
|
if (secretValue) {
|
||||||
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: secretValue,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
secretValueCiphertext = ciphertext;
|
||||||
|
secretValueIV = iv;
|
||||||
|
secretValueTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
let secretCommentCiphertext, secretCommentIV, secretCommentTag;
|
||||||
|
if (secretComment) {
|
||||||
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: secretComment,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
secretCommentCiphertext = ciphertext;
|
||||||
|
secretCommentIV = iv;
|
||||||
|
secretCommentTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = await SecretService.updateSecret({
|
||||||
|
secretName,
|
||||||
|
workspaceId: new Types.ObjectId(projectId),
|
||||||
|
environment: environmentSlug,
|
||||||
|
type,
|
||||||
|
authData: req.authData,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretPath: path,
|
||||||
|
skipMultilineEncoding
|
||||||
|
});
|
||||||
|
|
||||||
|
const packagedSecret = packageSecretV4({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secret: packagedSecret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete secret named [secretName] in project with id
|
||||||
|
* [projectId]under path [path]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteSecret = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: {
|
||||||
|
secretName
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
projectId,
|
||||||
|
environmentSlug,
|
||||||
|
path,
|
||||||
|
type
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.DeleteSecretV4, req);
|
||||||
|
|
||||||
|
await checkSecretsPermission({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: projectId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
secretAction: ProjectPermissionActions.Delete
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
|
workspaceId: new Types.ObjectId(projectId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { secret } = await SecretService.deleteSecret({
|
||||||
|
secretName,
|
||||||
|
workspaceId: new Types.ObjectId(projectId),
|
||||||
|
environment: environmentSlug,
|
||||||
|
type,
|
||||||
|
authData: req.authData,
|
||||||
|
secretPath: path
|
||||||
|
});
|
||||||
|
|
||||||
|
const packagedSecret = packageSecretV4({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secret: packagedSecret
|
||||||
|
});
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { EVENT_PULL_SECRETS, EVENT_PUSH_SECRETS } from "../variables";
|
import { EVENT_PUSH_SECRETS } from "../variables";
|
||||||
|
|
||||||
interface PushSecret {
|
interface PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
@ -37,18 +37,4 @@ const eventPushSecrets = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return event for pulling secrets
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.workspaceId - id of workspace to pull secrets from
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const eventPullSecrets = ({ workspaceId }: { workspaceId: string }) => {
|
|
||||||
return {
|
|
||||||
name: EVENT_PULL_SECRETS,
|
|
||||||
workspaceId,
|
|
||||||
payload: {}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export { eventPushSecrets };
|
export { eventPushSecrets };
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from "../interfaces/services/SecretService";
|
} from "../interfaces/services/SecretService";
|
||||||
import {
|
import {
|
||||||
Folder,
|
Folder,
|
||||||
|
IMembership,
|
||||||
ISecret,
|
ISecret,
|
||||||
IServiceTokenData,
|
IServiceTokenData,
|
||||||
IServiceTokenDataV3,
|
IServiceTokenDataV3,
|
||||||
@ -20,7 +21,7 @@ import {
|
|||||||
TFolderRootSchema
|
TFolderRootSchema
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import { Permission } from "../models/serviceTokenDataV3";
|
import { Permission } from "../models/serviceTokenDataV3";
|
||||||
import { EventType, SecretVersion } from "../ee/models";
|
import { ActorType, EventType, IRole, SecretVersion } from "../ee/models";
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
@ -33,6 +34,8 @@ import {
|
|||||||
ENCODING_SCHEME_BASE64,
|
ENCODING_SCHEME_BASE64,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
K8_USER_AGENT_NAME,
|
K8_USER_AGENT_NAME,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED
|
SECRET_SHARED
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
@ -42,7 +45,8 @@ import {
|
|||||||
decryptSymmetric128BitHexKeyUTF8,
|
decryptSymmetric128BitHexKeyUTF8,
|
||||||
encryptSymmetric128BitHexKeyUTF8
|
encryptSymmetric128BitHexKeyUTF8
|
||||||
} from "../utils/crypto";
|
} from "../utils/crypto";
|
||||||
import { TelemetryService } from "../services";
|
import { EventService, TelemetryService } from "../services";
|
||||||
|
import { eventPushSecrets } from "../events";
|
||||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { EEAuditLogService, EESecretService } from "../ee/services";
|
import { EEAuditLogService, EESecretService } from "../ee/services";
|
||||||
import { getAuthDataPayloadUserObj } from "../utils/authn/helpers";
|
import { getAuthDataPayloadUserObj } from "../utils/authn/helpers";
|
||||||
@ -50,6 +54,112 @@ import { getFolderByPath, getFolderIdFromServiceToken } from "../services/Folder
|
|||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { getAnImportedSecret } from "../services/SecretImportService";
|
import { getAnImportedSecret } from "../services/SecretImportService";
|
||||||
|
import { AuthData } from "../interfaces/middleware";
|
||||||
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
getUserProjectPermissions
|
||||||
|
} from "../ee/services/ProjectRoleService";
|
||||||
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
import {
|
||||||
|
validateServiceTokenDataClientForWorkspace,
|
||||||
|
validateServiceTokenDataV3ClientForWorkspace
|
||||||
|
} from "../validation";
|
||||||
|
|
||||||
|
export const checkSecretsPermission = async ({
|
||||||
|
authData,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretAction
|
||||||
|
}: {
|
||||||
|
authData: AuthData;
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
secretAction: ProjectPermissionActions; // CRUD
|
||||||
|
}): Promise<{
|
||||||
|
authVerifier: (env: string, secPath: string) => boolean;
|
||||||
|
membership?: Omit<IMembership, "customRole"> & { customRole: IRole };
|
||||||
|
}> => {
|
||||||
|
let STV2RequiredPermissions = [];
|
||||||
|
let STV3RequiredPermissions: Permission[] = [];
|
||||||
|
|
||||||
|
switch (secretAction) {
|
||||||
|
case ProjectPermissionActions.Create:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.WRITE];
|
||||||
|
break;
|
||||||
|
case ProjectPermissionActions.Read:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_READ_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.READ];
|
||||||
|
break;
|
||||||
|
case ProjectPermissionActions.Edit:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.WRITE];
|
||||||
|
break;
|
||||||
|
case ProjectPermissionActions.Delete:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.WRITE];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (authData.actor.type) {
|
||||||
|
case ActorType.USER: {
|
||||||
|
const { permission, membership } = await getUserProjectPermissions(
|
||||||
|
authData.actor.metadata.userId,
|
||||||
|
workspaceId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
secretAction,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
authVerifier: (env: string, secPath: string) =>
|
||||||
|
permission.can(
|
||||||
|
secretAction,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: env,
|
||||||
|
secretPath: secPath
|
||||||
|
})
|
||||||
|
),
|
||||||
|
membership
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE: {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload as IServiceTokenData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: STV2RequiredPermissions
|
||||||
|
});
|
||||||
|
return { authVerifier: () => true };
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData,
|
||||||
|
serviceTokenData: authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: STV3RequiredPermissions
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
authVerifier: (env: string, secPath: string) =>
|
||||||
|
isValidScopeV3({
|
||||||
|
authPayload: authData.authPayload as IServiceTokenDataV3,
|
||||||
|
environment: env,
|
||||||
|
secretPath: secPath,
|
||||||
|
requiredPermissions: STV3RequiredPermissions
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate scope for service token v3
|
* Validate scope for service token v3
|
||||||
@ -119,13 +229,12 @@ 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.
|
||||||
*
|
|
||||||
* Precondition: the workspace for secret [secret] must have E2EE disabled
|
* Precondition: the workspace for secret [secret] must have E2EE disabled
|
||||||
* @param {ISecret} secret - secret to repackage to raw
|
* @param {ISecret} secret - secret to repackage to raw
|
||||||
* @param {String} key - symmetric key to use to decrypt secret
|
* @param {String} key - symmetric key to use to decrypt secret
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const repackageSecretToRaw = ({ secret, key }: { secret: ISecret; key: string }) => {
|
export const repackageSecretV3ToRaw = ({ secret, key }: { secret: ISecret; key: string }) => {
|
||||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: secret.secretKeyIV,
|
iv: secret.secretKeyIV,
|
||||||
@ -160,7 +269,8 @@ export const repackageSecretToRaw = ({ secret, key }: { secret: ISecret; key: st
|
|||||||
user: secret.user,
|
user: secret.user,
|
||||||
secretKey,
|
secretKey,
|
||||||
secretValue,
|
secretValue,
|
||||||
secretComment
|
secretComment,
|
||||||
|
skipMultilineEncoding: secret.skipMultilineEncoding
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -517,6 +627,14 @@ export const createSecretHelper = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return secret;
|
return secret;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -755,7 +873,7 @@ export const updateSecretHelper = async ({
|
|||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretPath,
|
secretPath,
|
||||||
tags,
|
tags, // maybe this can accept just a secretComment?
|
||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
@ -850,6 +968,9 @@ export const updateSecretHelper = async ({
|
|||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
tags,
|
tags,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
secretBlindIndex: newSecretNameBlindIndex,
|
secretBlindIndex: newSecretNameBlindIndex,
|
||||||
@ -877,9 +998,12 @@ export const updateSecretHelper = async ({
|
|||||||
secretKeyCiphertext: secret.secretKeyCiphertext,
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
secretKeyIV: secret.secretKeyIV,
|
secretKeyIV: secret.secretKeyIV,
|
||||||
secretKeyTag: secret.secretKeyTag,
|
secretKeyTag: secret.secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext: secret.secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV: secret.secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag: secret.secretValueTag,
|
||||||
|
secretCommentCiphertext: secret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: secret.secretCommentIV,
|
||||||
|
secretCommentTag: secret.secretCommentTag,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
@ -933,6 +1057,14 @@ export const updateSecretHelper = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return secret;
|
return secret;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1060,6 +1192,14 @@ export const deleteSecretHelper = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secrets,
|
secrets,
|
||||||
secret
|
secret
|
||||||
@ -1407,6 +1547,14 @@ export const createSecretBatchHelper = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return newlyCreatedSecrets;
|
return newlyCreatedSecrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1602,6 +1750,14 @@ export const updateSecretBatchHelper = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1725,7 +1881,54 @@ export const deleteSecretBatchHelper = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secrets: deletedSecrets
|
secrets: deletedSecrets
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- v4/secrets helpers
|
||||||
|
export const packageSecretV4 = ({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
}: {
|
||||||
|
secret: ISecret;
|
||||||
|
key: string;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
_id,
|
||||||
|
version,
|
||||||
|
workspace,
|
||||||
|
type,
|
||||||
|
environment,
|
||||||
|
user,
|
||||||
|
secretKey,
|
||||||
|
secretValue,
|
||||||
|
secretComment,
|
||||||
|
skipMultilineEncoding
|
||||||
|
} = repackageSecretV3ToRaw({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({
|
||||||
|
_id,
|
||||||
|
version,
|
||||||
|
projectId: workspace,
|
||||||
|
environmentSlug: environment,
|
||||||
|
type,
|
||||||
|
user,
|
||||||
|
secretName: secretKey,
|
||||||
|
secretValue,
|
||||||
|
secretComment,
|
||||||
|
skipMultilineEncoding
|
||||||
|
});
|
||||||
|
}
|
@ -77,6 +77,9 @@ import {
|
|||||||
users as v3UsersRouter,
|
users as v3UsersRouter,
|
||||||
workspaces as v3WorkspacesRouter
|
workspaces as v3WorkspacesRouter
|
||||||
} from "./routes/v3";
|
} from "./routes/v3";
|
||||||
|
import {
|
||||||
|
secrets as v4SecretsRouter
|
||||||
|
} from "./routes/v4";
|
||||||
import { healthCheck } from "./routes/status";
|
import { healthCheck } from "./routes/status";
|
||||||
// import { getLogger } from "./utils/logger";
|
// import { getLogger } from "./utils/logger";
|
||||||
import { RouteNotFoundError } from "./utils/errors";
|
import { RouteNotFoundError } from "./utils/errors";
|
||||||
@ -252,6 +255,9 @@ const main = async () => {
|
|||||||
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
||||||
app.use("/api/v3/signup", v3SignupRouter);
|
app.use("/api/v3/signup", v3SignupRouter);
|
||||||
app.use("/api/v3/us", v3UsersRouter);
|
app.use("/api/v3/us", v3UsersRouter);
|
||||||
|
|
||||||
|
// v4 routes (user-facing)
|
||||||
|
app.use("/api/v4/secrets", v4SecretsRouter);
|
||||||
|
|
||||||
// api docs
|
// api docs
|
||||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
||||||
|
@ -51,9 +51,9 @@ export interface UpdateSecretParams {
|
|||||||
environment: string;
|
environment: string;
|
||||||
type: "shared" | "personal";
|
type: "shared" | "personal";
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
secretValueCiphertext: string;
|
secretValueCiphertext?: string;
|
||||||
secretValueIV: string;
|
secretValueIV?: string;
|
||||||
secretValueTag: string;
|
secretValueTag?: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretCommentCiphertext?: string;
|
secretCommentCiphertext?: string;
|
||||||
secretCommentIV?: string;
|
secretCommentIV?: string;
|
||||||
|
@ -120,6 +120,7 @@ const secretSchema = new Schema<ISecret>(
|
|||||||
},
|
},
|
||||||
skipMultilineEncoding: {
|
skipMultilineEncoding: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
algorithm: {
|
algorithm: {
|
||||||
|
39
backend/src/routes/v4/environments.ts
Normal file
39
backend/src/routes/v4/environments.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth } from "../../middleware";
|
||||||
|
import { environmentsController } from "../../controllers/v4";
|
||||||
|
// import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
environmentsController.createEnvironment
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
environmentsController.getEnvironments
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:environmentSlug",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
environmentsController.updateEnvironment
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:environmentSlug",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
environmentsController.deleteEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
export default router;
|
39
backend/src/routes/v4/folders.ts
Normal file
39
backend/src/routes/v4/folders.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth} from "../../middleware";
|
||||||
|
import { foldersController } from "../../controllers/v4";
|
||||||
|
// import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
foldersController.createFolder
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
foldersController.getFolders
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
":/folderName",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
foldersController.updateFolder
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:folderName",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
foldersController.deleteFolder
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
5
backend/src/routes/v4/index.ts
Normal file
5
backend/src/routes/v4/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import secrets from "./secrets";
|
||||||
|
|
||||||
|
export {
|
||||||
|
secrets
|
||||||
|
}
|
46
backend/src/routes/v4/secrets.ts
Normal file
46
backend/src/routes/v4/secrets.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth} from "../../middleware";
|
||||||
|
import { secretsController } from "../../controllers/v4";
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
secretsController.getSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:secretName",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
secretsController.getSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/:secretName",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
secretsController.createSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:secretName",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
secretsController.updateSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:secretName",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: []
|
||||||
|
}),
|
||||||
|
secretsController.deleteSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
@ -47,3 +47,39 @@ export const ReorderWorkspaceEnvironmentsV2 = z.object({
|
|||||||
otherEnvironmentName: z.string().trim()
|
otherEnvironmentName: z.string().trim()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const CreateEnvironmentV4 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
environmentName: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetEnvironmentsV4 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
environmentName: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateEnvironmentV4 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentName: z.string().trim().optional(),
|
||||||
|
newEnvironmentSlug: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteEnvironmentV4 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
environmentSlug: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
@ -39,3 +39,12 @@ export const GetFoldersV1 = z.object({
|
|||||||
directory: z.string().trim().default("/")
|
directory: z.string().trim().default("/")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const CreateFolderV2 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
folderName: z.string().trim(),
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
path: z.string().trim().default("/")
|
||||||
|
})
|
||||||
|
});
|
@ -449,3 +449,80 @@ export const DeleteSecretByNameBatchV3 = z.object({
|
|||||||
.min(1)
|
.min(1)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const GetSecretsV4 = z.object({
|
||||||
|
query: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
path: z.string().trim().default("/"),
|
||||||
|
includeImports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetSecretV4 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
secretName: z.string().trim()
|
||||||
|
}),
|
||||||
|
query: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
path: z.string().trim().default("/"),
|
||||||
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_PERSONAL),
|
||||||
|
includeImports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("true")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateSecretV4 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
secretName: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
path: z.string().trim().default("/"),
|
||||||
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED),
|
||||||
|
secretValue: z
|
||||||
|
.string()
|
||||||
|
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
||||||
|
secretComment: z.string().trim().optional().default(""),
|
||||||
|
skipMultilineEncoding: z.boolean().optional().default(false),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateSecretV4 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
secretName: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
path: z.string().trim().default("/"),
|
||||||
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED),
|
||||||
|
secretValue: z
|
||||||
|
.string()
|
||||||
|
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||||
|
.optional(),
|
||||||
|
secretComment: z.string().trim().optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().optional()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteSecretV4 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
secretName: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
environmentSlug: z.string().trim(),
|
||||||
|
path: z.string().trim().default("/"),
|
||||||
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user