mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge branch 'Infisical:main' into cloudflare-pages-integration
This commit is contained in:
@@ -151,6 +151,7 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||
const apps = await getApps({
|
||||
integrationAuth: req.integrationAuth,
|
||||
accessToken: req.accessToken,
|
||||
accessId: req.accessId,
|
||||
...teamId && { teamId }
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Integration
|
||||
} from '../../models';
|
||||
import { EventService } from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Integration } from "../../models";
|
||||
import { EventService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import Folder from "../../models/folder";
|
||||
import { getFolderByPath } from "../../services/FolderService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
|
||||
/**
|
||||
* Create/initialize an (empty) integration for integration authorization
|
||||
@@ -25,9 +26,24 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
targetServiceId,
|
||||
owner,
|
||||
path,
|
||||
region
|
||||
region,
|
||||
secretPath,
|
||||
} = req.body;
|
||||
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: req.integrationAuth.workspace._id,
|
||||
environment: sourceEnvironment,
|
||||
});
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (!folder) {
|
||||
throw BadRequestError({
|
||||
message: "Path for service token does not exist",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
||||
|
||||
// initialize new integration after saving integration access token
|
||||
@@ -44,17 +60,18 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
secretPath,
|
||||
integration: req.integrationAuth.integration,
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId),
|
||||
}).save();
|
||||
|
||||
|
||||
if (integration) {
|
||||
// trigger event - push secrets
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: integration.workspace,
|
||||
environment: sourceEnvironment
|
||||
})
|
||||
environment: sourceEnvironment,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,7 +87,6 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const updateIntegration = async (req: Request, res: Response) => {
|
||||
|
||||
// TODO: add integration-specific validation to ensure that each
|
||||
// integration has the correct fields populated in [Integration]
|
||||
|
||||
@@ -81,8 +97,23 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
appId,
|
||||
targetEnvironment,
|
||||
owner, // github-specific integration param
|
||||
secretPath,
|
||||
} = req.body;
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: req.integration.workspace,
|
||||
environment,
|
||||
});
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (!folder) {
|
||||
throw BadRequestError({
|
||||
message: "Path for service token does not exist",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const integration = await Integration.findOneAndUpdate(
|
||||
{
|
||||
_id: req.integration._id,
|
||||
@@ -94,6 +125,7 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
appId,
|
||||
targetEnvironment,
|
||||
owner,
|
||||
secretPath,
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
@@ -105,7 +137,7 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: integration.workspace,
|
||||
environment
|
||||
environment,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
throw new Error('Failed to validate organization membership');
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getOrganizationPlan(organizationId);
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
|
||||
if (plan.memberLimit !== null) {
|
||||
// case: limit imposed on number of members allowed
|
||||
|
||||
@@ -36,8 +36,8 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
|
||||
if (!user || !user?.publicKey) {
|
||||
// case: user has already completed account
|
||||
|
||||
return res.status(403).send({
|
||||
message: "If an account exists with this email, a password reset link has been sent"
|
||||
return res.status(200).send({
|
||||
message:"If an account exists with this email, a password reset link has been sent"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
validateFolderName,
|
||||
generateFolderId,
|
||||
getParentFromFolderId,
|
||||
getFolderByPath,
|
||||
} from "../../services/FolderService";
|
||||
import { ADMIN, MEMBER } from "../../variables";
|
||||
import { validateMembership } from "../../helpers/membership";
|
||||
@@ -177,11 +178,13 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
||||
|
||||
// TODO: validate workspace
|
||||
export const getFolders = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, parentFolderId } = req.query as {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
parentFolderId?: string;
|
||||
};
|
||||
const { workspaceId, environment, parentFolderId, parentFolderPath } =
|
||||
req.query as {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
parentFolderId?: string;
|
||||
parentFolderPath?: string;
|
||||
};
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if (!folders) {
|
||||
@@ -196,6 +199,20 @@ export const getFolders = async (req: Request, res: Response) => {
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
});
|
||||
|
||||
// if instead of parentFolderId given a path like /folder1/folder2
|
||||
if (parentFolderPath) {
|
||||
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) {
|
||||
const rootFolders = folders.nodes.children.map(({ id, name }) => ({
|
||||
id,
|
||||
|
||||
@@ -116,7 +116,7 @@ export const createWorkspace = async (req: Request, res: Response) => {
|
||||
throw new Error("Failed to validate organization membership");
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getOrganizationPlan(organizationId);
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
|
||||
if (plan.workspaceLimit !== null) {
|
||||
// case: limit imposed on number of workspaces allowed
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
Membership,
|
||||
} from '../../models';
|
||||
import { SecretVersion } from '../../ee/models';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
import { EELicenseService } from '../../ee/services';
|
||||
import { BadRequestError, WorkspaceNotFoundError } from '../../utils/errors';
|
||||
import _ from 'lodash';
|
||||
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||
|
||||
@@ -22,9 +23,26 @@ export const createWorkspaceEnvironment = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
|
||||
const { workspaceId } = req.params;
|
||||
const { environmentName, environmentSlug } = req.body;
|
||||
const workspace = await Workspace.findById(workspaceId).exec();
|
||||
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
const plan = await EELicenseService.getPlan(workspace.organization.toString());
|
||||
|
||||
if (plan.environmentLimit !== null) {
|
||||
// case: limit imposed on number of environments allowed
|
||||
if (workspace.environments.length >= plan.environmentLimit) {
|
||||
// case: number of environments used exceeds the number of environments allowed
|
||||
|
||||
return res.status(400).send({
|
||||
message: 'Failed to create environment due to environment limit reached. Upgrade plan to create more environments.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!workspace ||
|
||||
workspace?.environments.find(
|
||||
@@ -40,6 +58,8 @@ export const createWorkspaceEnvironment = async (
|
||||
});
|
||||
await workspace.save();
|
||||
|
||||
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully created new environment',
|
||||
workspace: workspaceId,
|
||||
@@ -186,7 +206,9 @@ export const deleteWorkspaceEnvironment = async (
|
||||
await Membership.updateMany(
|
||||
{ workspace: workspaceId },
|
||||
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
|
||||
)
|
||||
);
|
||||
|
||||
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully deleted environment',
|
||||
|
||||
@@ -700,11 +700,15 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
(!folders && folderId && folderId !== "root") ||
|
||||
(!folders && secretPath)
|
||||
) {
|
||||
throw BadRequestError({ message: "Folder not found" });
|
||||
res.send({ secrets: [] });
|
||||
return;
|
||||
}
|
||||
if (folders && folderId !== "root") {
|
||||
const folder = searchByFolderId(folders.nodes, folderId as string);
|
||||
if (!folder) throw BadRequestError({ message: "Folder not found" });
|
||||
if (!folder) {
|
||||
res.send({ secrets: [] });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
@@ -720,10 +724,11 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (folders && secretPath) {
|
||||
if (!folders) throw BadRequestError({ message: "Folder not found" });
|
||||
// avoid throwing error and send empty list
|
||||
const folder = getFolderByPath(folders.nodes, secretPath as string);
|
||||
if (!folder) {
|
||||
throw BadRequestError({ message: "Secret path not found" });
|
||||
res.send({ secrets: [] });
|
||||
return;
|
||||
}
|
||||
folderId = folder.id;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||
secret,
|
||||
key
|
||||
});
|
||||
|
||||
|
||||
return rep;
|
||||
})
|
||||
});
|
||||
@@ -88,7 +88,7 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
secretComment,
|
||||
secretPath = "/"
|
||||
} = req.body;
|
||||
|
||||
|
||||
const key = await BotService.getWorkspaceKeyWithBot({
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
@@ -102,12 +102,12 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
plaintext: secretValue,
|
||||
key
|
||||
});
|
||||
|
||||
|
||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: secretComment,
|
||||
key
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
const secret = await SecretService.createSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
@@ -135,7 +135,7 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
|
||||
const secretWithoutBlindIndex = secret.toObject();
|
||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
secret: repackageSecretToRaw({
|
||||
secret: secretWithoutBlindIndex,
|
||||
@@ -202,11 +202,11 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretPath = "/"
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretPath = "/"
|
||||
} = req.body;
|
||||
|
||||
const { secret } = await SecretService.deleteSecret({
|
||||
@@ -391,11 +391,11 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretPath = "/"
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretPath = "/"
|
||||
} = req.body;
|
||||
|
||||
const { secret } = await SecretService.deleteSecret({
|
||||
|
||||
@@ -8,8 +8,9 @@ import { EELicenseService } from '../../services';
|
||||
*/
|
||||
export const getOrganizationPlan = async (req: Request, res: Response) => {
|
||||
const { organizationId } = req.params;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
|
||||
const plan = await EELicenseService.getOrganizationPlan(organizationId);
|
||||
const plan = await EELicenseService.getPlan(organizationId, workspaceId);
|
||||
|
||||
return res.status(200).send({
|
||||
plan,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
requireOrganizationAuth,
|
||||
validateRequest
|
||||
} from '../../../middleware';
|
||||
import { param, body } from 'express-validator';
|
||||
import { param, body, query } from 'express-validator';
|
||||
import { organizationsController } from '../../controllers/v1';
|
||||
import {
|
||||
OWNER, ADMIN, MEMBER, ACCEPTED
|
||||
@@ -21,6 +21,7 @@ router.get(
|
||||
acceptedStatuses: [ACCEPTED]
|
||||
}),
|
||||
param('organizationId').exists().trim(),
|
||||
query('workspaceId').optional().isString(),
|
||||
validateRequest,
|
||||
organizationsController.getOrganizationPlan
|
||||
);
|
||||
|
||||
@@ -22,13 +22,14 @@ interface FeatureSet {
|
||||
workspacesUsed: number;
|
||||
memberLimit: number | null;
|
||||
membersUsed: number;
|
||||
environmentLimit: number | null;
|
||||
environmentsUsed: number;
|
||||
secretVersioning: boolean;
|
||||
pitRecovery: boolean;
|
||||
rbac: boolean;
|
||||
customRateLimits: boolean;
|
||||
customAlerts: boolean;
|
||||
auditLogs: boolean;
|
||||
envLimit?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,13 +52,14 @@ class EELicenseService {
|
||||
workspacesUsed: 0,
|
||||
memberLimit: null,
|
||||
membersUsed: 0,
|
||||
environmentLimit: null,
|
||||
environmentsUsed: 0,
|
||||
secretVersioning: true,
|
||||
pitRecovery: true,
|
||||
rbac: true,
|
||||
customRateLimits: true,
|
||||
customAlerts: true,
|
||||
auditLogs: false,
|
||||
envLimit: null
|
||||
auditLogs: false
|
||||
}
|
||||
|
||||
public localFeatureSet: NodeCache;
|
||||
@@ -69,10 +71,10 @@ class EELicenseService {
|
||||
});
|
||||
}
|
||||
|
||||
public async getOrganizationPlan(organizationId: string): Promise<FeatureSet> {
|
||||
public async getPlan(organizationId: string, workspaceId?: string): Promise<FeatureSet> {
|
||||
try {
|
||||
if (this.instanceType === 'cloud') {
|
||||
const cachedPlan = this.localFeatureSet.get<FeatureSet>(organizationId);
|
||||
const cachedPlan = this.localFeatureSet.get<FeatureSet>(`${organizationId}-${workspaceId ?? ''}`);
|
||||
if (cachedPlan) {
|
||||
return cachedPlan;
|
||||
}
|
||||
@@ -80,12 +82,16 @@ class EELicenseService {
|
||||
const organization = await Organization.findById(organizationId);
|
||||
if (!organization) throw OrganizationNotFoundError();
|
||||
|
||||
const { data: { currentPlan } } = await licenseServerKeyRequest.get(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`
|
||||
);
|
||||
let url = `${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`;
|
||||
|
||||
if (workspaceId) {
|
||||
url += `?workspaceId=${workspaceId}`;
|
||||
}
|
||||
|
||||
const { data: { currentPlan } } = await licenseServerKeyRequest.get(url);
|
||||
|
||||
// cache fetched plan for organization
|
||||
this.localFeatureSet.set(organizationId, currentPlan);
|
||||
this.localFeatureSet.set(`${organizationId}-${workspaceId ?? ''}`, currentPlan);
|
||||
|
||||
return currentPlan;
|
||||
}
|
||||
@@ -96,10 +102,10 @@ class EELicenseService {
|
||||
return this.globalFeatureSet;
|
||||
}
|
||||
|
||||
public async refreshOrganizationPlan(organizationId: string) {
|
||||
public async refreshPlan(organizationId: string, workspaceId?: string) {
|
||||
if (this.instanceType === 'cloud') {
|
||||
this.localFeatureSet.del(organizationId);
|
||||
await this.getOrganizationPlan(organizationId);
|
||||
this.localFeatureSet.del(`${organizationId}-${workspaceId ?? ''}`);
|
||||
await this.getPlan(organizationId, workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
Bot,
|
||||
BotKey,
|
||||
Secret,
|
||||
ISecret,
|
||||
IUser
|
||||
} from "../models";
|
||||
import { Bot, BotKey, Secret, ISecret, IUser } from "../models";
|
||||
import {
|
||||
generateKeyPair,
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
decryptSymmetric128BitHexKeyUTF8,
|
||||
decryptAsymmetric
|
||||
} from '../utils/crypto';
|
||||
decryptAsymmetric,
|
||||
} from "../utils/crypto";
|
||||
import {
|
||||
SECRET_SHARED,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
ENCODING_SCHEME_BASE64,
|
||||
} from "../variables";
|
||||
import {
|
||||
getEncryptionKey,
|
||||
getRootEncryptionKey,
|
||||
client
|
||||
} from "../config";
|
||||
import { getEncryptionKey, getRootEncryptionKey, client } from "../config";
|
||||
import { InternalServerError } from "../utils/errors";
|
||||
import Folder from "../models/folder";
|
||||
import { getFolderByPath } from "../services/FolderService";
|
||||
|
||||
/**
|
||||
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
||||
@@ -40,15 +32,14 @@ export const createBot = async ({
|
||||
}) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
|
||||
const { publicKey, privateKey } = generateKeyPair();
|
||||
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
const {
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||
const { ciphertext, iv, tag } = client.encryptSymmetric(
|
||||
privateKey,
|
||||
rootEncryptionKey
|
||||
);
|
||||
|
||||
return await new Bot({
|
||||
name,
|
||||
@@ -59,9 +50,8 @@ export const createBot = async ({
|
||||
iv,
|
||||
tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
keyEncoding: ENCODING_SCHEME_BASE64,
|
||||
}).save();
|
||||
|
||||
} else if (encryptionKey) {
|
||||
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: privateKey,
|
||||
@@ -77,12 +67,12 @@ export const createBot = async ({
|
||||
iv,
|
||||
tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
}).save();
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to create new bot due to missing encryption key'
|
||||
message: "Failed to create new bot due to missing encryption key",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,11 +82,11 @@ export const createBot = async ({
|
||||
*/
|
||||
export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => {
|
||||
const botKey = await BotKey.exists({
|
||||
workspace: workspaceId
|
||||
});
|
||||
|
||||
workspace: workspaceId,
|
||||
});
|
||||
|
||||
return botKey ? false : true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return decrypted secrets for workspace with id [workspaceId]
|
||||
@@ -108,16 +98,38 @@ export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => {
|
||||
export const getSecretsBotHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}) => {
|
||||
const content = {} as any;
|
||||
const key = await getKey({ workspaceId: workspaceId });
|
||||
|
||||
let folderId = "root";
|
||||
const folders = await Folder.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
});
|
||||
|
||||
if (!folders && secretPath !== "/") {
|
||||
throw InternalServerError({ message: "Folder not found" });
|
||||
}
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (!folder) {
|
||||
throw InternalServerError({ message: "Folder not found" });
|
||||
}
|
||||
folderId = folder.id;
|
||||
}
|
||||
|
||||
const secrets = await Secret.find({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
type: SECRET_SHARED,
|
||||
folder: folderId,
|
||||
});
|
||||
|
||||
secrets.forEach((secret: ISecret) => {
|
||||
@@ -148,14 +160,17 @@ export const getSecretsBotHelper = async ({
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @returns {String} key - decrypted workspace key
|
||||
*/
|
||||
export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
|
||||
export const getKey = async ({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const botKey = await BotKey.findOne({
|
||||
workspace: workspaceId,
|
||||
})
|
||||
.populate<{ sender: IUser }>("sender", "publicKey");
|
||||
}).populate<{ sender: IUser }>("sender", "publicKey");
|
||||
|
||||
if (!botKey) throw new Error("Failed to find bot key");
|
||||
|
||||
@@ -168,7 +183,12 @@ export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) =
|
||||
|
||||
if (rootEncryptionKey && bot.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||
// case: encoding scheme is base64
|
||||
const privateKeyBot = client.decryptSymmetric(bot.encryptedPrivateKey, rootEncryptionKey, bot.iv, bot.tag);
|
||||
const privateKeyBot = client.decryptSymmetric(
|
||||
bot.encryptedPrivateKey,
|
||||
rootEncryptionKey,
|
||||
bot.iv,
|
||||
bot.tag
|
||||
);
|
||||
|
||||
return decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
@@ -177,15 +197,14 @@ export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) =
|
||||
privateKey: privateKeyBot,
|
||||
});
|
||||
} else if (encryptionKey && bot.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||
|
||||
// case: encoding scheme is utf8
|
||||
const privateKeyBot = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: bot.encryptedPrivateKey,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
key: encryptionKey
|
||||
key: encryptionKey,
|
||||
});
|
||||
|
||||
|
||||
return decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
nonce: botKey.nonce,
|
||||
@@ -195,7 +214,8 @@ export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) =
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: "Failed to obtain bot's copy of workspace key needed for bot operations"
|
||||
message:
|
||||
"Failed to obtain bot's copy of workspace key needed for bot operations",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -254,4 +274,4 @@ export const decryptSymmetricHelper = async ({
|
||||
});
|
||||
|
||||
return plaintext;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Types } from "mongoose";
|
||||
import { Bot, Integration, IntegrationAuth } from "../models";
|
||||
import { exchangeCode, exchangeRefresh, syncSecrets } from "../integrations";
|
||||
import { BotService } from "../services";
|
||||
import {
|
||||
Bot,
|
||||
Integration,
|
||||
IntegrationAuth
|
||||
} from '../models';
|
||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||
import { BotService } from '../services';
|
||||
import {
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8
|
||||
} from '../variables';
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
} from '../utils/errors';
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
} from "../variables";
|
||||
import { UnauthorizedRequestError } from "../utils/errors";
|
||||
|
||||
interface Update {
|
||||
workspace: string;
|
||||
integration: string;
|
||||
teamId?: string;
|
||||
accountId?: string;
|
||||
workspace: string;
|
||||
integration: string;
|
||||
teamId?: string;
|
||||
accountId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,78 +25,83 @@ interface Update {
|
||||
* - Create bot sequence for integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.integration - name of integration
|
||||
* @param {String} obj.integration - name of integration
|
||||
* @param {String} obj.code - code
|
||||
* @returns {IntegrationAuth} integrationAuth - integration auth after OAuth2 code-token exchange
|
||||
*/
|
||||
*/
|
||||
export const handleOAuthExchangeHelper = async ({
|
||||
workspaceId,
|
||||
workspaceId,
|
||||
integration,
|
||||
code,
|
||||
environment,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
integration: string;
|
||||
code: string;
|
||||
environment: string;
|
||||
}) => {
|
||||
const bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
if (!bot)
|
||||
throw new Error("Bot must be enabled for OAuth2 code-token exchange");
|
||||
|
||||
// exchange code for access and refresh tokens
|
||||
const res = await exchangeCode({
|
||||
integration,
|
||||
code,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: string;
|
||||
integration: string;
|
||||
code: string;
|
||||
environment: string;
|
||||
}) => {
|
||||
const bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
const update: Update = {
|
||||
workspace: workspaceId,
|
||||
integration,
|
||||
};
|
||||
|
||||
switch (integration) {
|
||||
case INTEGRATION_VERCEL:
|
||||
update.teamId = res.teamId;
|
||||
break;
|
||||
case INTEGRATION_NETLIFY:
|
||||
update.accountId = res.accountId;
|
||||
break;
|
||||
}
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||
{
|
||||
workspace: workspaceId,
|
||||
integration,
|
||||
},
|
||||
update,
|
||||
{
|
||||
new: true,
|
||||
upsert: true,
|
||||
}
|
||||
);
|
||||
|
||||
if (res.refreshToken) {
|
||||
// case: refresh token returned from exchange
|
||||
// set integration auth refresh token
|
||||
await setIntegrationAuthRefreshHelper({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
refreshToken: res.refreshToken,
|
||||
});
|
||||
|
||||
if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange');
|
||||
|
||||
// exchange code for access and refresh tokens
|
||||
const res = await exchangeCode({
|
||||
integration,
|
||||
code
|
||||
}
|
||||
|
||||
if (res.accessToken) {
|
||||
// case: access token returned from exchange
|
||||
// set integration auth access token
|
||||
await setIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId: null,
|
||||
accessToken: res.accessToken,
|
||||
accessExpiresAt: res.accessExpiresAt,
|
||||
});
|
||||
|
||||
const update: Update = {
|
||||
workspace: workspaceId,
|
||||
integration
|
||||
}
|
||||
|
||||
switch (integration) {
|
||||
case INTEGRATION_VERCEL:
|
||||
update.teamId = res.teamId;
|
||||
break;
|
||||
case INTEGRATION_NETLIFY:
|
||||
update.accountId = res.accountId;
|
||||
break;
|
||||
}
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||
workspace: workspaceId,
|
||||
integration
|
||||
}, update, {
|
||||
new: true,
|
||||
upsert: true
|
||||
});
|
||||
|
||||
if (res.refreshToken) {
|
||||
// case: refresh token returned from exchange
|
||||
// set integration auth refresh token
|
||||
await setIntegrationAuthRefreshHelper({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
refreshToken: res.refreshToken
|
||||
});
|
||||
}
|
||||
|
||||
if (res.accessToken) {
|
||||
// case: access token returned from exchange
|
||||
// set integration auth access token
|
||||
await setIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId: null,
|
||||
accessToken: res.accessToken,
|
||||
accessExpiresAt: res.accessExpiresAt
|
||||
});
|
||||
}
|
||||
|
||||
return integrationAuth;
|
||||
}
|
||||
}
|
||||
|
||||
return integrationAuth;
|
||||
};
|
||||
/**
|
||||
* Sync/push environment variables in workspace with id [workspaceId] to
|
||||
* all active integrations for that workspace
|
||||
@@ -110,48 +109,54 @@ export const handleOAuthExchangeHelper = async ({
|
||||
* @param {Object} obj.workspaceId - id of workspace
|
||||
*/
|
||||
export const syncIntegrationsHelper = async ({
|
||||
workspaceId,
|
||||
environment
|
||||
workspaceId,
|
||||
environment,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
}) => {
|
||||
const integrations = await Integration.find({
|
||||
workspace: workspaceId,
|
||||
...(environment ? {
|
||||
environment
|
||||
} : {}),
|
||||
isActive: true,
|
||||
app: { $ne: null }
|
||||
const integrations = await Integration.find({
|
||||
workspace: workspaceId,
|
||||
...(environment
|
||||
? {
|
||||
environment,
|
||||
}
|
||||
: {}),
|
||||
isActive: true,
|
||||
app: { $ne: null },
|
||||
});
|
||||
|
||||
// for each workspace integration, sync/push secrets
|
||||
// to that integration
|
||||
for await (const integration of integrations) {
|
||||
// get workspace, environment (shared) secrets
|
||||
const secrets = await BotService.getSecrets({
|
||||
// issue here?
|
||||
workspaceId: integration.workspace,
|
||||
environment: integration.environment,
|
||||
secretPath: integration.secretPath,
|
||||
});
|
||||
|
||||
// for each workspace integration, sync/push secrets
|
||||
// to that integration
|
||||
for await (const integration of integrations) {
|
||||
// get workspace, environment (shared) secrets
|
||||
const secrets = await BotService.getSecrets({ // issue here?
|
||||
workspaceId: integration.workspace,
|
||||
environment: integration.environment
|
||||
});
|
||||
const integrationAuth = await IntegrationAuth.findById(
|
||||
integration.integrationAuth
|
||||
);
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
|
||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||
|
||||
// get integration auth access token
|
||||
const access = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integration.integrationAuth
|
||||
});
|
||||
// get integration auth access token
|
||||
const access = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integration.integrationAuth,
|
||||
});
|
||||
|
||||
// sync secrets to integration
|
||||
await syncSecrets({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessId: access.accessId === undefined ? null : access.accessId,
|
||||
accessToken: access.accessToken
|
||||
});
|
||||
}
|
||||
}
|
||||
// sync secrets to integration
|
||||
await syncSecrets({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessId: access.accessId === undefined ? null : access.accessId,
|
||||
accessToken: access.accessToken,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return decrypted refresh token using the bot's copy
|
||||
@@ -161,22 +166,29 @@ export const syncIntegrationsHelper = async ({
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} refreshToken - decrypted refresh token
|
||||
*/
|
||||
export const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integrationAuthId)
|
||||
.select('+refreshCiphertext +refreshIV +refreshTag');
|
||||
export const getIntegrationAuthRefreshHelper = async ({
|
||||
integrationAuthId,
|
||||
}: {
|
||||
integrationAuthId: Types.ObjectId;
|
||||
}) => {
|
||||
const integrationAuth = await IntegrationAuth.findById(
|
||||
integrationAuthId
|
||||
).select("+refreshCiphertext +refreshIV +refreshTag");
|
||||
|
||||
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||
|
||||
const refreshToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.refreshCiphertext as string,
|
||||
iv: integrationAuth.refreshIV as string,
|
||||
tag: integrationAuth.refreshTag as string
|
||||
if (!integrationAuth)
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed to locate Integration Authentication credentials",
|
||||
});
|
||||
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
const refreshToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.refreshCiphertext as string,
|
||||
iv: integrationAuth.refreshIV as string,
|
||||
tag: integrationAuth.refreshTag as string,
|
||||
});
|
||||
|
||||
return refreshToken;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return decrypted access token using the bot's copy
|
||||
@@ -186,50 +198,65 @@ export const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { i
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @returns {String} accessToken - decrypted access token
|
||||
*/
|
||||
export const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
let accessId;
|
||||
let accessToken;
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integrationAuthId)
|
||||
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag');
|
||||
export const getIntegrationAuthAccessHelper = async ({
|
||||
integrationAuthId,
|
||||
}: {
|
||||
integrationAuthId: Types.ObjectId;
|
||||
}) => {
|
||||
let accessId;
|
||||
let accessToken;
|
||||
const integrationAuth = await IntegrationAuth.findById(
|
||||
integrationAuthId
|
||||
).select(
|
||||
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag"
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||
|
||||
accessToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessCiphertext as string,
|
||||
iv: integrationAuth.accessIV as string,
|
||||
tag: integrationAuth.accessTag as string
|
||||
if (!integrationAuth)
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed to locate Integration Authentication credentials",
|
||||
});
|
||||
|
||||
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
||||
// there is a access token expiration date
|
||||
// and refresh token to exchange with the OAuth2 server
|
||||
|
||||
if (integrationAuth.accessExpiresAt < new Date()) {
|
||||
// access token is expired
|
||||
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
|
||||
accessToken = await exchangeRefresh({
|
||||
integrationAuth,
|
||||
refreshToken
|
||||
});
|
||||
}
|
||||
accessToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessCiphertext as string,
|
||||
iv: integrationAuth.accessIV as string,
|
||||
tag: integrationAuth.accessTag as string,
|
||||
});
|
||||
|
||||
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
||||
// there is a access token expiration date
|
||||
// and refresh token to exchange with the OAuth2 server
|
||||
|
||||
if (integrationAuth.accessExpiresAt < new Date()) {
|
||||
// access token is expired
|
||||
const refreshToken = await getIntegrationAuthRefreshHelper({
|
||||
integrationAuthId,
|
||||
});
|
||||
accessToken = await exchangeRefresh({
|
||||
integrationAuth,
|
||||
refreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) {
|
||||
accessId = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessIdCiphertext as string,
|
||||
iv: integrationAuth.accessIdIV as string,
|
||||
tag: integrationAuth.accessIdTag as string
|
||||
});
|
||||
}
|
||||
|
||||
return ({
|
||||
accessId,
|
||||
accessToken
|
||||
}
|
||||
|
||||
if (
|
||||
integrationAuth?.accessIdCiphertext &&
|
||||
integrationAuth?.accessIdIV &&
|
||||
integrationAuth?.accessIdTag
|
||||
) {
|
||||
accessId = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessIdCiphertext as string,
|
||||
iv: integrationAuth.accessIdIV as string,
|
||||
tag: integrationAuth.accessIdTag as string,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
accessId,
|
||||
accessToken,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypt refresh token [refreshToken] using the bot's copy
|
||||
@@ -240,41 +267,43 @@ export const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { in
|
||||
* @param {String} obj.refreshToken - refresh token
|
||||
*/
|
||||
export const setIntegrationAuthRefreshHelper = async ({
|
||||
integrationAuthId,
|
||||
refreshToken
|
||||
integrationAuthId,
|
||||
refreshToken,
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
refreshToken: string;
|
||||
integrationAuthId: string;
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
|
||||
let integrationAuth = await IntegrationAuth
|
||||
.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||
|
||||
const obj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: refreshToken
|
||||
});
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||
_id: integrationAuthId
|
||||
}, {
|
||||
refreshCiphertext: obj.ciphertext,
|
||||
refreshIV: obj.iv,
|
||||
refreshTag: obj.tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}, {
|
||||
new: true
|
||||
});
|
||||
|
||||
return integrationAuth;
|
||||
}
|
||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
|
||||
const obj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: refreshToken,
|
||||
});
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||
{
|
||||
_id: integrationAuthId,
|
||||
},
|
||||
{
|
||||
refreshCiphertext: obj.ciphertext,
|
||||
refreshIV: obj.iv,
|
||||
refreshTag: obj.tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
}
|
||||
);
|
||||
|
||||
return integrationAuth;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypt access token [accessToken] and (optionally) access id [accessId]
|
||||
* using the bot's copy of the workspace key for workspace belonging to
|
||||
* using the bot's copy of the workspace key for workspace belonging to
|
||||
* integration auth with id [integrationAuthId] and store it along with [accessExpiresAt]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
@@ -282,48 +311,52 @@ export const setIntegrationAuthRefreshHelper = async ({
|
||||
* @param {Date} obj.accessExpiresAt - expiration date of access token
|
||||
*/
|
||||
export const setIntegrationAuthAccessHelper = async ({
|
||||
integrationAuthId,
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt
|
||||
integrationAuthId,
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt,
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessExpiresAt: Date | undefined;
|
||||
integrationAuthId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessExpiresAt: Date | undefined;
|
||||
}) => {
|
||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||
|
||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessToken
|
||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
|
||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessToken,
|
||||
});
|
||||
|
||||
let encryptedAccessIdObj;
|
||||
if (accessId) {
|
||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessId,
|
||||
});
|
||||
|
||||
let encryptedAccessIdObj;
|
||||
if (accessId) {
|
||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessId
|
||||
});
|
||||
}
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||
{
|
||||
_id: integrationAuthId,
|
||||
},
|
||||
{
|
||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
||||
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj.iv,
|
||||
accessTag: encryptedAccessTokenObj.tag,
|
||||
accessExpiresAt,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
}
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||
_id: integrationAuthId
|
||||
}, {
|
||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
||||
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj.iv,
|
||||
accessTag: encryptedAccessTokenObj.tag,
|
||||
accessExpiresAt,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}, {
|
||||
new: true
|
||||
});
|
||||
|
||||
return integrationAuth;
|
||||
}
|
||||
);
|
||||
|
||||
return integrationAuth;
|
||||
};
|
||||
|
||||
@@ -170,7 +170,7 @@ export const updateSubscriptionOrgQuantity = async ({
|
||||
);
|
||||
}
|
||||
|
||||
await EELicenseService.refreshOrganizationPlan(organizationId);
|
||||
await EELicenseService.refreshPlan(organizationId);
|
||||
|
||||
return stripeSubscription;
|
||||
};
|
||||
@@ -1,16 +1,16 @@
|
||||
import rateLimit from 'express-rate-limit';
|
||||
const MongoStore = require('rate-limit-mongo');
|
||||
// const MongoStore = require('rate-limit-mongo');
|
||||
|
||||
// 200 per minute
|
||||
export const apiLimiter = rateLimit({
|
||||
store: new MongoStore({
|
||||
uri: process.env.MONGO_URL,
|
||||
expireTimeMs: 1000 * 60,
|
||||
collectionName: "expressRateRecords-apiLimiter",
|
||||
errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
||||
}),
|
||||
windowMs: 1000 * 60,
|
||||
max: 200,
|
||||
// store: new MongoStore({
|
||||
// uri: process.env.MONGO_URL,
|
||||
// expireTimeMs: 1000 * 60,
|
||||
// collectionName: "expressRateRecords-apiLimiter",
|
||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
||||
// }),
|
||||
windowMs: 60 * 1000,
|
||||
max: 240,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
skip: (request) => {
|
||||
@@ -23,14 +23,14 @@ export const apiLimiter = rateLimit({
|
||||
|
||||
// 50 requests per 1 hours
|
||||
const authLimit = rateLimit({
|
||||
store: new MongoStore({
|
||||
uri: process.env.MONGO_URL,
|
||||
expireTimeMs: 1000 * 60 * 60,
|
||||
errorHandler: console.error.bind(null, 'rate-limit-mongo'),
|
||||
collectionName: "expressRateRecords-authLimit",
|
||||
}),
|
||||
windowMs: 1000 * 60 * 60,
|
||||
max: 50,
|
||||
// store: new MongoStore({
|
||||
// uri: process.env.MONGO_URL,
|
||||
// expireTimeMs: 1000 * 60 * 60,
|
||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
|
||||
// collectionName: "expressRateRecords-authLimit",
|
||||
// }),
|
||||
windowMs: 60 * 1000,
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
@@ -40,14 +40,14 @@ const authLimit = rateLimit({
|
||||
|
||||
// 5 requests per 1 hour
|
||||
export const passwordLimiter = rateLimit({
|
||||
store: new MongoStore({
|
||||
uri: process.env.MONGO_URL,
|
||||
expireTimeMs: 1000 * 60 * 60,
|
||||
errorHandler: console.error.bind(null, 'rate-limit-mongo'),
|
||||
collectionName: "expressRateRecords-passwordLimiter",
|
||||
}),
|
||||
windowMs: 1000 * 60 * 60,
|
||||
max: 5,
|
||||
// store: new MongoStore({
|
||||
// uri: process.env.MONGO_URL,
|
||||
// expireTimeMs: 1000 * 60 * 60,
|
||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
|
||||
// collectionName: "expressRateRecords-passwordLimiter",
|
||||
// }),
|
||||
windowMs: 60 * 60 * 1000,
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
|
||||
@@ -57,7 +57,7 @@ import { getFolderIdFromServiceToken } from "../services/FolderService";
|
||||
export const repackageSecretToRaw = ({
|
||||
secret,
|
||||
key
|
||||
}:{
|
||||
}: {
|
||||
secret: ISecret;
|
||||
key: string;
|
||||
}) => {
|
||||
@@ -76,8 +76,8 @@ export const repackageSecretToRaw = ({
|
||||
key
|
||||
});
|
||||
|
||||
let secretComment: string = '';
|
||||
|
||||
let secretComment: string = '';
|
||||
|
||||
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
|
||||
secretComment = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretCommentCiphertext,
|
||||
@@ -86,7 +86,7 @@ export const repackageSecretToRaw = ({
|
||||
key
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return ({
|
||||
_id: secret._id,
|
||||
version: secret.version,
|
||||
@@ -503,7 +503,7 @@ export const getSecretsHelper = async ({
|
||||
folder: folderId,
|
||||
type: SECRET_PERSONAL,
|
||||
...getAuthDataPayloadUserObj(authData),
|
||||
}).lean();
|
||||
}).populate("tags").lean();
|
||||
|
||||
// concat with shared secrets
|
||||
secrets = secrets.concat(
|
||||
@@ -515,7 +515,7 @@ export const getSecretsHelper = async ({
|
||||
secretBlindIndex: {
|
||||
$nin: secrets.map((secret) => secret.secretBlindIndex),
|
||||
},
|
||||
}).lean()
|
||||
}).populate("tags").lean()
|
||||
);
|
||||
|
||||
// (EE) create (audit) log
|
||||
@@ -553,7 +553,7 @@ export const getSecretsHelper = async ({
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return secrets;
|
||||
};
|
||||
|
||||
@@ -652,7 +652,7 @@ export const getSecretHelper = async ({
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return secret;
|
||||
};
|
||||
|
||||
@@ -843,7 +843,7 @@ export const deleteSecretHelper = async ({
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
|
||||
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
@@ -909,12 +909,12 @@ export const deleteSecretHelper = async ({
|
||||
});
|
||||
|
||||
action && (await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP,
|
||||
}));
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP,
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
@@ -941,7 +941,7 @@ export const deleteSecretHelper = async ({
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return ({
|
||||
secrets,
|
||||
secret
|
||||
|
||||
@@ -41,7 +41,7 @@ export const createWorkspace = async ({
|
||||
workspaceId: workspace._id
|
||||
});
|
||||
|
||||
await EELicenseService.refreshOrganizationPlan(organizationId);
|
||||
await EELicenseService.refreshPlan(organizationId);
|
||||
|
||||
return workspace;
|
||||
};
|
||||
|
||||
@@ -50,10 +50,12 @@ interface App {
|
||||
const getApps = async ({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
accessId,
|
||||
teamId,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
accessId?: string;
|
||||
teamId?: string;
|
||||
}) => {
|
||||
let apps: App[] = [];
|
||||
|
||||
@@ -23,7 +23,7 @@ const requireIntegrationAuthorizationAuth = ({
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { integrationAuthId } = req[location];
|
||||
|
||||
const { integrationAuth, accessToken } = await validateClientForIntegrationAuth({
|
||||
const { integrationAuth, accessToken, accessId } = await validateClientForIntegrationAuth({
|
||||
authData: req.authData,
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId),
|
||||
acceptedRoles,
|
||||
@@ -38,6 +38,10 @@ const requireIntegrationAuthorizationAuth = ({
|
||||
req.accessToken = accessToken;
|
||||
}
|
||||
|
||||
if (accessId) {
|
||||
req.accessId = accessId;
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_HASHICORP_VAULT
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
} from "../variables";
|
||||
|
||||
export interface IIntegration {
|
||||
@@ -33,23 +33,24 @@ export interface IIntegration {
|
||||
targetServiceId: string;
|
||||
path: string;
|
||||
region: string;
|
||||
secretPath: string;
|
||||
integration:
|
||||
| 'azure-key-vault'
|
||||
| 'aws-parameter-store'
|
||||
| 'aws-secret-manager'
|
||||
| 'heroku'
|
||||
| 'vercel'
|
||||
| 'netlify'
|
||||
| 'github'
|
||||
| 'gitlab'
|
||||
| 'render'
|
||||
| 'railway'
|
||||
| 'flyio'
|
||||
| 'circleci'
|
||||
| 'travisci'
|
||||
| 'supabase'
|
||||
| 'checkly'
|
||||
| 'hashicorp-vault';
|
||||
| "azure-key-vault"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "checkly"
|
||||
| "hashicorp-vault";
|
||||
integrationAuth: Types.ObjectId;
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
url: {
|
||||
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
app: {
|
||||
// name of app in provider
|
||||
@@ -90,17 +91,17 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
},
|
||||
targetEnvironmentId: {
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
targetService: {
|
||||
// railway-specific service
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
targetServiceId: {
|
||||
// railway-specific service
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
owner: {
|
||||
// github-specific repo owner-login
|
||||
@@ -111,12 +112,12 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
// aws-parameter-store-specific path
|
||||
// (also) vercel preview-branch
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
region: {
|
||||
// aws-parameter-store-specific path
|
||||
type: String,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
@@ -136,7 +137,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_HASHICORP_VAULT
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
@@ -145,6 +146,11 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
ref: "IntegrationAuth",
|
||||
required: true,
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "/",
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
@@ -1,75 +1,77 @@
|
||||
import express from 'express';
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
requireIntegrationAuth,
|
||||
requireIntegrationAuthorizationAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
requireAuth,
|
||||
requireIntegrationAuth,
|
||||
requireIntegrationAuthorizationAuth,
|
||||
validateRequest,
|
||||
} from "../../middleware";
|
||||
import {
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../../variables';
|
||||
import { body, param } from 'express-validator';
|
||||
import { integrationController } from '../../controllers/v1';
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
} from "../../variables";
|
||||
import { body, param } from "express-validator";
|
||||
import { integrationController } from "../../controllers/v1";
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
location: 'body'
|
||||
}),
|
||||
body('integrationAuthId').exists().isString().trim(),
|
||||
body('app').trim(),
|
||||
body('isActive').exists().isBoolean(),
|
||||
body('appId').trim(),
|
||||
body('sourceEnvironment').trim(),
|
||||
body('targetEnvironment').trim(),
|
||||
body('targetEnvironmentId').trim(),
|
||||
body('targetService').trim(),
|
||||
body('targetServiceId').trim(),
|
||||
body('owner').trim(),
|
||||
body('path').trim(),
|
||||
body('region').trim(),
|
||||
validateRequest,
|
||||
integrationController.createIntegration
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
location: "body",
|
||||
}),
|
||||
body("integrationAuthId").exists().isString().trim(),
|
||||
body("app").trim(),
|
||||
body("isActive").exists().isBoolean(),
|
||||
body("appId").trim(),
|
||||
body("secretPath").default("/").isString().trim(),
|
||||
body("sourceEnvironment").trim(),
|
||||
body("targetEnvironment").trim(),
|
||||
body("targetEnvironmentId").trim(),
|
||||
body("targetService").trim(),
|
||||
body("targetServiceId").trim(),
|
||||
body("owner").trim(),
|
||||
body("path").trim(),
|
||||
body("region").trim(),
|
||||
validateRequest,
|
||||
integrationController.createIntegration
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:integrationId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireIntegrationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
}),
|
||||
param('integrationId').exists().trim(),
|
||||
body('isActive').exists().isBoolean(),
|
||||
body('app').exists().trim(),
|
||||
body('environment').exists().trim(),
|
||||
body('appId').exists(),
|
||||
body('targetEnvironment').exists(),
|
||||
body('owner').exists(),
|
||||
validateRequest,
|
||||
integrationController.updateIntegration
|
||||
"/:integrationId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireIntegrationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
}),
|
||||
param("integrationId").exists().trim(),
|
||||
body("isActive").exists().isBoolean(),
|
||||
body("app").exists().trim(),
|
||||
body("secretPath").default("/").isString().trim(),
|
||||
body("environment").exists().trim(),
|
||||
body("appId").exists(),
|
||||
body("targetEnvironment").exists(),
|
||||
body("owner").exists(),
|
||||
validateRequest,
|
||||
integrationController.updateIntegration
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:integrationId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireIntegrationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
}),
|
||||
param('integrationId').exists().trim(),
|
||||
validateRequest,
|
||||
integrationController.deleteIntegration
|
||||
"/:integrationId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireIntegrationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
}),
|
||||
param("integrationId").exists().trim(),
|
||||
validateRequest,
|
||||
integrationController.deleteIntegration
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -63,6 +63,7 @@ router.get(
|
||||
query("workspaceId").exists().isString().trim(),
|
||||
query("environment").exists().isString().trim(),
|
||||
query("parentFolderId").optional().isString().trim(),
|
||||
query("parentFolderPath").optional().isString().trim(),
|
||||
validateRequest,
|
||||
getFolders
|
||||
);
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
router.get(
|
||||
"/raw",
|
||||
query("workspaceId").exists().isString().trim(),
|
||||
query("workspaceId").exists().isString().trim(),
|
||||
query("environment").exists().isString().trim(),
|
||||
query("secretPath").default("/").isString().trim(),
|
||||
validateRequest,
|
||||
|
||||
@@ -1,110 +1,112 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
getSecretsBotHelper,
|
||||
encryptSymmetricHelper,
|
||||
decryptSymmetricHelper,
|
||||
getKey,
|
||||
getIsWorkspaceE2EEHelper
|
||||
} from '../helpers/bot';
|
||||
getSecretsBotHelper,
|
||||
encryptSymmetricHelper,
|
||||
decryptSymmetricHelper,
|
||||
getKey,
|
||||
getIsWorkspaceE2EEHelper,
|
||||
} from "../helpers/bot";
|
||||
|
||||
/**
|
||||
* Class to handle bot actions
|
||||
*/
|
||||
class BotService {
|
||||
|
||||
/**
|
||||
* Return whether or not workspace with id [workspaceId] is end-to-end encrypted
|
||||
* @param workspaceId - id of workspace
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
static async getIsWorkspaceE2EE(workspaceId: Types.ObjectId) {
|
||||
return await getIsWorkspaceE2EEHelper(workspaceId);
|
||||
}
|
||||
/**
|
||||
* Return whether or not workspace with id [workspaceId] is end-to-end encrypted
|
||||
* @param workspaceId - id of workspace
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
static async getIsWorkspaceE2EE(workspaceId: Types.ObjectId) {
|
||||
return await getIsWorkspaceE2EEHelper(workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workspace key for workspace with id [workspaceId] shared to bot.
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get workspace key for
|
||||
* @returns
|
||||
*/
|
||||
static async getWorkspaceKeyWithBot({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await getKey({
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return decrypted secrets for workspace with id [workspaceId] and
|
||||
* environment [environmen] shared to bot.
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace of secrets
|
||||
* @param {String} obj.environment - environment for secrets
|
||||
* @returns {Object} secretObj - object where keys are secret keys and values are secret values
|
||||
*/
|
||||
static async getSecrets({
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
}) {
|
||||
return await getSecretsBotHelper({
|
||||
workspaceId,
|
||||
environment
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return symmetrically encrypted [plaintext] using the
|
||||
* bot's copy of the workspace key for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.plaintext - plaintext to encrypt
|
||||
*/
|
||||
static async encryptSymmetric({
|
||||
workspaceId,
|
||||
plaintext
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
plaintext: string;
|
||||
}) {
|
||||
return await encryptSymmetricHelper({
|
||||
workspaceId,
|
||||
plaintext
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return symmetrically decrypted [ciphertext] using the
|
||||
* bot's copy of the workspace key for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||
* @param {String} obj.iv - iv
|
||||
* @param {String} obj.tag - tag
|
||||
*/
|
||||
static async decryptSymmetric({
|
||||
workspaceId,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
}) {
|
||||
return await decryptSymmetricHelper({
|
||||
workspaceId,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get workspace key for workspace with id [workspaceId] shared to bot.
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get workspace key for
|
||||
* @returns
|
||||
*/
|
||||
static async getWorkspaceKeyWithBot({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await getKey({
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return decrypted secrets for workspace with id [workspaceId] and
|
||||
* environment [environmen] shared to bot.
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace of secrets
|
||||
* @param {String} obj.environment - environment for secrets
|
||||
* @returns {Object} secretObj - object where keys are secret keys and values are secret values
|
||||
*/
|
||||
static async getSecrets({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}) {
|
||||
return await getSecretsBotHelper({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return symmetrically encrypted [plaintext] using the
|
||||
* bot's copy of the workspace key for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.plaintext - plaintext to encrypt
|
||||
*/
|
||||
static async encryptSymmetric({
|
||||
workspaceId,
|
||||
plaintext,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
plaintext: string;
|
||||
}) {
|
||||
return await encryptSymmetricHelper({
|
||||
workspaceId,
|
||||
plaintext,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return symmetrically decrypted [ciphertext] using the
|
||||
* bot's copy of the workspace key for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||
* @param {String} obj.iv - iv
|
||||
* @param {String} obj.tag - tag
|
||||
*/
|
||||
static async decryptSymmetric({
|
||||
workspaceId,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
}) {
|
||||
return await decryptSymmetricHelper({
|
||||
workspaceId,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default BotService;
|
||||
export default BotService;
|
||||
|
||||
1
backend/src/types/express/index.d.ts
vendored
1
backend/src/types/express/index.d.ts
vendored
@@ -37,6 +37,7 @@ declare global {
|
||||
serviceToken: any;
|
||||
serviceAccount: any;
|
||||
accessToken: any;
|
||||
accessId: any;
|
||||
serviceTokenData: any;
|
||||
apiKeyData: any;
|
||||
query?: any;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
BackupPrivateKey,
|
||||
IntegrationAuth,
|
||||
ServiceTokenData,
|
||||
Integration,
|
||||
} from "../../models";
|
||||
import { generateKeyPair } from "../../utils/crypto";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
|
||||
@@ -424,3 +425,19 @@ export const backfillServiceToken = async () => {
|
||||
);
|
||||
console.log("Migration: Service token migration v1 complete");
|
||||
};
|
||||
|
||||
export const backfillIntegration = async () => {
|
||||
await Integration.updateMany(
|
||||
{
|
||||
secretPath: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
secretPath: "/",
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("Migration: Integration migration v1 complete");
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
backfillEncryptionMetadata,
|
||||
backfillSecretFolders,
|
||||
backfillServiceToken,
|
||||
backfillIntegration,
|
||||
} from "./backfillData";
|
||||
import {
|
||||
reencryptBotPrivateKeys,
|
||||
@@ -77,6 +78,7 @@ export const setup = async () => {
|
||||
await backfillEncryptionMetadata();
|
||||
await backfillSecretFolders();
|
||||
await backfillServiceToken();
|
||||
await backfillIntegration();
|
||||
|
||||
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||
// to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||
|
||||
@@ -56,11 +56,14 @@ import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
|
||||
let accessToken;
|
||||
let accessToken, accessId;
|
||||
if (attachAccessToken) {
|
||||
accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||
const access = (await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id
|
||||
})).accessToken;
|
||||
}));
|
||||
|
||||
accessToken = access.accessToken;
|
||||
accessId = access.accessId;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
@@ -70,7 +73,7 @@ import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
return ({ integrationAuth, accessToken, accessId });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
@@ -79,7 +82,7 @@ import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
workspaceId: integrationAuth.workspace._id
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
return ({ integrationAuth, accessToken, accessId });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
@@ -95,7 +98,7 @@ import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
return ({ integrationAuth, accessToken, accessId });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
|
||||
Reference in New Issue
Block a user