feat: remove try-catch blocks for handling errors in middleware

This commit is contained in:
Spelchure
2023-05-01 17:14:39 +03:00
parent addac63700
commit 21eb1815c4
22 changed files with 2141 additions and 2580 deletions
+3 -33
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { Action } from '../models'; import { Action } from '../models';
import { import {
@@ -36,8 +35,6 @@ const createActionUpdateSecret = async ({
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[]; secretIds: Types.ObjectId[];
}) => { }) => {
let action;
try {
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({ const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds, secretIds,
n: 2 n: 2
@@ -47,7 +44,7 @@ const createActionUpdateSecret = async ({
newSecretVersion: s.versions[1]._id newSecretVersion: s.versions[1]._id
})); }));
action = await new Action({ const action = await new Action({
name, name,
user: userId, user: userId,
serviceAccount: serviceAccountId, serviceAccount: serviceAccountId,
@@ -58,12 +55,6 @@ const createActionUpdateSecret = async ({
} }
}).save(); }).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create update secret action');
}
return action; return action;
} }
@@ -90,8 +81,6 @@ const createActionSecret = async ({
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[]; secretIds: Types.ObjectId[];
}) => { }) => {
let action;
try {
// case: action is adding, deleting, or reading secrets // case: action is adding, deleting, or reading secrets
// -> add new secret versions // -> add new secret versions
const latestSecretVersions = (await getLatestSecretVersionIds({ const latestSecretVersions = (await getLatestSecretVersionIds({
@@ -101,7 +90,7 @@ const createActionSecret = async ({
newSecretVersion: s.versionId newSecretVersion: s.versionId
})); }));
action = await new Action({ const action = await new Action({
name, name,
user: userId, user: userId,
serviceAccount: serviceAccountId, serviceAccount: serviceAccountId,
@@ -112,12 +101,6 @@ const createActionSecret = async ({
} }
}).save(); }).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action create/read/delete secret action');
}
return action; return action;
} }
@@ -140,19 +123,12 @@ const createActionClient = ({
serviceAccountId?: Types.ObjectId; serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId; serviceTokenDataId?: Types.ObjectId;
}) => { }) => {
let action; const action = new Action({
try {
action = new Action({
name, name,
user: userId, user: userId,
serviceAccount: serviceAccountId, serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId serviceTokenData: serviceTokenDataId
}).save(); }).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create client action');
}
return action; return action;
} }
@@ -181,7 +157,6 @@ const createActionHelper = async ({
secretIds?: Types.ObjectId[]; secretIds?: Types.ObjectId[];
}) => { }) => {
let action; let action;
try {
switch (name) { switch (name) {
case ACTION_LOGIN: case ACTION_LOGIN:
case ACTION_LOGOUT: case ACTION_LOGOUT:
@@ -211,11 +186,6 @@ const createActionHelper = async ({
}); });
break; break;
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action');
}
return action; return action;
} }
+1 -9
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { import {
Log, Log,
@@ -32,9 +31,7 @@ const createLogHelper = async ({
channel: string; channel: string;
ipAddress: string; ipAddress: string;
}) => { }) => {
let log; const log = await new Log({
try {
log = await new Log({
user: userId, user: userId,
serviceAccount: serviceAccountId, serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId, serviceTokenData: serviceTokenDataId,
@@ -44,11 +41,6 @@ const createLogHelper = async ({
channel, channel,
ipAddress ipAddress
}).save(); }).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create log');
}
return log; return log;
} }
+58 -83
View File
@@ -1,14 +1,6 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import * as Sentry from '@sentry/node'; import { Secret, ISecret } from "../../models";
import { import { SecretSnapshot, SecretVersion, ISecretVersion } from "../models";
Secret,
ISecret,
} from '../../models';
import {
SecretSnapshot,
SecretVersion,
ISecretVersion
} from '../models';
/** /**
* Save a secret snapshot that is a copy of the current state of secrets in workspace with id * Save a secret snapshot that is a copy of the current state of secrets in workspace with id
@@ -19,56 +11,53 @@ import {
* @returns {SecretSnapshot} secretSnapshot - new secret snapshot * @returns {SecretSnapshot} secretSnapshot - new secret snapshot
*/ */
const takeSecretSnapshotHelper = async ({ const takeSecretSnapshotHelper = async ({
workspaceId workspaceId,
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
}) => { }) => {
const secretIds = (
await Secret.find(
{
workspace: workspaceId,
},
"_id"
)
).map((s) => s._id);
let secretSnapshot; const latestSecretVersions = (
try { await SecretVersion.aggregate([
const secretIds = (await Secret.find({
workspace: workspaceId
}, '_id')).map((s) => s._id);
const latestSecretVersions = (await SecretVersion.aggregate([
{ {
$match: { $match: {
secret: { secret: {
$in: secretIds $in: secretIds,
} },
} },
}, },
{ {
$group: { $group: {
_id: '$secret', _id: "$secret",
version: { $max: '$version' }, version: { $max: "$version" },
versionId: { $max: '$_id' } // secret version id versionId: { $max: "$_id" }, // secret version id
} },
}, },
{ {
$sort: { version: -1 } $sort: { version: -1 },
} },
]) ]).exec()
.exec()) ).map((s) => s.versionId);
.map((s) => s.versionId);
const latestSecretSnapshot = await SecretSnapshot.findOne({ const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId workspace: workspaceId,
}).sort({ version: -1 }); }).sort({ version: -1 });
secretSnapshot = await new SecretSnapshot({ const secretSnapshot = await new SecretSnapshot({
workspace: workspaceId, workspace: workspaceId,
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1, version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
secretVersions: latestSecretVersions secretVersions: latestSecretVersions,
}).save(); }).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to take a secret snapshot');
}
return secretSnapshot; return secretSnapshot;
} };
/** /**
* Add secret versions [secretVersions] to the SecretVersion collection. * Add secret versions [secretVersions] to the SecretVersion collection.
@@ -77,49 +66,38 @@ const takeSecretSnapshotHelper = async ({
* @returns {SecretVersion[]} newSecretVersions - new secret versions * @returns {SecretVersion[]} newSecretVersions - new secret versions
*/ */
const addSecretVersionsHelper = async ({ const addSecretVersionsHelper = async ({
secretVersions secretVersions,
}: { }: {
secretVersions: ISecretVersion[] secretVersions: ISecretVersion[];
}) => { }) => {
let newSecretVersions; const newSecretVersions = await SecretVersion.insertMany(secretVersions);
try {
newSecretVersions = await SecretVersion.insertMany(secretVersions);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(`Failed to add secret versions [err=${err}]`);
}
return newSecretVersions; return newSecretVersions;
} };
const markDeletedSecretVersionsHelper = async ({ const markDeletedSecretVersionsHelper = async ({
secretIds secretIds,
}: { }: {
secretIds: Types.ObjectId[]; secretIds: Types.ObjectId[];
}) => { }) => {
try { await SecretVersion.updateMany(
await SecretVersion.updateMany({ {
secret: { $in: secretIds } secret: { $in: secretIds },
}, { },
isDeleted: true {
}, { isDeleted: true,
new: true },
}); {
} catch (err) { new: true,
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to mark secret versions as deleted');
} }
} );
};
/** /**
* Initialize secret versioning by setting previously unversioned * Initialize secret versioning by setting previously unversioned
* secrets to version 1 and begin populating secret versions. * secrets to version 1 and begin populating secret versions.
*/ */
const initSecretVersioningHelper = async () => { const initSecretVersioningHelper = async () => {
try {
await Secret.updateMany( await Secret.updateMany(
{ version: { $exists: false } }, { version: { $exists: false } },
{ $set: { version: 1 } } { $set: { version: 1 } }
@@ -128,10 +106,10 @@ const initSecretVersioningHelper = async () => {
const unversionedSecrets: ISecret[] = await Secret.aggregate([ const unversionedSecrets: ISecret[] = await Secret.aggregate([
{ {
$lookup: { $lookup: {
from: 'secretversions', from: "secretversions",
localField: '_id', localField: "_id",
foreignField: 'secret', foreignField: "secret",
as: 'versions', as: "versions",
}, },
}, },
{ {
@@ -143,27 +121,24 @@ const initSecretVersioningHelper = async () => {
if (unversionedSecrets.length > 0) { if (unversionedSecrets.length > 0) {
await addSecretVersionsHelper({ await addSecretVersionsHelper({
secretVersions: unversionedSecrets.map((s, idx) => new SecretVersion({ secretVersions: unversionedSecrets.map(
(s, idx) =>
new SecretVersion({
...s, ...s,
secret: s._id, secret: s._id,
version: s.version ? s.version : 1, version: s.version ? s.version : 1,
isDeleted: false, isDeleted: false,
workspace: s.workspace, workspace: s.workspace,
environment: s.environment environment: s.environment,
})) })
),
}); });
} }
};
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to ensure that secrets are versioned');
}
}
export { export {
takeSecretSnapshotHelper, takeSecretSnapshotHelper,
addSecretVersionsHelper, addSecretVersionsHelper,
markDeletedSecretVersionsHelper, markDeletedSecretVersionsHelper,
initSecretVersioningHelper initSecretVersioningHelper,
} };
+2 -20
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { SecretVersion } from '../models'; import { SecretVersion } from '../models';
@@ -13,16 +12,13 @@ const getLatestSecretVersionIds = async ({
}: { }: {
secretIds: Types.ObjectId[]; secretIds: Types.ObjectId[];
}) => { }) => {
interface LatestSecretVersionId { interface LatestSecretVersionId {
_id: Types.ObjectId; _id: Types.ObjectId;
version: number; version: number;
versionId: Types.ObjectId; versionId: Types.ObjectId;
} }
let latestSecretVersionIds: LatestSecretVersionId[]; const latestSecretVersionIds = (await SecretVersion.aggregate([
try {
latestSecretVersionIds = (await SecretVersion.aggregate([
{ {
$match: { $match: {
secret: { secret: {
@@ -43,12 +39,6 @@ const getLatestSecretVersionIds = async ({
]) ])
.exec()); .exec());
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get latest secret versions');
}
return latestSecretVersionIds; return latestSecretVersionIds;
} }
@@ -66,11 +56,8 @@ const getLatestNSecretSecretVersionIds = async ({
secretIds: Types.ObjectId[]; secretIds: Types.ObjectId[];
n: number; n: number;
}) => { }) => {
// TODO: optimize query // TODO: optimize query
let latestNSecretVersions; const latestNSecretVersions = (await SecretVersion.aggregate([
try {
latestNSecretVersions = (await SecretVersion.aggregate([
{ {
$match: { $match: {
secret: { secret: {
@@ -95,11 +82,6 @@ const getLatestNSecretSecretVersionIds = async ({
}, },
} }
])); ]));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get latest n secret versions');
}
return latestNSecretVersions; return latestNSecretVersions;
} }
-1
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
+71 -102
View File
@@ -1,5 +1,4 @@
import * as Sentry from '@sentry/node'; import { Types } from "mongoose";
import { Types } from 'mongoose';
import { import {
Bot, Bot,
BotKey, BotKey,
@@ -10,32 +9,26 @@ import {
IServiceAccount, IServiceAccount,
ServiceAccount, ServiceAccount,
IServiceTokenData, IServiceTokenData,
ServiceTokenData ServiceTokenData,
} from '../models'; } from "../models";
import { import {
generateKeyPair, generateKeyPair,
encryptSymmetric, encryptSymmetric,
decryptSymmetric, decryptSymmetric,
decryptAsymmetric decryptAsymmetric,
} from '../utils/crypto'; } from "../utils/crypto";
import { import {
SECRET_SHARED, SECRET_SHARED,
AUTH_MODE_JWT, AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT, AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY AUTH_MODE_API_KEY,
} from '../variables'; } from "../variables";
import { getEncryptionKey } from '../config'; import { getEncryptionKey } from "../config";
import { BotNotFoundError, UnauthorizedRequestError } from '../utils/errors'; import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
import { import { validateMembership } from "../helpers/membership";
validateMembership import { validateUserClientForWorkspace } from "../helpers/user";
} from '../helpers/membership'; import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount";
import {
validateUserClientForWorkspace
} from '../helpers/user';
import {
validateServiceAccountClientForWorkspace
} from '../helpers/serviceAccount';
/** /**
* Validate authenticated clients for bot with id [botId] based * Validate authenticated clients for bot with id [botId] based
@@ -48,58 +41,70 @@ import {
const validateClientForBot = async ({ const validateClientForBot = async ({
authData, authData,
botId, botId,
acceptedRoles acceptedRoles,
}: { }: {
authData: { authData: {
authMode: string; authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData; authPayload: IUser | IServiceAccount | IServiceTokenData;
}; };
botId: Types.ObjectId; botId: Types.ObjectId;
acceptedRoles: Array<'admin' | 'member'>; acceptedRoles: Array<"admin" | "member">;
}) => { }) => {
const bot = await Bot.findById(botId); const bot = await Bot.findById(botId);
if (!bot) throw BotNotFoundError(); if (!bot) throw BotNotFoundError();
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { if (
authData.authMode === AUTH_MODE_JWT &&
authData.authPayload instanceof User
) {
await validateUserClientForWorkspace({ await validateUserClientForWorkspace({
user: authData.authPayload, user: authData.authPayload,
workspaceId: bot.workspace, workspaceId: bot.workspace,
acceptedRoles acceptedRoles,
}); });
return bot; return bot;
} }
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { if (
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
authData.authPayload instanceof ServiceAccount
) {
await validateServiceAccountClientForWorkspace({ await validateServiceAccountClientForWorkspace({
serviceAccount: authData.authPayload, serviceAccount: authData.authPayload,
workspaceId: bot.workspace workspaceId: bot.workspace,
}); });
return bot; return bot;
} }
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { if (
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
authData.authPayload instanceof ServiceTokenData
) {
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: 'Failed service token authorization for bot' message: "Failed service token authorization for bot",
}); });
} }
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { if (
authData.authMode === AUTH_MODE_API_KEY &&
authData.authPayload instanceof User
) {
await validateUserClientForWorkspace({ await validateUserClientForWorkspace({
user: authData.authPayload, user: authData.authPayload,
workspaceId: bot.workspace, workspaceId: bot.workspace,
acceptedRoles acceptedRoles,
}); });
return bot; return bot;
} }
throw BotNotFoundError({ throw BotNotFoundError({
message: 'Failed client authorization for bot' message: "Failed client authorization for bot",
}); });
} };
/** /**
* Create an inactive bot with name [name] for workspace with id [workspaceId] * Create an inactive bot with name [name] for workspace with id [workspaceId]
@@ -114,31 +119,24 @@ const createBot = async ({
name: string; name: string;
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
}) => { }) => {
let bot;
try {
const { publicKey, privateKey } = generateKeyPair(); const { publicKey, privateKey } = generateKeyPair();
const { ciphertext, iv, tag } = encryptSymmetric({ const { ciphertext, iv, tag } = encryptSymmetric({
plaintext: privateKey, plaintext: privateKey,
key: await getEncryptionKey() key: await getEncryptionKey(),
}); });
bot = await new Bot({ const bot = await new Bot({
name, name,
workspace: workspaceId, workspace: workspaceId,
isActive: false, isActive: false,
publicKey, publicKey,
encryptedPrivateKey: ciphertext, encryptedPrivateKey: ciphertext,
iv, iv,
tag tag,
}).save(); }).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create bot');
}
return bot; return bot;
} };
/** /**
* Return decrypted secrets for workspace with id [workspaceId] * Return decrypted secrets for workspace with id [workspaceId]
@@ -149,18 +147,17 @@ const createBot = async ({
*/ */
const getSecretsHelper = async ({ const getSecretsHelper = async ({
workspaceId, workspaceId,
environment environment,
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
environment: string; environment: string;
}) => { }) => {
const content = {} as any; const content = {} as any;
try { const key = await getKey({ workspaceId: workspaceId.toString() });
const key = await getKey({ workspaceId });
const secrets = await Secret.find({ const secrets = await Secret.find({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
type: SECRET_SHARED type: SECRET_SHARED,
}); });
secrets.forEach((secret: ISecret) => { secrets.forEach((secret: ISecret) => {
@@ -168,26 +165,21 @@ const getSecretsHelper = async ({
ciphertext: secret.secretKeyCiphertext, ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV, iv: secret.secretKeyIV,
tag: secret.secretKeyTag, tag: secret.secretKeyTag,
key key,
}); });
const secretValue = decryptSymmetric({ const secretValue = decryptSymmetric({
ciphertext: secret.secretValueCiphertext, ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV, iv: secret.secretValueIV,
tag: secret.secretValueTag, tag: secret.secretValueTag,
key key,
}); });
content[secretKey] = secretValue; content[secretKey] = secretValue;
}); });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get secrets');
}
return content; return content;
} };
/** /**
* Return bot's copy of the workspace key for workspace * Return bot's copy of the workspace key for workspace
@@ -196,43 +188,36 @@ const getSecretsHelper = async ({
* @param {String} obj.workspaceId - id of workspace * @param {String} obj.workspaceId - id of workspace
* @returns {String} key - decrypted workspace key * @returns {String} key - decrypted workspace key
*/ */
const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => { const getKey = async ({ workspaceId }: { workspaceId: string }) => {
let key;
try {
const botKey = await BotKey.findOne({ const botKey = await BotKey.findOne({
workspace: workspaceId workspace: workspaceId,
}).populate<{ sender: IUser }>('sender', 'publicKey'); }).populate<{ sender: IUser }>("sender", "publicKey");
if (!botKey) throw new Error('Failed to find bot key'); if (!botKey) throw new Error("Failed to find bot key");
const bot = await Bot.findOne({ const bot = await Bot.findOne({
workspace: workspaceId workspace: workspaceId,
}).select('+encryptedPrivateKey +iv +tag'); }).select("+encryptedPrivateKey +iv +tag");
if (!bot) throw new Error('Failed to find bot'); if (!bot) throw new Error("Failed to find bot");
if (!bot.isActive) throw new Error('Bot is not active'); if (!bot.isActive) throw new Error("Bot is not active");
const privateKeyBot = decryptSymmetric({ const privateKeyBot = decryptSymmetric({
ciphertext: bot.encryptedPrivateKey, ciphertext: bot.encryptedPrivateKey,
iv: bot.iv, iv: bot.iv,
tag: bot.tag, tag: bot.tag,
key: await getEncryptionKey() key: await getEncryptionKey(),
}); });
key = decryptAsymmetric({ const key = decryptAsymmetric({
ciphertext: botKey.encryptedKey, ciphertext: botKey.encryptedKey,
nonce: botKey.nonce, nonce: botKey.nonce,
publicKey: botKey.sender.publicKey as string, publicKey: botKey.sender.publicKey as string,
privateKey: privateKeyBot privateKey: privateKeyBot,
}); });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get workspace key');
}
return key; return key;
} };
/** /**
* Return symmetrically encrypted [plaintext] using the * Return symmetrically encrypted [plaintext] using the
@@ -243,30 +228,23 @@ const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
*/ */
const encryptSymmetricHelper = async ({ const encryptSymmetricHelper = async ({
workspaceId, workspaceId,
plaintext plaintext,
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
plaintext: string; plaintext: string;
}) => { }) => {
const key = await getKey({ workspaceId: workspaceId.toString() });
try {
const key = await getKey({ workspaceId });
const { ciphertext, iv, tag } = encryptSymmetric({ const { ciphertext, iv, tag } = encryptSymmetric({
plaintext, plaintext,
key key,
}); });
return ({ return {
ciphertext, ciphertext,
iv, iv,
tag tag,
}); };
} catch (err) { };
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric encryption with bot');
}
}
/** /**
* Return symmetrically decrypted [ciphertext] using the * Return symmetrically decrypted [ciphertext] using the
* key for workspace with id [workspaceId] * key for workspace with id [workspaceId]
@@ -280,37 +258,28 @@ const decryptSymmetricHelper = async ({
workspaceId, workspaceId,
ciphertext, ciphertext,
iv, iv,
tag tag,
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
ciphertext: string; ciphertext: string;
iv: string; iv: string;
tag: string; tag: string;
}) => { }) => {
let plaintext; const key = await getKey({ workspaceId: workspaceId.toString() });
try {
const key = await getKey({ workspaceId });
const plaintext = decryptSymmetric({ const plaintext = decryptSymmetric({
ciphertext, ciphertext,
iv, iv,
tag, tag,
key key,
}); });
return plaintext; return plaintext;
} catch (err) { };
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric decryption with bot');
}
return plaintext;
}
export { export {
validateClientForBot, validateClientForBot,
createBot, createBot,
getSecretsHelper, getSecretsHelper,
encryptSymmetricHelper, encryptSymmetricHelper,
decryptSymmetricHelper decryptSymmetricHelper,
} };
+10 -25
View File
@@ -1,8 +1,7 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import * as Sentry from '@sentry/node'; import { Bot, IBot } from "../models";
import { Bot, IBot } from '../models'; import { EVENT_PUSH_SECRETS } from "../variables";
import { EVENT_PUSH_SECRETS } from '../variables'; import { IntegrationService } from "../services";
import { IntegrationService } from '../services';
interface Event { interface Event {
name: string; name: string;
@@ -19,39 +18,25 @@ interface Event {
* @param {String} obj.event.workspaceId - id of workspace that event is part of * @param {String} obj.event.workspaceId - id of workspace that event is part of
* @param {Object} obj.event.payload - payload of event (depends on event) * @param {Object} obj.event.payload - payload of event (depends on event)
*/ */
const handleEventHelper = async ({ const handleEventHelper = async ({ event }: { event: Event }) => {
event const { workspaceId, environment } = event;
}: {
event: Event;
}) => {
const {
workspaceId,
environment
} = event;
// TODO: moduralize bot check into separate function // TODO: moduralize bot check into separate function
const bot = await Bot.findOne({ const bot = await Bot.findOne({
workspace: workspaceId, workspace: workspaceId,
isActive: true isActive: true,
}); });
if (!bot) return; if (!bot) return;
try {
switch (event.name) { switch (event.name) {
case EVENT_PUSH_SECRETS: case EVENT_PUSH_SECRETS:
IntegrationService.syncIntegrations({ IntegrationService.syncIntegrations({
workspaceId, workspaceId,
environment environment,
}); });
break; break;
} }
} catch (err) { };
Sentry.setUser(null);
Sentry.captureException(err);
}
}
export { export { handleEventHelper };
handleEventHelper
}
+1 -1
View File
@@ -256,7 +256,7 @@ const syncIntegrationsHelper = async ({
integration, integration,
integrationAuth, integrationAuth,
secrets, secrets,
accessId: access.accessId, accessId: access.accessId === undefined ? null : access.accessId,
accessToken: access.accessToken accessToken: access.accessToken
}); });
} }
-7
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Key, IKey } from '../models'; import { Key, IKey } from '../models';
interface Key { interface Key {
@@ -27,7 +26,6 @@ const pushKeys = async ({
workspaceId: string; workspaceId: string;
keys: Key[]; keys: Key[];
}): Promise<void> => { }): Promise<void> => {
try {
// filter out already-inserted keys // filter out already-inserted keys
const keysSet = new Set( const keysSet = new Set(
( (
@@ -52,11 +50,6 @@ const pushKeys = async ({
workspace: workspaceId workspace: workspaceId
})) }))
); );
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to push access keys');
}
}; };
export { pushKeys }; export { pushKeys };
+2 -24
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { import {
MembershipOrg, MembershipOrg,
@@ -144,15 +143,7 @@ const validateMembershipOrg = async ({
* @return {Object} membershipOrg - membership * @return {Object} membershipOrg - membership
*/ */
const findMembershipOrg = (queryObj: any) => { const findMembershipOrg = (queryObj: any) => {
let membershipOrg; const membershipOrg = MembershipOrg.findOne(queryObj);
try {
membershipOrg = MembershipOrg.findOne(queryObj);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to find organization membership');
}
return membershipOrg; return membershipOrg;
}; };
@@ -175,7 +166,6 @@ const addMembershipsOrg = async ({
roles: string[]; roles: string[];
statuses: string[]; statuses: string[];
}) => { }) => {
try {
const operations = userIds.map((userId, idx) => { const operations = userIds.map((userId, idx) => {
return { return {
updateOne: { updateOne: {
@@ -197,11 +187,6 @@ const addMembershipsOrg = async ({
}); });
await MembershipOrg.bulkWrite(operations as any); await MembershipOrg.bulkWrite(operations as any);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to add users to organization');
}
}; };
/** /**
@@ -214,9 +199,7 @@ const deleteMembershipOrg = async ({
}: { }: {
membershipOrgId: string; membershipOrgId: string;
}) => { }) => {
let deletedMembershipOrg; const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
try {
deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
_id: membershipOrgId _id: membershipOrgId
}); });
@@ -246,11 +229,6 @@ const deleteMembershipOrg = async ({
} }
}); });
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to delete organization membership');
}
return deletedMembershipOrg; return deletedMembershipOrg;
}; };
+70 -80
View File
@@ -1,39 +1,34 @@
import * as Sentry from '@sentry/node'; import Stripe from "stripe";
import Stripe from 'stripe'; import { Types } from "mongoose";
import { Types } from 'mongoose';
import { import {
IUser, IUser,
User, User,
IServiceAccount, IServiceAccount,
ServiceAccount, ServiceAccount,
IServiceTokenData, IServiceTokenData,
ServiceTokenData ServiceTokenData,
} from '../models'; } from "../models";
import { Organization, MembershipOrg } from '../models'; import { Organization, MembershipOrg } from "../models";
import { import {
ACCEPTED, ACCEPTED,
AUTH_MODE_JWT, AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT, AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY, AUTH_MODE_API_KEY,
OWNER OWNER,
} from '../variables'; } from "../variables";
import { import {
getStripeSecretKey, getStripeSecretKey,
getStripeProductPro, getStripeProductPro,
getStripeProductTeam, getStripeProductTeam,
getStripeProductStarter getStripeProductStarter,
} from '../config'; } from "../config";
import { import {
UnauthorizedRequestError, UnauthorizedRequestError,
OrganizationNotFoundError OrganizationNotFoundError,
} from '../utils/errors'; } from "../utils/errors";
import { import { validateUserClientForOrganization } from "../helpers/user";
validateUserClientForOrganization import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount";
} from '../helpers/user';
import {
validateServiceAccountClientForOrganization
} from '../helpers/serviceAccount';
/** /**
* Validate accepted clients for organization with id [organizationId] * Validate accepted clients for organization with id [organizationId]
@@ -45,66 +40,77 @@ const validateClientForOrganization = async ({
authData, authData,
organizationId, organizationId,
acceptedRoles, acceptedRoles,
acceptedStatuses acceptedStatuses,
}: { }: {
authData: { authData: {
authMode: string; authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData; authPayload: IUser | IServiceAccount | IServiceTokenData;
}, };
organizationId: Types.ObjectId; organizationId: Types.ObjectId;
acceptedRoles: Array<'owner' | 'admin' | 'member'>; acceptedRoles: Array<"owner" | "admin" | "member">;
acceptedStatuses: Array<'invited' | 'accepted'>; acceptedStatuses: Array<"invited" | "accepted">;
}) => { }) => {
const organization = await Organization.findById(organizationId); const organization = await Organization.findById(organizationId);
if (!organization) { if (!organization) {
throw OrganizationNotFoundError({ throw OrganizationNotFoundError({
message: 'Failed to find organization' message: "Failed to find organization",
}); });
} }
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { if (
authData.authMode === AUTH_MODE_JWT &&
authData.authPayload instanceof User
) {
const membershipOrg = await validateUserClientForOrganization({ const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload, user: authData.authPayload,
organization, organization,
acceptedRoles, acceptedRoles,
acceptedStatuses acceptedStatuses,
}); });
return ({ organization, membershipOrg }); return { organization, membershipOrg };
} }
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { if (
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
authData.authPayload instanceof ServiceAccount
) {
await validateServiceAccountClientForOrganization({ await validateServiceAccountClientForOrganization({
serviceAccount: authData.authPayload, serviceAccount: authData.authPayload,
organization organization,
}); });
return ({ organization }); return { organization };
} }
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { if (
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
authData.authPayload instanceof ServiceTokenData
) {
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: 'Failed service token authorization for organization' message: "Failed service token authorization for organization",
}); });
} }
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { if (
authData.authMode === AUTH_MODE_API_KEY &&
authData.authPayload instanceof User
) {
const membershipOrg = await validateUserClientForOrganization({ const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload, user: authData.authPayload,
organization, organization,
acceptedRoles, acceptedRoles,
acceptedStatuses acceptedStatuses,
}); });
return ({ organization, membershipOrg }); return { organization, membershipOrg };
} }
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: 'Failed client authorization for organization' message: "Failed client authorization for organization",
}); });
} };
/** /**
* Create an organization with name [name] * Create an organization with name [name]
@@ -115,40 +121,34 @@ const validateClientForOrganization = async ({
*/ */
const createOrganization = async ({ const createOrganization = async ({
name, name,
email email,
}: { }: {
name: string; name: string;
email: string; email: string;
}) => { }) => {
let organization; let organization;
try {
// register stripe account // register stripe account
const stripe = new Stripe(await getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: "2022-08-01",
}); });
if (await getStripeSecretKey()) { if (await getStripeSecretKey()) {
const customer = await stripe.customers.create({ const customer = await stripe.customers.create({
email, email,
description: name description: name,
}); });
organization = await new Organization({ organization = await new Organization({
name, name,
customerId: customer.id customerId: customer.id,
}).save(); }).save();
} else { } else {
organization = await new Organization({ organization = await new Organization({
name name,
}).save(); }).save();
} }
await initSubscriptionOrg({ organizationId: organization._id }); await initSubscriptionOrg({ organizationId: organization._id });
} catch (err) {
Sentry.setUser({ email });
Sentry.captureException(err);
throw new Error(`Failed to create organization [err=${err}]`);
}
return organization; return organization;
}; };
@@ -162,56 +162,51 @@ const createOrganization = async ({
* @return {Subscription} obj.subscription - new subscription * @return {Subscription} obj.subscription - new subscription
*/ */
const initSubscriptionOrg = async ({ const initSubscriptionOrg = async ({
organizationId organizationId,
}: { }: {
organizationId: Types.ObjectId; organizationId: Types.ObjectId;
}) => { }) => {
let stripeSubscription; let stripeSubscription;
let subscription; let subscription;
try {
// find organization // find organization
const organization = await Organization.findOne({ const organization = await Organization.findOne({
_id: organizationId _id: organizationId,
}); });
if (organization) { if (organization) {
if (organization.customerId) { if (organization.customerId) {
// initialize starter subscription with quantity of 0 // initialize starter subscription with quantity of 0
const stripe = new Stripe(await getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: "2022-08-01",
}); });
const productToPriceMap = { const productToPriceMap = {
starter: await getStripeProductStarter(), starter: await getStripeProductStarter(),
team: await getStripeProductTeam(), team: await getStripeProductTeam(),
pro: await getStripeProductPro() pro: await getStripeProductPro(),
}; };
stripeSubscription = await stripe.subscriptions.create({ stripeSubscription = await stripe.subscriptions.create({
customer: organization.customerId, customer: organization.customerId,
items: [ items: [
{ {
price: productToPriceMap['starter'], price: productToPriceMap["starter"],
quantity: 1 quantity: 1,
} },
], ],
payment_behavior: 'default_incomplete', payment_behavior: "default_incomplete",
proration_behavior: 'none', proration_behavior: "none",
expand: ['latest_invoice.payment_intent'] expand: ["latest_invoice.payment_intent"],
}); });
} }
} else { } else {
throw new Error('Failed to initialize free organization subscription'); throw new Error("Failed to initialize free organization subscription");
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to initialize free organization subscription');
} }
return { return {
stripeSubscription, stripeSubscription,
subscription subscription,
}; };
}; };
@@ -222,30 +217,29 @@ const initSubscriptionOrg = async ({
* @param {Number} obj.organizationId - id of subscription's organization * @param {Number} obj.organizationId - id of subscription's organization
*/ */
const updateSubscriptionOrgQuantity = async ({ const updateSubscriptionOrgQuantity = async ({
organizationId organizationId,
}: { }: {
organizationId: string; organizationId: string;
}) => { }) => {
let stripeSubscription; let stripeSubscription;
try {
// find organization // find organization
const organization = await Organization.findOne({ const organization = await Organization.findOne({
_id: organizationId _id: organizationId,
}); });
if (organization && organization.customerId) { if (organization && organization.customerId) {
const quantity = await MembershipOrg.countDocuments({ const quantity = await MembershipOrg.countDocuments({
organization: organizationId, organization: organizationId,
status: ACCEPTED status: ACCEPTED,
}); });
const stripe = new Stripe(await getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: "2022-08-01",
}); });
const subscription = ( const subscription = (
await stripe.subscriptions.list({ await stripe.subscriptions.list({
customer: organization.customerId customer: organization.customerId,
}) })
).data[0]; ).data[0];
@@ -254,15 +248,11 @@ const updateSubscriptionOrgQuantity = async ({
{ {
id: subscription.items.data[0].id, id: subscription.items.data[0].id,
price: subscription.items.data[0].price.id, price: subscription.items.data[0].price.id,
quantity quantity,
} },
] ],
}); });
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
}
return stripeSubscription; return stripeSubscription;
}; };
@@ -271,5 +261,5 @@ export {
validateClientForOrganization, validateClientForOrganization,
createOrganization, createOrganization,
initSubscriptionOrg, initSubscriptionOrg,
updateSubscriptionOrgQuantity updateSubscriptionOrgQuantity,
}; };
+135 -168
View File
@@ -1,28 +1,17 @@
import * as Sentry from '@sentry/node'; import { Types } from "mongoose";
import { Types } from 'mongoose'; import { Secret, ISecret, Membership } from "../models";
import { import { EESecretService, EELogService } from "../ee/services";
Secret, import { IAction, SecretVersion } from "../ee/models";
ISecret,
Membership
} from '../models';
import {
EESecretService,
EELogService
} from '../ee/services';
import {
IAction,
SecretVersion
} from '../ee/models';
import { import {
SECRET_SHARED, SECRET_SHARED,
SECRET_PERSONAL, SECRET_PERSONAL,
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS, ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS ACTION_READ_SECRETS,
} from '../variables'; } from "../variables";
import _ from 'lodash'; import _ from "lodash";
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
interface V1PushSecret { interface V1PushSecret {
ciphertextKey: string; ciphertextKey: string;
@@ -37,7 +26,7 @@ interface V1PushSecret {
ivComment: string; ivComment: string;
tagComment: string; tagComment: string;
hashComment: string; hashComment: string;
type: 'shared' | 'personal'; type: "shared" | "personal";
} }
interface V2PushSecret { interface V2PushSecret {
@@ -83,42 +72,47 @@ const v1PushSecrets = async ({
secrets: V1PushSecret[]; secrets: V1PushSecret[];
}): Promise<void> => { }): Promise<void> => {
// TODO: clean up function and fix up types // TODO: clean up function and fix up types
try {
// construct useful data structures // construct useful data structures
const oldSecrets = await getSecrets({ const oldSecrets = await getSecrets({
userId, userId,
workspaceId, workspaceId,
environment environment,
}); });
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) => const oldSecretsObj: any = oldSecrets.reduce(
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s }) (accumulator, s: any) => ({
, {}); ...accumulator,
const newSecretsObj: any = secrets.reduce((accumulator, s) => [`${s.type}-${s.secretKeyHash}`]: s,
({ ...accumulator, [`${s.type}-${s.hashKey}`]: s }) }),
, {}); {}
);
const newSecretsObj: any = secrets.reduce(
(accumulator, s) => ({ ...accumulator, [`${s.type}-${s.hashKey}`]: s }),
{}
);
// handle deleting secrets // handle deleting secrets
const toDelete = oldSecrets const toDelete = oldSecrets
.filter( .filter((s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj))
(s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj)
)
.map((s) => s._id); .map((s) => s._id);
if (toDelete.length > 0) { if (toDelete.length > 0) {
await Secret.deleteMany({ await Secret.deleteMany({
_id: { $in: toDelete } _id: { $in: toDelete },
}); });
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
secretIds: toDelete secretIds: toDelete,
}); });
} }
const toUpdate = oldSecrets const toUpdate = oldSecrets.filter((s) => {
.filter((s) => {
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) { if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue if (
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment) { s.secretValueHash !==
newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue ||
s.secretCommentHash !==
newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment
) {
// case: filter secrets where value or comment changed // case: filter secrets where value or comment changed
return true; return true;
} }
@@ -132,8 +126,7 @@ const v1PushSecrets = async ({
return false; return false;
}); });
const operations = toUpdate const operations = toUpdate.map((s) => {
.map((s) => {
const { const {
ciphertextValue, ciphertextValue,
ivValue, ivValue,
@@ -142,7 +135,7 @@ const v1PushSecrets = async ({
ciphertextComment, ciphertextComment,
ivComment, ivComment,
tagComment, tagComment,
hashComment hashComment,
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`]; } = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
const update: Update = { const update: Update = {
@@ -154,41 +147,36 @@ const v1PushSecrets = async ({
secretCommentIV: ivComment, secretCommentIV: ivComment,
secretCommentTag: tagComment, secretCommentTag: tagComment,
secretCommentHash: hashComment, secretCommentHash: hashComment,
} };
if (!s.version) { if (!s.version) {
// case: (legacy) secret was not versioned // case: (legacy) secret was not versioned
update.version = 1; update.version = 1;
} else { } else {
update['$inc'] = { update["$inc"] = {
version: 1 version: 1,
} };
} }
if (s.type === SECRET_PERSONAL) { if (s.type === SECRET_PERSONAL) {
// attach user associated with the personal secret // attach user associated with the personal secret
update['user'] = userId; update["user"] = userId;
} }
return { return {
updateOne: { updateOne: {
filter: { filter: {
_id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id _id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id,
},
update,
}, },
update
}
}; };
}); });
await Secret.bulkWrite(operations as any); await Secret.bulkWrite(operations as any);
// (EE) add secret versions for updated secrets // (EE) add secret versions for updated secrets
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: toUpdate.map(({ secretVersions: toUpdate.map(({ _id, version, type, secretKeyHash }) => {
_id,
version,
type,
secretKeyHash,
}) => {
const newSecret = newSecretsObj[`${type}-${secretKeyHash}`]; const newSecret = newSecretsObj[`${type}-${secretKeyHash}`];
return new SecretVersion({ return new SecretVersion({
secret: _id, secret: _id,
@@ -205,17 +193,20 @@ const v1PushSecrets = async ({
secretValueCiphertext: newSecret.ciphertextValue, secretValueCiphertext: newSecret.ciphertextValue,
secretValueIV: newSecret.ivValue, secretValueIV: newSecret.ivValue,
secretValueTag: newSecret.tagValue, secretValueTag: newSecret.tagValue,
secretValueHash: newSecret.hashValue secretValueHash: newSecret.hashValue,
}) });
}) }),
}); });
// handle adding new secrets // handle adding new secrets
const toAdd = secrets.filter((s) => !(`${s.type}-${s.hashKey}` in oldSecretsObj)); const toAdd = secrets.filter(
(s) => !(`${s.type}-${s.hashKey}` in oldSecretsObj)
);
if (toAdd.length > 0) { if (toAdd.length > 0) {
// add secrets // add secrets
const newSecrets: ISecret[] = (await Secret.insertMany( const newSecrets: ISecret[] = (
await Secret.insertMany(
toAdd.map((s, idx) => { toAdd.map((s, idx) => {
const obj: any = { const obj: any = {
version: 1, version: 1,
@@ -233,20 +224,22 @@ const v1PushSecrets = async ({
secretCommentCiphertext: s.ciphertextComment, secretCommentCiphertext: s.ciphertextComment,
secretCommentIV: s.ivComment, secretCommentIV: s.ivComment,
secretCommentTag: s.tagComment, secretCommentTag: s.tagComment,
secretCommentHash: s.hashComment secretCommentHash: s.hashComment,
}; };
if (toAdd[idx].type === 'personal') { if (toAdd[idx].type === "personal") {
obj['user' as keyof typeof obj] = userId; obj["user" as keyof typeof obj] = userId;
} }
return obj; return obj;
}) })
)).map((insertedSecret) => insertedSecret.toObject()); )
).map((insertedSecret) => insertedSecret.toObject());
// (EE) add secret versions for new secrets // (EE) add secret versions for new secrets
EESecretService.addSecretVersions({ EESecretService.addSecretVersions({
secretVersions: newSecrets.map(({ secretVersions: newSecrets.map(
({
_id, _id,
version, version,
workspace, workspace,
@@ -260,8 +253,9 @@ const v1PushSecrets = async ({
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
secretValueHash secretValueHash,
}) => new SecretVersion({ }) =>
new SecretVersion({
secret: _id, secret: _id,
version, version,
workspace, workspace,
@@ -276,20 +270,16 @@ const v1PushSecrets = async ({
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
secretValueHash secretValueHash,
})) })
),
}); });
} }
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to push shared and personal secrets');
}
}; };
/** /**
@@ -311,7 +301,7 @@ const v2PushSecrets = async ({
environment, environment,
secrets, secrets,
channel, channel,
ipAddress ipAddress,
}: { }: {
userId: string; userId: string;
workspaceId: string; workspaceId: string;
@@ -321,53 +311,61 @@ const v2PushSecrets = async ({
ipAddress: string; ipAddress: string;
}): Promise<void> => { }): Promise<void> => {
// TODO: clean up function and fix up types // TODO: clean up function and fix up types
try {
const actions: IAction[] = []; const actions: IAction[] = [];
// construct useful data structures // construct useful data structures
const oldSecrets = await getSecrets({ const oldSecrets = await getSecrets({
userId, userId,
workspaceId, workspaceId,
environment environment,
}); });
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) => const oldSecretsObj: any = oldSecrets.reduce(
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s }) (accumulator, s: any) => ({
, {}); ...accumulator,
const newSecretsObj: any = secrets.reduce((accumulator, s) => [`${s.type}-${s.secretKeyHash}`]: s,
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s }) }),
, {}); {}
);
const newSecretsObj: any = secrets.reduce(
(accumulator, s) => ({
...accumulator,
[`${s.type}-${s.secretKeyHash}`]: s,
}),
{}
);
// handle deleting secrets // handle deleting secrets
const toDelete = oldSecrets const toDelete = oldSecrets
.filter( .filter((s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj))
(s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj)
)
.map((s) => s._id); .map((s) => s._id);
if (toDelete.length > 0) { if (toDelete.length > 0) {
await Secret.deleteMany({ await Secret.deleteMany({
_id: { $in: toDelete } _id: { $in: toDelete },
}); });
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
secretIds: toDelete secretIds: toDelete,
}); });
const deleteAction = await EELogService.createAction({ const deleteAction = await EELogService.createAction({
name: ACTION_DELETE_SECRETS, name: ACTION_DELETE_SECRETS,
userId: new Types.ObjectId(userId), userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(userId), workspaceId: new Types.ObjectId(userId),
secretIds: toDelete secretIds: toDelete,
}); });
deleteAction && actions.push(deleteAction); deleteAction && actions.push(deleteAction);
} }
const toUpdate = oldSecrets const toUpdate = oldSecrets.filter((s) => {
.filter((s) => {
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) { if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash if (
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash) { s.secretValueHash !==
newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash ||
s.secretCommentHash !==
newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash
) {
// case: filter secrets where value or comment changed // case: filter secrets where value or comment changed
return true; return true;
} }
@@ -382,8 +380,7 @@ const v2PushSecrets = async ({
}); });
if (toUpdate.length > 0) { if (toUpdate.length > 0) {
const operations = toUpdate const operations = toUpdate.map((s) => {
.map((s) => {
const { const {
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
@@ -404,29 +401,29 @@ const v2PushSecrets = async ({
secretCommentIV, secretCommentIV,
secretCommentTag, secretCommentTag,
secretCommentHash, secretCommentHash,
} };
if (!s.version) { if (!s.version) {
// case: (legacy) secret was not versioned // case: (legacy) secret was not versioned
update.version = 1; update.version = 1;
} else { } else {
update['$inc'] = { update["$inc"] = {
version: 1 version: 1,
} };
} }
if (s.type === SECRET_PERSONAL) { if (s.type === SECRET_PERSONAL) {
// attach user associated with the personal secret // attach user associated with the personal secret
update['user'] = userId; update["user"] = userId;
} }
return { return {
updateOne: { updateOne: {
filter: { filter: {
_id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id _id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id,
},
update,
}, },
update
}
}; };
}); });
await Secret.bulkWrite(operations as any); await Secret.bulkWrite(operations as any);
@@ -434,30 +431,32 @@ const v2PushSecrets = async ({
// (EE) add secret versions for updated secrets // (EE) add secret versions for updated secrets
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: toUpdate.map((s) => { secretVersions: toUpdate.map((s) => {
return ({ return {
...newSecretsObj[`${s.type}-${s.secretKeyHash}`], ...newSecretsObj[`${s.type}-${s.secretKeyHash}`],
secret: s._id, secret: s._id,
version: s.version ? s.version + 1 : 1, version: s.version ? s.version + 1 : 1,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
user: s.user, user: s.user,
environment: s.environment, environment: s.environment,
isDeleted: false isDeleted: false,
}) };
}) }),
}); });
const updateAction = await EELogService.createAction({ const updateAction = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS, name: ACTION_UPDATE_SECRETS,
userId: new Types.ObjectId(userId), userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: toUpdate.map((u) => u._id) secretIds: toUpdate.map((u) => u._id),
}); });
updateAction && actions.push(updateAction); updateAction && actions.push(updateAction);
} }
// handle adding new secrets // handle adding new secrets
const toAdd = secrets.filter((s) => !(`${s.type}-${s.secretKeyHash}` in oldSecretsObj)); const toAdd = secrets.filter(
(s) => !(`${s.type}-${s.secretKeyHash}` in oldSecretsObj)
);
if (toAdd.length > 0) { if (toAdd.length > 0) {
// add secrets // add secrets
@@ -468,7 +467,7 @@ const v2PushSecrets = async ({
workspace: workspaceId, workspace: workspaceId,
type: toAdd[idx].type, type: toAdd[idx].type,
environment, environment,
...(toAdd[idx].type === 'personal' ? { user: userId } : {}) ...(toAdd[idx].type === "personal" ? { user: userId } : {}),
})) }))
); );
@@ -478,24 +477,24 @@ const v2PushSecrets = async ({
return new SecretVersion({ return new SecretVersion({
...secretDocument, ...secretDocument,
secret: secretDocument._id, secret: secretDocument._id,
isDeleted: false isDeleted: false,
}) });
}) }),
}); });
const addAction = await EELogService.createAction({ const addAction = await EELogService.createAction({
name: ACTION_ADD_SECRETS, name: ACTION_ADD_SECRETS,
userId: new Types.ObjectId(userId), userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: newSecrets.map((n) => n._id) secretIds: newSecrets.map((n) => n._id),
}); });
addAction && actions.push(addAction); addAction && actions.push(addAction);
} }
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}) });
// (EE) create (audit) log // (EE) create (audit) log
if (actions.length > 0) { if (actions.length > 0) {
@@ -504,14 +503,9 @@ const v2PushSecrets = async ({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
actions, actions,
channel, channel,
ipAddress ipAddress,
}); });
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to push shared and personal secrets');
}
}; };
/** /**
@@ -525,20 +519,17 @@ const v2PushSecrets = async ({
const getSecrets = async ({ const getSecrets = async ({
userId, userId,
workspaceId, workspaceId,
environment environment,
}: { }: {
userId: string; userId: string;
workspaceId: string; workspaceId: string;
environment: string; environment: string;
}): Promise<ISecret[]> => { }): Promise<ISecret[]> => {
let secrets: any; // TODO: FIX any
try {
// get shared workspace secrets // get shared workspace secrets
const sharedSecrets = await Secret.find({ const sharedSecrets = await Secret.find({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
type: SECRET_SHARED type: SECRET_SHARED,
}); });
// get personal workspace secrets // get personal workspace secrets
@@ -546,16 +537,11 @@ const getSecrets = async ({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
type: SECRET_PERSONAL, type: SECRET_PERSONAL,
user: userId user: userId,
}); });
// concat shared and personal workspace secrets // concat shared and personal workspace secrets
secrets = personalSecrets.concat(sharedSecrets); const secrets = personalSecrets.concat(sharedSecrets);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to pull shared and personal secrets');
}
return secrets; return secrets;
}; };
@@ -575,7 +561,7 @@ const pullSecrets = async ({
workspaceId, workspaceId,
environment, environment,
channel, channel,
ipAddress ipAddress,
}: { }: {
userId: string; userId: string;
workspaceId: string; workspaceId: string;
@@ -583,34 +569,27 @@ const pullSecrets = async ({
channel: string; channel: string;
ipAddress: string; ipAddress: string;
}): Promise<ISecret[]> => { }): Promise<ISecret[]> => {
let secrets: any; const secrets = await getSecrets({
try {
secrets = await getSecrets({
userId, userId,
workspaceId, workspaceId,
environment environment,
}) });
const readAction = await EELogService.createAction({ const readAction = await EELogService.createAction({
name: ACTION_READ_SECRETS, name: ACTION_READ_SECRETS,
userId: new Types.ObjectId(userId), userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: secrets.map((n: any) => n._id) secretIds: secrets.map((n: any) => n._id),
}); });
readAction && await EELogService.createLog({ readAction &&
(await EELogService.createLog({
userId: new Types.ObjectId(userId), userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
actions: [readAction], actions: [readAction],
channel, channel,
ipAddress ipAddress,
}); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to pull shared and personal secrets');
}
return secrets; return secrets;
}; };
@@ -622,9 +601,7 @@ const pullSecrets = async ({
* @param {Object} obj.secrets * @param {Object} obj.secrets
*/ */
const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => { const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
let reformatedSecrets; const reformatedSecrets = secrets.map((s) => ({
try {
reformatedSecrets = secrets.map((s) => ({
_id: s._id, _id: s._id,
workspace: s.workspace, workspace: s.workspace,
type: s.type, type: s.type,
@@ -634,35 +611,25 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
ciphertext: s.secretKeyCiphertext, ciphertext: s.secretKeyCiphertext,
iv: s.secretKeyIV, iv: s.secretKeyIV,
tag: s.secretKeyTag, tag: s.secretKeyTag,
hash: s.secretKeyHash hash: s.secretKeyHash,
}, },
secretValue: { secretValue: {
workspace: s.workspace, workspace: s.workspace,
ciphertext: s.secretValueCiphertext, ciphertext: s.secretValueCiphertext,
iv: s.secretValueIV, iv: s.secretValueIV,
tag: s.secretValueTag, tag: s.secretValueTag,
hash: s.secretValueHash hash: s.secretValueHash,
}, },
secretComment: { secretComment: {
workspace: s.workspace, workspace: s.workspace,
ciphertext: s.secretCommentCiphertext, ciphertext: s.secretCommentCiphertext,
iv: s.secretCommentIV, iv: s.secretCommentIV,
tag: s.secretCommentTag, tag: s.secretCommentTag,
hash: s.secretCommentHash hash: s.secretCommentHash,
} },
})); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to reformat pulled secrets');
}
return reformatedSecrets; return reformatedSecrets;
}; };
export { export { v1PushSecrets, v2PushSecrets, pullSecrets, reformatPullSecrets };
v1PushSecrets,
v2PushSecrets,
pullSecrets,
reformatPullSecrets
};
+72 -70
View File
@@ -1,16 +1,15 @@
import * as Sentry from '@sentry/node'; import { Types } from "mongoose";
import { Types } from 'mongoose'; import { TokenData } from "../models";
import { TokenData } from '../models'; import crypto from "crypto";
import crypto from 'crypto'; import bcrypt from "bcrypt";
import bcrypt from 'bcrypt';
import { import {
TOKEN_EMAIL_CONFIRMATION, TOKEN_EMAIL_CONFIRMATION,
TOKEN_EMAIL_MFA, TOKEN_EMAIL_MFA,
TOKEN_EMAIL_ORG_INVITATION, TOKEN_EMAIL_ORG_INVITATION,
TOKEN_EMAIL_PASSWORD_RESET TOKEN_EMAIL_PASSWORD_RESET,
} from '../variables'; } from "../variables";
import { UnauthorizedRequestError } from '../utils/errors'; import { UnauthorizedRequestError } from "../utils/errors";
import { getSaltRounds } from '../config'; import { getSaltRounds } from "../config";
/** /**
* Create and store a token in the database for purpose [type] * Create and store a token in the database for purpose [type]
@@ -25,41 +24,44 @@ const createTokenHelper = async ({
type, type,
email, email,
phoneNumber, phoneNumber,
organizationId organizationId,
}: { }: {
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset'; type:
| "emailConfirmation"
| "emailMfa"
| "organizationInvitation"
| "passwordReset";
email?: string; email?: string;
phoneNumber?: string; phoneNumber?: string;
organizationId?: Types.ObjectId organizationId?: Types.ObjectId;
}) => { }) => {
let token, expiresAt, triesLeft; let token, expiresAt, triesLeft;
try {
// generate random token based on specified token use-case // generate random token based on specified token use-case
// type [type] // type [type]
switch (type) { switch (type) {
case TOKEN_EMAIL_CONFIRMATION: case TOKEN_EMAIL_CONFIRMATION:
// generate random 6-digit code // generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1)); token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
expiresAt = new Date((new Date()).getTime() + 86400000); expiresAt = new Date(new Date().getTime() + 86400000);
break; break;
case TOKEN_EMAIL_MFA: case TOKEN_EMAIL_MFA:
// generate random 6-digit code // generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1)); token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
triesLeft = 5; triesLeft = 5;
expiresAt = new Date((new Date()).getTime() + 300000); expiresAt = new Date(new Date().getTime() + 300000);
break; break;
case TOKEN_EMAIL_ORG_INVITATION: case TOKEN_EMAIL_ORG_INVITATION:
// generate random hex // generate random hex
token = crypto.randomBytes(16).toString('hex'); token = crypto.randomBytes(16).toString("hex");
expiresAt = new Date((new Date()).getTime() + 259200000); expiresAt = new Date(new Date().getTime() + 259200000);
break; break;
case TOKEN_EMAIL_PASSWORD_RESET: case TOKEN_EMAIL_PASSWORD_RESET:
// generate random hex // generate random hex
token = crypto.randomBytes(16).toString('hex'); token = crypto.randomBytes(16).toString("hex");
expiresAt = new Date((new Date()).getTime() + 86400000); expiresAt = new Date(new Date().getTime() + 86400000);
break; break;
default: default:
token = crypto.randomBytes(16).toString('hex'); token = crypto.randomBytes(16).toString("hex");
expiresAt = new Date(); expiresAt = new Date();
break; break;
} }
@@ -85,8 +87,8 @@ const createTokenHelper = async ({
const update: TokenDataUpdate = { const update: TokenDataUpdate = {
type, type,
tokenHash: await bcrypt.hash(token, await getSaltRounds()), tokenHash: await bcrypt.hash(token, await getSaltRounds()),
expiresAt expiresAt,
} };
if (email) { if (email) {
query.email = email; query.email = email;
@@ -97,32 +99,21 @@ const createTokenHelper = async ({
update.phoneNumber = phoneNumber; update.phoneNumber = phoneNumber;
} }
if (organizationId) { if (organizationId) {
query.organization = organizationId query.organization = organizationId;
update.organization = organizationId update.organization = organizationId;
} }
if (triesLeft) { if (triesLeft) {
update.triesLeft = triesLeft; update.triesLeft = triesLeft;
} }
await TokenData.findOneAndUpdate( await TokenData.findOneAndUpdate(query, update, {
query,
update,
{
new: true, new: true,
upsert: true upsert: true,
} });
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(
"Failed to create token"
);
}
return token; return token;
} };
/** /**
* *
@@ -135,9 +126,13 @@ const validateTokenHelper = async ({
email, email,
phoneNumber, phoneNumber,
organizationId, organizationId,
token token,
}: { }: {
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset'; type:
| "emailConfirmation"
| "emailMfa"
| "organizationInvitation"
| "passwordReset";
email?: string; email?: string;
phoneNumber?: string; phoneNumber?: string;
organizationId?: Types.ObjectId; organizationId?: Types.ObjectId;
@@ -152,22 +147,28 @@ const validateTokenHelper = async ({
const query: Query = { type }; const query: Query = { type };
if (email) { query.email = email; } if (email) {
if (phoneNumber) { query.phoneNumber = phoneNumber; } query.email = email;
if (organizationId) { query.organization = organizationId; } }
if (phoneNumber) {
query.phoneNumber = phoneNumber;
}
if (organizationId) {
query.organization = organizationId;
}
const tokenData = await TokenData.findOne(query).select('+tokenHash'); const tokenData = await TokenData.findOne(query).select("+tokenHash");
if (!tokenData) throw new Error('Failed to find token to validate'); if (!tokenData) throw new Error("Failed to find token to validate");
if (tokenData.expiresAt < new Date()) { if (tokenData.expiresAt < new Date()) {
// case: token expired // case: token expired
await TokenData.findByIdAndDelete(tokenData._id); await TokenData.findByIdAndDelete(tokenData._id);
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: 'MFA session expired. Please log in again', message: "MFA session expired. Please log in again",
context: { context: {
code: 'mfa_expired' code: "mfa_expired",
} },
}); });
} }
@@ -181,35 +182,36 @@ const validateTokenHelper = async ({
await TokenData.findByIdAndDelete(tokenData._id); await TokenData.findByIdAndDelete(tokenData._id);
} else { } else {
// case: token has more than 1 try left // case: token has more than 1 try left
await TokenData.findByIdAndUpdate(tokenData._id, { await TokenData.findByIdAndUpdate(
triesLeft: tokenData.triesLeft - 1 tokenData._id,
}, { {
new: true triesLeft: tokenData.triesLeft - 1,
},
{
new: true,
}
);
}
throw UnauthorizedRequestError({
message: "MFA code is invalid",
context: {
code: "mfa_invalid",
triesLeft: tokenData.triesLeft - 1,
},
}); });
} }
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: 'MFA code is invalid', message: "MFA code is invalid",
context: { context: {
code: 'mfa_invalid', code: "mfa_invalid",
triesLeft: tokenData.triesLeft - 1 },
}
});
}
throw UnauthorizedRequestError({
message: 'MFA code is invalid',
context: {
code: 'mfa_invalid'
}
}); });
} }
// case: token is valid // case: token is valid
await TokenData.findByIdAndDelete(tokenData._id); await TokenData.findByIdAndDelete(tokenData._id);
} };
export { export { createTokenHelper, validateTokenHelper };
createTokenHelper,
validateTokenHelper
}
+2 -17
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { import {
IUser, IUser,
@@ -28,16 +27,9 @@ import {
* @returns {Object} user - the initialized user * @returns {Object} user - the initialized user
*/ */
const setupAccount = async ({ email }: { email: string }) => { const setupAccount = async ({ email }: { email: string }) => {
let user; const user = await new User({
try {
user = await new User({
email email
}).save(); }).save();
} catch (err) {
Sentry.setUser({ email });
Sentry.captureException(err);
throw new Error('Failed to set up account');
}
return user; return user;
}; };
@@ -89,12 +81,10 @@ const completeAccount = async ({
salt: string; salt: string;
verifier: string; verifier: string;
}) => { }) => {
let user;
try {
const options = { const options = {
new: true new: true
}; };
user = await User.findByIdAndUpdate( const user = await User.findByIdAndUpdate(
userId, userId,
{ {
firstName, firstName,
@@ -112,11 +102,6 @@ const completeAccount = async ({
}, },
options options
); );
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to complete account set up');
}
return user; return user;
}; };
+87 -169
View File
@@ -1,7 +1,6 @@
import * as Sentry from "@sentry/node";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import { IIntegrationAuth } from "../models"; import { IIntegrationAuth } from "../models";
import request from '../config/request'; import request from "../config/request";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE, INTEGRATION_AWS_PARAMETER_STORE,
@@ -26,7 +25,7 @@ import {
INTEGRATION_FLYIO_API_URL, INTEGRATION_FLYIO_API_URL,
INTEGRATION_CIRCLECI_API_URL, INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_TRAVISCI_API_URL, INTEGRATION_TRAVISCI_API_URL,
INTEGRATION_SUPABASE_API_URL INTEGRATION_SUPABASE_API_URL,
} from "../variables"; } from "../variables";
interface App { interface App {
@@ -47,15 +46,13 @@ interface App {
const getApps = async ({ const getApps = async ({
integrationAuth, integrationAuth,
accessToken, accessToken,
teamId teamId,
}: { }: {
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
accessToken: string; accessToken: string;
teamId?: string; teamId?: string;
}) => { }) => {
let apps: App[] = []; let apps: App[] = [];
try {
switch (integrationAuth.integration) { switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
apps = []; apps = [];
@@ -90,7 +87,7 @@ const getApps = async ({
case INTEGRATION_GITLAB: case INTEGRATION_GITLAB:
apps = await getAppsGitlab({ apps = await getAppsGitlab({
accessToken, accessToken,
teamId teamId,
}); });
break; break;
case INTEGRATION_RENDER: case INTEGRATION_RENDER:
@@ -100,7 +97,7 @@ const getApps = async ({
break; break;
case INTEGRATION_RAILWAY: case INTEGRATION_RAILWAY:
apps = await getAppsRailway({ apps = await getAppsRailway({
accessToken accessToken,
}); });
break; break;
case INTEGRATION_FLYIO: case INTEGRATION_FLYIO:
@@ -116,19 +113,14 @@ const getApps = async ({
case INTEGRATION_TRAVISCI: case INTEGRATION_TRAVISCI:
apps = await getAppsTravisCI({ apps = await getAppsTravisCI({
accessToken, accessToken,
}) });
break; break;
case INTEGRATION_SUPABASE: case INTEGRATION_SUPABASE:
apps = await getAppsSupabase({ apps = await getAppsSupabase({
accessToken accessToken,
}); });
break; break;
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get integration apps");
}
return apps; return apps;
}; };
@@ -141,8 +133,6 @@ const getApps = async ({
* @returns {String} apps.name - name of Heroku app * @returns {String} apps.name - name of Heroku app
*/ */
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => { const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
let apps;
try {
const res = ( const res = (
await request.get(`${INTEGRATION_HEROKU_API_URL}/apps`, { await request.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: { headers: {
@@ -152,14 +142,9 @@ const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
}) })
).data; ).data;
apps = res.map((a: any) => ({ const apps = res.map((a: any) => ({
name: a.name, name: a.name,
})); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Heroku integration apps");
}
return apps; return apps;
}; };
@@ -178,13 +163,11 @@ const getAppsVercel = async ({
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
accessToken: string; accessToken: string;
}) => { }) => {
let apps;
try {
const res = ( const res = (
await request.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, { await request.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json' "Accept-Encoding": "application/json",
}, },
...(integrationAuth?.teamId ...(integrationAuth?.teamId
? { ? {
@@ -196,15 +179,10 @@ const getAppsVercel = async ({
}) })
).data; ).data;
apps = res.projects.map((a: any) => ({ const apps = res.projects.map((a: any) => ({
name: a.name, name: a.name,
appId: a.id appId: a.id,
})); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Vercel integration apps");
}
return apps; return apps;
}; };
@@ -218,7 +196,6 @@ const getAppsVercel = async ({
*/ */
const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => { const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
const apps: any = []; const apps: any = [];
try {
let page = 1; let page = 1;
const perPage = 10; const perPage = 10;
let hasMorePages = true; let hasMorePages = true;
@@ -227,21 +204,24 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
while (hasMorePages) { while (hasMorePages) {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage) per_page: String(perPage),
}); });
const { data } = await request.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, { const { data } = await request.get(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`,
{
params, params,
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json' "Accept-Encoding": "application/json",
},
} }
}); );
data.map((a: any) => { data.map((a: any) => {
apps.push({ apps.push({
name: a.name, name: a.name,
appId: a.site_id appId: a.site_id,
}); });
}); });
@@ -251,11 +231,6 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
page++; page++;
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Netlify integration apps");
}
return apps; return apps;
}; };
@@ -268,8 +243,6 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of Github site * @returns {String} apps.name - name of Github site
*/ */
const getAppsGithub = async ({ accessToken }: { accessToken: string }) => { const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
let apps;
try {
interface GitHubApp { interface GitHubApp {
id: string; id: string;
name: string; name: string;
@@ -278,7 +251,7 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
}; };
owner: { owner: {
login: string; login: string;
} };
} }
const octokit = new Octokit({ const octokit = new Octokit({
@@ -313,7 +286,7 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
const repos = await getAllRepos(); const repos = await getAllRepos();
apps = repos const apps = repos
.filter((a: GitHubApp) => a.permissions.admin === true) .filter((a: GitHubApp) => a.permissions.admin === true)
.map((a: GitHubApp) => { .map((a: GitHubApp) => {
return { return {
@@ -323,12 +296,6 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
}; };
}); });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Github repos");
}
return apps; return apps;
}; };
@@ -341,30 +308,21 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.appId - id of Render service * @returns {String} apps.appId - id of Render service
*/ */
const getAppsRender = async ({ accessToken }: { accessToken: string }) => { const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
let apps: any;
try {
const res = ( const res = (
await request.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, { await request.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: 'application/json', Accept: "application/json",
'Accept-Encoding': 'application/json', "Accept-Encoding": "application/json",
}, },
}) })
).data; ).data;
apps = res const apps = res.map((a: any) => ({
.map((a: any) => ({
name: a.service.name, name: a.service.name,
appId: a.service.id appId: a.service.id,
})); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Render services");
}
return apps; return apps;
}; };
@@ -376,10 +334,8 @@ const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of Railway project * @returns {String} apps.name - name of Railway project
* @returns {String} apps.appId - id of Railway project * @returns {String} apps.appId - id of Railway project
* *
*/ */
const getAppsRailway = async ({ accessToken }: { accessToken: string }) => { const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
let apps: any[] = [];
try {
const query = ` const query = `
query GetProjects($userId: String, $teamId: String) { query GetProjects($userId: String, $teamId: String) {
projects(userId: $userId, teamId: $teamId) { projects(userId: $userId, teamId: $teamId) {
@@ -395,30 +351,34 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
const variables = {}; const variables = {};
const { data: { data: { projects: { edges }}} } = await request.post(INTEGRATION_RAILWAY_API_URL, { const {
data: {
data: {
projects: { edges },
},
},
} = await request.post(
INTEGRATION_RAILWAY_API_URL,
{
query, query,
variables, variables,
}, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Accept-Encoding': 'application/json'
}, },
}); {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept-Encoding": "application/json",
},
}
);
apps = edges.map((e: any) => ({ const apps = edges.map((e: any) => ({
name: e.node.name, name: e.node.name,
appId: e.node.id appId: e.node.id,
})); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Railway services");
}
return apps; return apps;
} };
/** /**
* Return list of apps for Fly.io integration * Return list of apps for Fly.io integration
@@ -428,8 +388,6 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of Fly.io apps * @returns {String} apps.name - name of Fly.io apps
*/ */
const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => { const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
let apps;
try {
const query = ` const query = `
query($role: String) { query($role: String) {
apps(type: "container", first: 400, role: $role) { apps(type: "container", first: 400, role: $role) {
@@ -442,27 +400,28 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
} }
`; `;
const res = (await request.post(INTEGRATION_FLYIO_API_URL, { const res = (
await request.post(
INTEGRATION_FLYIO_API_URL,
{
query, query,
variables: { variables: {
role: null, role: null,
}, },
}, { },
{
headers: { headers: {
Authorization: "Bearer " + accessToken, Authorization: "Bearer " + accessToken,
'Accept': 'application/json', Accept: "application/json",
'Accept-Encoding': 'application/json', "Accept-Encoding": "application/json",
}, },
})).data.data.apps.nodes; }
)
).data.data.apps.nodes;
apps = res.map((a: any) => ({ const apps = res.map((a: any) => ({
name: a.name, name: a.name,
})); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Fly.io apps");
}
return apps; return apps;
}; };
@@ -475,63 +434,43 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of CircleCI apps * @returns {String} apps.name - name of CircleCI apps
*/ */
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => { const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
let apps: any;
try {
const res = ( const res = (
await request.get( await request.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, {
`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`,
{
headers: { headers: {
"Circle-Token": accessToken, "Circle-Token": accessToken,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
}, },
} })
) ).data;
).data
apps = res?.map((a: any) => { const apps = res?.map((a: any) => {
return { return {
name: a?.reponame name: a?.reponame,
} };
}); });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get CircleCI projects");
}
return apps; return apps;
}; };
const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => { const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
let apps: any;
try {
const res = ( const res = (
await request.get( await request.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, {
`${INTEGRATION_TRAVISCI_API_URL}/repos`,
{
headers: { headers: {
"Authorization": `token ${accessToken}`, Authorization: `token ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
}, },
} })
)
).data; ).data;
apps = res?.map((a: any) => { const apps = res?.map((a: any) => {
return { return {
name: a?.slug?.split("/")[1], name: a?.slug?.split("/")[1],
appId: a?.id, appId: a?.id,
} };
}); });
}catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get TravisCI projects");
}
return apps; return apps;
} };
/** /**
* Return list of repositories for GitLab integration * Return list of repositories for GitLab integration
@@ -542,7 +481,7 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
*/ */
const getAppsGitlab = async ({ const getAppsGitlab = async ({
accessToken, accessToken,
teamId teamId,
}: { }: {
accessToken: string; accessToken: string;
teamId?: string; teamId?: string;
@@ -552,7 +491,6 @@ const getAppsGitlab = async ({
let page = 1; let page = 1;
const perPage = 10; const perPage = 10;
let hasMorePages = true; let hasMorePages = true;
try {
if (teamId) { if (teamId) {
// case: fetch projects for group with id [teamId] in GitLab // case: fetch projects for group with id [teamId] in GitLab
@@ -560,26 +498,24 @@ const getAppsGitlab = async ({
while (hasMorePages) { while (hasMorePages) {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage) per_page: String(perPage),
}); });
const { data } = ( const { data } = await request.get(
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`, `${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
{ {
params, params,
headers: { headers: {
"Authorization": `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
}, },
} }
)
); );
data.map((a: any) => { data.map((a: any) => {
apps.push({ apps.push({
name: a.name, name: a.name,
appId: a.id appId: a.id,
}); });
}); });
@@ -593,40 +529,35 @@ const getAppsGitlab = async ({
// case: fetch projects for individual in GitLab // case: fetch projects for individual in GitLab
const { id } = ( const { id } = (
await request.get( await request.get(`${INTEGRATION_GITLAB_API_URL}/v4/user`, {
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
{
headers: { headers: {
"Authorization": `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
}, },
} })
)
).data; ).data;
while (hasMorePages) { while (hasMorePages) {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage) per_page: String(perPage),
}); });
const { data } = ( const { data } = await request.get(
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`, `${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
{ {
params, params,
headers: { headers: {
"Authorization": `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
}, },
} }
)
); );
data.map((a: any) => { data.map((a: any) => {
apps.push({ apps.push({
name: a.name, name: a.name,
appId: a.id appId: a.id,
}); });
}); });
@@ -637,15 +568,9 @@ const getAppsGitlab = async ({
page++; page++;
} }
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get GitLab projects");
}
return apps; return apps;
} };
/** /**
* Return list of projects for Supabase integration * Return list of projects for Supabase integration
@@ -655,29 +580,22 @@ const getAppsGitlab = async ({
* @returns {String} apps.name - name of Supabase app * @returns {String} apps.name - name of Supabase app
*/ */
const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => { const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
let apps: any;
try {
const { data } = await request.get( const { data } = await request.get(
`${INTEGRATION_SUPABASE_API_URL}/v1/projects`, `${INTEGRATION_SUPABASE_API_URL}/v1/projects`,
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json' "Accept-Encoding": "application/json",
} },
} }
); );
apps = data.map((a: any) => { const apps = data.map((a: any) => {
return { return {
name: a.name, name: a.name,
appId: a.id appId: a.id,
}; };
}); });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Supabase projects');
}
return apps; return apps;
}; };
+66 -126
View File
@@ -1,5 +1,4 @@
import * as Sentry from '@sentry/node'; import request from "../config/request";
import request from '../config/request';
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
@@ -12,8 +11,8 @@ import {
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL INTEGRATION_GITLAB_TOKEN_URL,
} from '../variables'; } from "../variables";
import { import {
getSiteURL, getSiteURL,
getClientIdAzure, getClientIdAzure,
@@ -26,8 +25,8 @@ import {
getClientIdGitHub, getClientIdGitHub,
getClientSecretGitHub, getClientSecretGitHub,
getClientIdGitLab, getClientIdGitLab,
getClientSecretGitLab getClientSecretGitLab,
} from '../config'; } from "../config";
interface ExchangeCodeAzureResponse { interface ExchangeCodeAzureResponse {
token_type: string; token_type: string;
@@ -93,50 +92,44 @@ interface ExchangeCodeGitlabResponse {
*/ */
const exchangeCode = async ({ const exchangeCode = async ({
integration, integration,
code code,
}: { }: {
integration: string; integration: string;
code: string; code: string;
}) => { }) => {
let obj = {} as any; let obj = {} as any;
try {
switch (integration) { switch (integration) {
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
obj = await exchangeCodeAzure({ obj = await exchangeCodeAzure({
code code,
}); });
break; break;
case INTEGRATION_HEROKU: case INTEGRATION_HEROKU:
obj = await exchangeCodeHeroku({ obj = await exchangeCodeHeroku({
code code,
}); });
break; break;
case INTEGRATION_VERCEL: case INTEGRATION_VERCEL:
obj = await exchangeCodeVercel({ obj = await exchangeCodeVercel({
code code,
}); });
break; break;
case INTEGRATION_NETLIFY: case INTEGRATION_NETLIFY:
obj = await exchangeCodeNetlify({ obj = await exchangeCodeNetlify({
code code,
}); });
break; break;
case INTEGRATION_GITHUB: case INTEGRATION_GITHUB:
obj = await exchangeCodeGithub({ obj = await exchangeCodeGithub({
code code,
}); });
break; break;
case INTEGRATION_GITLAB: case INTEGRATION_GITLAB:
obj = await exchangeCodeGitlab({ obj = await exchangeCodeGitlab({
code code,
}); });
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange');
}
return obj; return obj;
}; };
@@ -145,41 +138,31 @@ const exchangeCode = async ({
* Return [accessToken] for Azure OAuth2 code-token exchange * Return [accessToken] for Azure OAuth2 code-token exchange
* @param param0 * @param param0
*/ */
const exchangeCodeAzure = async ({ const exchangeCodeAzure = async ({ code }: { code: string }) => {
code
}: {
code: string;
}) => {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
let res: ExchangeCodeAzureResponse;
try { const res: ExchangeCodeAzureResponse = (
res = (await request.post( await request.post(
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: "authorization_code",
code: code, code: code,
scope: 'https://vault.azure.net/.default openid offline_access', scope: "https://vault.azure.net/.default openid offline_access",
client_id: await getClientIdAzure(), client_id: await getClientIdAzure(),
client_secret: await getClientSecretAzure(), client_secret: await getClientSecretAzure(),
redirect_uri: `${await getSiteURL()}/integrations/azure-key-vault/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/azure-key-vault/oauth2/callback`,
} as any) } as any)
)).data; )
).data;
accessExpiresAt.setSeconds( accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Azure');
}
return ({ return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: res.refresh_token, refreshToken: res.refresh_token,
accessExpiresAt accessExpiresAt,
}); };
} };
/** /**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku * Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
@@ -191,38 +174,28 @@ const exchangeCodeAzure = async ({
* @returns {String} obj2.refreshToken - refresh token for Heroku API * @returns {String} obj2.refreshToken - refresh token for Heroku API
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token * @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/ */
const exchangeCodeHeroku = async ({ const exchangeCodeHeroku = async ({ code }: { code: string }) => {
code
}: {
code: string;
}) => {
let res: ExchangeCodeHerokuResponse;
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
try {
res = (await request.post( const res: ExchangeCodeHerokuResponse = (
await request.post(
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: "authorization_code",
code: code, code: code,
client_secret: await getClientSecretHeroku() client_secret: await getClientSecretHeroku(),
} as any) } as any)
)).data; )
).data;
accessExpiresAt.setSeconds( accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Heroku');
}
return ({ return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: res.refresh_token, refreshToken: res.refresh_token,
accessExpiresAt accessExpiresAt,
}); };
} };
/** /**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel * Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
@@ -235,30 +208,23 @@ const exchangeCodeHeroku = async ({
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token * @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/ */
const exchangeCodeVercel = async ({ code }: { code: string }) => { const exchangeCodeVercel = async ({ code }: { code: string }) => {
let res: ExchangeCodeVercelResponse; const res: ExchangeCodeVercelResponse = (
try {
res = (
await request.post( await request.post(
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
code: code, code: code,
client_id: await getClientIdVercel(), client_id: await getClientIdVercel(),
client_secret: await getClientSecretVercel(), client_secret: await getClientSecretVercel(),
redirect_uri: `${await getSiteURL()}/integrations/vercel/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/vercel/oauth2/callback`,
} as any) } as any)
) )
).data; ).data;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(`Failed OAuth2 code-token exchange with Vercel [err=${err}]`);
}
return { return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: null, refreshToken: null,
accessExpiresAt: null, accessExpiresAt: null,
teamId: res.team_id teamId: res.team_id,
}; };
}; };
@@ -273,47 +239,39 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token * @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/ */
const exchangeCodeNetlify = async ({ code }: { code: string }) => { const exchangeCodeNetlify = async ({ code }: { code: string }) => {
let res: ExchangeCodeNetlifyResponse; const res: ExchangeCodeNetlifyResponse = (
let accountId;
try {
res = (
await request.post( await request.post(
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: "authorization_code",
code: code, code: code,
client_id: await getClientIdNetlify(), client_id: await getClientIdNetlify(),
client_secret: await getClientSecretNetlify(), client_secret: await getClientSecretNetlify(),
redirect_uri: `${await getSiteURL()}/integrations/netlify/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/netlify/oauth2/callback`,
} as any) } as any)
) )
).data; ).data;
const res2 = await request.get('https://api.netlify.com/api/v1/sites', { const res2 = await request.get("https://api.netlify.com/api/v1/sites", {
headers: { headers: {
Authorization: `Bearer ${res.access_token}` Authorization: `Bearer ${res.access_token}`,
} },
}); });
const res3 = ( const res3 = (
await request.get('https://api.netlify.com/api/v1/accounts', { await request.get("https://api.netlify.com/api/v1/accounts", {
headers: { headers: {
Authorization: `Bearer ${res.access_token}` Authorization: `Bearer ${res.access_token}`,
} },
}) })
).data; ).data;
accountId = res3[0].id; const accountId = res3[0].id;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Netlify');
}
return { return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: res.refresh_token, refreshToken: res.refresh_token,
accountId accountId,
}; };
}; };
@@ -328,33 +286,25 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token * @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/ */
const exchangeCodeGithub = async ({ code }: { code: string }) => { const exchangeCodeGithub = async ({ code }: { code: string }) => {
let res: ExchangeCodeGithubResponse; const res: ExchangeCodeGithubResponse = (
try {
res = (
await request.get(INTEGRATION_GITHUB_TOKEN_URL, { await request.get(INTEGRATION_GITHUB_TOKEN_URL, {
params: { params: {
client_id: await getClientIdGitHub(), client_id: await getClientIdGitHub(),
client_secret: await getClientSecretGitHub(), client_secret: await getClientSecretGitHub(),
code: code, code: code,
redirect_uri: `${await getSiteURL()}/integrations/github/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/github/oauth2/callback`,
}, },
headers: { headers: {
'Accept': 'application/json', Accept: "application/json",
'Accept-Encoding': 'application/json' "Accept-Encoding": "application/json",
} },
}) })
).data; ).data;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Github');
}
return { return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: null, refreshToken: null,
accessExpiresAt: null accessExpiresAt: null,
}; };
}; };
@@ -369,42 +319,32 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token * @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/ */
const exchangeCodeGitlab = async ({ code }: { code: string }) => { const exchangeCodeGitlab = async ({ code }: { code: string }) => {
let res: ExchangeCodeGitlabResponse;
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const res: ExchangeCodeGitlabResponse = (
try {
res = (
await request.post( await request.post(
INTEGRATION_GITLAB_TOKEN_URL, INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: "authorization_code",
code: code, code: code,
client_id: await getClientIdGitLab(), client_id: await getClientIdGitLab(),
client_secret: await getClientSecretGitLab(), client_secret: await getClientSecretGitLab(),
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`,
} as any), } as any),
{ {
headers: { headers: {
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
} },
} }
) )
).data; ).data;
accessExpiresAt.setSeconds( accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
}
return { return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: res.refresh_token, refreshToken: res.refresh_token,
accessExpiresAt accessExpiresAt,
}; };
} };
export { exchangeCode }; export { exchangeCode };
+49 -80
View File
@@ -1,29 +1,24 @@
import * as Sentry from '@sentry/node'; import request from "../config/request";
import request from '../config/request'; import { IIntegrationAuth } from "../models";
import {
IIntegrationAuth
} from '../models';
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
} from '../variables'; } from "../variables";
import { import {
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL INTEGRATION_GITLAB_TOKEN_URL,
} from '../variables'; } from "../variables";
import { import { IntegrationService } from "../services";
IntegrationService
} from '../services';
import { import {
getSiteURL, getSiteURL,
getClientIdAzure, getClientIdAzure,
getClientSecretAzure, getClientSecretAzure,
getClientSecretHeroku, getClientSecretHeroku,
getClientIdGitLab, getClientIdGitLab,
getClientSecretGitLab getClientSecretGitLab,
} from '../config'; } from "../config";
interface RefreshTokenAzureResponse { interface RefreshTokenAzureResponse {
token_type: string; token_type: string;
@@ -60,12 +55,11 @@ interface RefreshTokenGitLabResponse {
*/ */
const exchangeRefresh = async ({ const exchangeRefresh = async ({
integrationAuth, integrationAuth,
refreshToken refreshToken,
}: { }: {
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
refreshToken: string; refreshToken: string;
}) => { }) => {
interface TokenDetails { interface TokenDetails {
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
@@ -73,47 +67,45 @@ const exchangeRefresh = async ({
} }
let tokenDetails: TokenDetails; let tokenDetails: TokenDetails;
try {
switch (integrationAuth.integration) { switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
tokenDetails = await exchangeRefreshAzure({ tokenDetails = await exchangeRefreshAzure({
refreshToken refreshToken,
}); });
break; break;
case INTEGRATION_HEROKU: case INTEGRATION_HEROKU:
tokenDetails = await exchangeRefreshHeroku({ tokenDetails = await exchangeRefreshHeroku({
refreshToken refreshToken,
}); });
break; break;
case INTEGRATION_GITLAB: case INTEGRATION_GITLAB:
tokenDetails = await exchangeRefreshGitLab({ tokenDetails = await exchangeRefreshGitLab({
refreshToken refreshToken,
}); });
break; break;
default: default:
throw new Error('Failed to exchange token for incompatible integration'); throw new Error("Failed to exchange token for incompatible integration");
} }
if (tokenDetails?.accessToken && tokenDetails?.refreshToken && tokenDetails?.accessExpiresAt) { if (
tokenDetails?.accessToken &&
tokenDetails?.refreshToken &&
tokenDetails?.accessExpiresAt
) {
await IntegrationService.setIntegrationAuthAccess({ await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(), integrationAuthId: integrationAuth._id.toString(),
accessId: null, accessId: null,
accessToken: tokenDetails.accessToken, accessToken: tokenDetails.accessToken,
accessExpiresAt: tokenDetails.accessExpiresAt accessExpiresAt: tokenDetails.accessExpiresAt,
}); });
await IntegrationService.setIntegrationAuthRefresh({ await IntegrationService.setIntegrationAuthRefresh({
integrationAuthId: integrationAuth._id.toString(), integrationAuthId: integrationAuth._id.toString(),
refreshToken: tokenDetails.refreshToken refreshToken: tokenDetails.refreshToken,
}); });
} }
return tokenDetails.accessToken; return tokenDetails.accessToken;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get new OAuth2 access token');
}
}; };
/** /**
@@ -124,38 +116,30 @@ const exchangeRefresh = async ({
* @returns * @returns
*/ */
const exchangeRefreshAzure = async ({ const exchangeRefreshAzure = async ({
refreshToken refreshToken,
}: { }: {
refreshToken: string; refreshToken: string;
}) => { }) => {
try {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const { data }: { data: RefreshTokenAzureResponse } = await request.post( const { data }: { data: RefreshTokenAzureResponse } = await request.post(
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
client_id: await getClientIdAzure(), client_id: await getClientIdAzure(),
scope: 'openid offline_access', scope: "openid offline_access",
refresh_token: refreshToken, refresh_token: refreshToken,
grant_type: 'refresh_token', grant_type: "refresh_token",
client_secret: await getClientSecretAzure() client_secret: await getClientSecretAzure(),
} as any) } as any)
); );
accessExpiresAt.setSeconds( accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
accessExpiresAt.getSeconds() + data.expires_in
);
return ({ return {
accessToken: data.access_token, accessToken: data.access_token,
refreshToken: data.refresh_token, refreshToken: data.refresh_token,
accessExpiresAt accessExpiresAt,
}); };
} catch (err) { };
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get refresh OAuth2 access token for Azure');
}
}
/** /**
* Return new access token by exchanging refresh token [refreshToken] for the * Return new access token by exchanging refresh token [refreshToken] for the
@@ -165,39 +149,31 @@ const exchangeRefreshAzure = async ({
* @returns * @returns
*/ */
const exchangeRefreshHeroku = async ({ const exchangeRefreshHeroku = async ({
refreshToken refreshToken,
}: { }: {
refreshToken: string; refreshToken: string;
}) => { }) => {
try {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const { const {
data data,
}: { }: {
data: RefreshTokenHerokuResponse data: RefreshTokenHerokuResponse;
} = await request.post( } = await request.post(
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: 'refresh_token', grant_type: "refresh_token",
refresh_token: refreshToken, refresh_token: refreshToken,
client_secret: await getClientSecretHeroku() client_secret: await getClientSecretHeroku(),
} as any) } as any)
); );
accessExpiresAt.setSeconds( accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
accessExpiresAt.getSeconds() + data.expires_in
);
return ({ return {
accessToken: data.access_token, accessToken: data.access_token,
refreshToken: data.refresh_token, refreshToken: data.refresh_token,
accessExpiresAt accessExpiresAt,
}); };
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to refresh OAuth2 access token for Heroku');
}
}; };
/** /**
@@ -208,45 +184,38 @@ const exchangeRefreshHeroku = async ({
* @returns * @returns
*/ */
const exchangeRefreshGitLab = async ({ const exchangeRefreshGitLab = async ({
refreshToken refreshToken,
}: { }: {
refreshToken: string; refreshToken: string;
}) => { }) => {
try {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const { const {
data data,
}: { }: {
data: RefreshTokenGitLabResponse data: RefreshTokenGitLabResponse;
} = await request.post( } = await request.post(
INTEGRATION_GITLAB_TOKEN_URL, INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: 'refresh_token', grant_type: "refresh_token",
refresh_token: refreshToken, refresh_token: refreshToken,
client_id: await getClientIdGitLab, client_id: await getClientIdGitLab,
client_secret: await getClientSecretGitLab(), client_secret: await getClientSecretGitLab(),
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`,
} as any), } as any),
{ {
headers: { headers: {
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
},
} }
});
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + data.expires_in
); );
return ({ accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
return {
accessToken: data.access_token, accessToken: data.access_token,
refreshToken: data.refresh_token, refreshToken: data.refresh_token,
accessExpiresAt accessExpiresAt,
}); };
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to refresh OAuth2 access token for GitLab');
}
}; };
export { exchangeRefresh }; export { exchangeRefresh };
-7
View File
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { import {
IIntegrationAuth, IIntegrationAuth,
IntegrationAuth, IntegrationAuth,
@@ -22,7 +21,6 @@ const revokeAccess = async ({
accessToken: string; accessToken: string;
}) => { }) => {
let deletedIntegrationAuth; let deletedIntegrationAuth;
try {
// add any integration-specific revocation logic // add any integration-specific revocation logic
switch (integrationAuth.integration) { switch (integrationAuth.integration) {
case INTEGRATION_HEROKU: case INTEGRATION_HEROKU:
@@ -46,11 +44,6 @@ const revokeAccess = async ({
integrationAuth: deletedIntegrationAuth._id integrationAuth: deletedIntegrationAuth._id
}); });
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to delete integration authorization');
}
return deletedIntegrationAuth; return deletedIntegrationAuth;
}; };
+1 -14
View File
@@ -1,4 +1,3 @@
import * as Sentry from "@sentry/node";
import { import {
IIntegrationAuth IIntegrationAuth
} from '../models'; } from '../models';
@@ -31,7 +30,7 @@ const getTeams = async ({
}) => { }) => {
let teams: Team[] = []; let teams: Team[] = [];
try {
switch (integrationAuth.integration) { switch (integrationAuth.integration) {
case INTEGRATION_GITLAB: case INTEGRATION_GITLAB:
teams = await getTeamsGitLab({ teams = await getTeamsGitLab({
@@ -39,12 +38,6 @@ const getTeams = async ({
}); });
break; break;
} }
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get integration teams');
}
return teams; return teams;
} }
@@ -63,7 +56,6 @@ const getTeamsGitLab = async ({
accessToken: string; accessToken: string;
}) => { }) => {
let teams: Team[] = []; let teams: Team[] = [];
try {
const res = (await request.get( const res = (await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups`, `${INTEGRATION_GITLAB_API_URL}/v4/groups`,
{ {
@@ -78,11 +70,6 @@ const getTeamsGitLab = async ({
name: t.name, name: t.name,
teamId: t.id teamId: t.id
})); }));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get GitLab integration teams");
}
return teams; return teams;
} }
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { IntegrationAuth, IWorkspace } from '../models'; import { IntegrationAuth, IWorkspace } from '../models';
+5 -37
View File
@@ -1,7 +1,6 @@
import nacl from 'tweetnacl'; import nacl from 'tweetnacl';
import util from 'tweetnacl-util'; import util from 'tweetnacl-util';
import AesGCM from './aes-gcm'; import AesGCM from './aes-gcm';
import * as Sentry from '@sentry/node';
/** /**
* Return new base64, NaCl, public-private key pair. * Return new base64, NaCl, public-private key pair.
@@ -38,20 +37,13 @@ const encryptAsymmetric = ({
publicKey: string; publicKey: string;
privateKey: string; privateKey: string;
}) => { }) => {
let nonce, ciphertext; const nonce = nacl.randomBytes(24);
try { const ciphertext = nacl.box(
nonce = nacl.randomBytes(24);
ciphertext = nacl.box(
util.decodeUTF8(plaintext), util.decodeUTF8(plaintext),
nonce, nonce,
util.decodeBase64(publicKey), util.decodeBase64(publicKey),
util.decodeBase64(privateKey) util.decodeBase64(privateKey)
); );
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform asymmetric encryption');
}
return { return {
ciphertext: util.encodeBase64(ciphertext), ciphertext: util.encodeBase64(ciphertext),
@@ -80,19 +72,12 @@ const decryptAsymmetric = ({
publicKey: string; publicKey: string;
privateKey: string; privateKey: string;
}): string => { }): string => {
let plaintext: any; const plaintext: any = nacl.box.open(
try {
plaintext = nacl.box.open(
util.decodeBase64(ciphertext), util.decodeBase64(ciphertext),
util.decodeBase64(nonce), util.decodeBase64(nonce),
util.decodeBase64(publicKey), util.decodeBase64(publicKey),
util.decodeBase64(privateKey) util.decodeBase64(privateKey)
); );
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform asymmetric decryption');
}
return util.encodeUTF8(plaintext); return util.encodeUTF8(plaintext);
}; };
@@ -110,17 +95,8 @@ const encryptSymmetric = ({
plaintext: string; plaintext: string;
key: string; key: string;
}) => { }) => {
let ciphertext, iv, tag;
try {
const obj = AesGCM.encrypt(plaintext, key); const obj = AesGCM.encrypt(plaintext, key);
ciphertext = obj.ciphertext; const { ciphertext, iv, tag } = obj;
iv = obj.iv;
tag = obj.tag;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric encryption');
}
return { return {
ciphertext, ciphertext,
@@ -150,15 +126,7 @@ const decryptSymmetric = ({
tag: string; tag: string;
key: string; key: string;
}): string => { }): string => {
let plaintext; const plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
try {
plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric decryption');
}
return plaintext; return plaintext;
}; };
+13 -13
View File
@@ -28,14 +28,14 @@ describe('Crypto', () => {
test('should throw error if publicKey is undefined', () => { test('should throw error if publicKey is undefined', () => {
expect(() => { expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey }); encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('Failed to perform asymmetric encryption'); }).toThrowError('invalid encoding');
}); });
test('should throw error if publicKey is empty string', () => { test('should throw error if publicKey is empty string', () => {
publicKey = ''; publicKey = '';
expect(() => { expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey }); encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('Failed to perform asymmetric encryption'); }).toThrowError('bad public key size');
}); });
}); });
@@ -47,14 +47,14 @@ describe('Crypto', () => {
test('should throw error if privateKey is undefined', () => { test('should throw error if privateKey is undefined', () => {
expect(() => { expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey }); encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('Failed to perform asymmetric encryption'); }).toThrowError('invalid encoding');
}); });
test('should throw error if privateKey is empty string', () => { test('should throw error if privateKey is empty string', () => {
privateKey = ''; privateKey = '';
expect(() => { expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey }); encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('Failed to perform asymmetric encryption'); }).toThrowError('bad secret key size');
}); });
}); });
@@ -66,7 +66,7 @@ describe('Crypto', () => {
test('should throw error if plaintext is undefined', () => { test('should throw error if plaintext is undefined', () => {
expect(() => { expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey }); encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('Failed to perform asymmetric encryption'); }).toThrowError('expected string');
}); });
test('should encrypt plaintext containing special characters', () => { test('should encrypt plaintext containing special characters', () => {
@@ -130,7 +130,7 @@ describe('Crypto', () => {
publicKey, publicKey,
privateKey privateKey
}); });
}).toThrowError('Failed to perform asymmetric decryption'); }).toThrowError('invalid encoding');
}); });
test('should throw error if nonce is modified', () => { test('should throw error if nonce is modified', () => {
@@ -149,7 +149,7 @@ describe('Crypto', () => {
publicKey, publicKey,
privateKey privateKey
}); });
}).toThrowError('Failed to perform asymmetric decryption'); }).toThrowError('invalid encoding');
}); });
}); });
}); });
@@ -170,7 +170,7 @@ describe('Crypto', () => {
const invalidKey = 'invalid-key'; const invalidKey = 'invalid-key';
expect(() => { expect(() => {
encryptSymmetric({ plaintext, key: invalidKey }); encryptSymmetric({ plaintext, key: invalidKey });
}).toThrowError('Failed to perform symmetric encryption'); }).toThrowError('Invalid key length');
}); });
test('should throw an error when invalid key is provided', () => { test('should throw an error when invalid key is provided', () => {
@@ -179,7 +179,7 @@ describe('Crypto', () => {
expect(() => { expect(() => {
encryptSymmetric({ plaintext, key: invalidKey }); encryptSymmetric({ plaintext, key: invalidKey });
}).toThrowError('Failed to perform symmetric encryption'); }).toThrowError('Invalid key length');
}); });
}); });
@@ -209,7 +209,7 @@ describe('Crypto', () => {
tag, tag,
key key
}); });
}).toThrowError('Failed to perform symmetric decryption'); }).toThrowError('Unsupported state or unable to authenticate data');
}); });
test('should fail if iv is modified', () => { test('should fail if iv is modified', () => {
@@ -221,7 +221,7 @@ describe('Crypto', () => {
tag, tag,
key key
}); });
}).toThrowError('Failed to perform symmetric decryption'); }).toThrowError('Unsupported state or unable to authenticate data');
}); });
test('should fail if tag is modified', () => { test('should fail if tag is modified', () => {
@@ -233,7 +233,7 @@ describe('Crypto', () => {
tag: modifiedTag, tag: modifiedTag,
key key
}); });
}).toThrowError('Failed to perform symmetric decryption'); }).toThrowError(/Invalid authentication tag length: \d+/);
}); });
test('should throw an error when decryption fails', () => { test('should throw an error when decryption fails', () => {
@@ -245,7 +245,7 @@ describe('Crypto', () => {
tag, tag,
key: invalidKey key: invalidKey
}); });
}).toThrowError('Failed to perform symmetric decryption'); }).toThrowError('Invalid key length');
}); });
}); });
}); });