mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
feat: remove try-catch blocks for handling errors in middleware
This commit is contained in:
@@ -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,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -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,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
@@ -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,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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,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';
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user