diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index c1725456cf..eef1c9ad3f 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; import * as bigintConversion from 'bigint-conversion'; @@ -34,47 +33,39 @@ declare module 'jsonwebtoken' { * @returns */ export const login1 = async (req: Request, res: Response) => { - try { - const { - email, - clientPublicKey - }: { email: string; clientPublicKey: string } = req.body; + const { + email, + clientPublicKey + }: { email: string; clientPublicKey: string } = req.body; - const user = await User.findOne({ - email - }).select('+salt +verifier'); + const user = await User.findOne({ + email + }).select('+salt +verifier'); - if (!user) throw new Error('Failed to find user'); + if (!user) throw new Error('Failed to find user'); - const server = new jsrp.server(); - server.init( - { - salt: user.salt, - verifier: user.verifier - }, - async () => { - // generate server-side public key - const serverPublicKey = server.getPublicKey(); + const server = new jsrp.server(); + server.init( + { + salt: user.salt, + verifier: user.verifier + }, + async () => { + // generate server-side public key + const serverPublicKey = server.getPublicKey(); - await LoginSRPDetail.findOneAndReplace({ email: email }, { - email: email, - clientPublicKey: clientPublicKey, - serverBInt: bigintConversion.bigintToBuf(server.bInt), - }, { upsert: true, returnNewDocument: false }) + await LoginSRPDetail.findOneAndReplace({ email: email }, { + email: email, + clientPublicKey: clientPublicKey, + serverBInt: bigintConversion.bigintToBuf(server.bInt), + }, { upsert: true, returnNewDocument: false }) - return res.status(200).send({ - serverPublicKey, - salt: user.salt - }); - } - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to start authentication process' - }); - } + return res.status(200).send({ + serverPublicKey, + salt: user.salt + }); + } + ); }; /** @@ -85,84 +76,76 @@ export const login1 = async (req: Request, res: Response) => { * @returns */ export const login2 = async (req: Request, res: Response) => { - try { - const { email, clientProof } = req.body; - const user = await User.findOne({ - email - }).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag'); + const { email, clientProof } = req.body; + const user = await User.findOne({ + email + }).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag'); - if (!user) throw new Error('Failed to find user'); + if (!user) throw new Error('Failed to find user'); - const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email }) + const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email }) - if (!loginSRPDetailFromDB) { - return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) - } + if (!loginSRPDetailFromDB) { + return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) + } - const server = new jsrp.server(); - server.init( - { - salt: user.salt, - verifier: user.verifier, - b: loginSRPDetailFromDB.serverBInt - }, - async () => { - server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); + const server = new jsrp.server(); + server.init( + { + salt: user.salt, + verifier: user.verifier, + b: loginSRPDetailFromDB.serverBInt + }, + async () => { + server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); - // compare server and client shared keys - if (server.checkClientProof(clientProof)) { - // issue tokens + // compare server and client shared keys + if (server.checkClientProof(clientProof)) { + // issue tokens - await checkUserDevice({ - user, - ip: req.ip, - userAgent: req.headers['user-agent'] ?? '' - }); + await checkUserDevice({ + user, + ip: req.ip, + userAgent: req.headers['user-agent'] ?? '' + }); - const tokens = await issueAuthTokens({ userId: user._id.toString() }); + const tokens = await issueAuthTokens({ userId: user._id.toString() }); - // store (refresh) token in httpOnly cookie - res.cookie('jid', tokens.refreshToken, { - httpOnly: true, - path: '/', - sameSite: 'strict', - secure: await getHttpsEnabled() - }); + // store (refresh) token in httpOnly cookie + res.cookie('jid', tokens.refreshToken, { + httpOnly: true, + path: '/', + sameSite: 'strict', + secure: await getHttpsEnabled() + }); - const loginAction = await EELogService.createAction({ - name: ACTION_LOGIN, - userId: user._id - }); + const loginAction = await EELogService.createAction({ + name: ACTION_LOGIN, + userId: user._id + }); - loginAction && await EELogService.createLog({ - userId: user._id, - actions: [loginAction], - channel: getChannelFromUserAgent(req.headers['user-agent']), - ipAddress: req.ip - }); + loginAction && await EELogService.createLog({ + userId: user._id, + actions: [loginAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); - // return (access) token in response - return res.status(200).send({ - token: tokens.token, - publicKey: user.publicKey, - encryptedPrivateKey: user.encryptedPrivateKey, - iv: user.iv, - tag: user.tag - }); - } - - return res.status(400).send({ - message: 'Failed to authenticate. Try again?' + // return (access) token in response + return res.status(200).send({ + token: tokens.token, + publicKey: user.publicKey, + encryptedPrivateKey: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag }); } - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to authenticate. Try again?' - }); - } + + return res.status(400).send({ + message: 'Failed to authenticate. Try again?' + }); + } + ); }; /** @@ -172,38 +155,29 @@ export const login2 = async (req: Request, res: Response) => { * @returns */ export const logout = async (req: Request, res: Response) => { - try { - await clearTokens({ - userId: req.user._id.toString() - }); + await clearTokens({ + userId: req.user._id.toString() + }); - // clear httpOnly cookie - res.cookie('jid', '', { - httpOnly: true, - path: '/', - sameSite: 'strict', - secure: (await getHttpsEnabled()) as boolean - }); + // clear httpOnly cookie + res.cookie('jid', '', { + httpOnly: true, + path: '/', + sameSite: 'strict', + secure: (await getHttpsEnabled()) as boolean + }); - const logoutAction = await EELogService.createAction({ - name: ACTION_LOGOUT, - userId: req.user._id - }); + const logoutAction = await EELogService.createAction({ + name: ACTION_LOGOUT, + userId: req.user._id + }); - logoutAction && await EELogService.createLog({ - userId: req.user._id, - actions: [logoutAction], - channel: getChannelFromUserAgent(req.headers['user-agent']), - ipAddress: req.ip - }); - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to logout' - }); - } + logoutAction && await EELogService.createLog({ + userId: req.user._id, + actions: [logoutAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); return res.status(200).send({ message: 'Successfully logged out.' @@ -229,43 +203,35 @@ export const checkAuth = async (req: Request, res: Response) => { * @returns */ export const getNewToken = async (req: Request, res: Response) => { - try { - const refreshToken = req.cookies.jid; + const refreshToken = req.cookies.jid; - if (!refreshToken) { - throw new Error('Failed to find token in request cookies'); - } - - const decodedToken = ( - jwt.verify(refreshToken, await getJwtRefreshSecret()) - ); - - const user = await User.findOne({ - _id: decodedToken.userId - }).select('+publicKey'); - - if (!user) throw new Error('Failed to authenticate unfound user'); - if (!user?.publicKey) - throw new Error('Failed to authenticate not fully set up account'); - - const token = createToken({ - payload: { - userId: decodedToken.userId - }, - expiresIn: await getJwtAuthLifetime(), - secret: await getJwtAuthSecret() - }); - - return res.status(200).send({ - token - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Invalid request' - }); + if (!refreshToken) { + throw new Error('Failed to find token in request cookies'); } + + const decodedToken = ( + jwt.verify(refreshToken, await getJwtRefreshSecret()) + ); + + const user = await User.findOne({ + _id: decodedToken.userId + }).select('+publicKey'); + + if (!user) throw new Error('Failed to authenticate unfound user'); + if (!user?.publicKey) + throw new Error('Failed to authenticate not fully set up account'); + + const token = createToken({ + payload: { + userId: decodedToken.userId + }, + expiresIn: await getJwtAuthLifetime(), + secret: await getJwtAuthSecret() + }); + + return res.status(200).send({ + token + }); }; export const handleAuthProviderCallback = (req: Request, res: Response) => { diff --git a/backend/src/controllers/v1/botController.ts b/backend/src/controllers/v1/botController.ts index 0fdce2cd9f..f155f58a5e 100644 --- a/backend/src/controllers/v1/botController.ts +++ b/backend/src/controllers/v1/botController.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express'; import { Types } from 'mongoose'; -import * as Sentry from '@sentry/node'; import { Bot, BotKey } from '../../models'; import { createBot } from '../../helpers/bot'; @@ -17,33 +16,24 @@ interface BotKey { * @returns */ export const getBotByWorkspaceId = async (req: Request, res: Response) => { - let bot; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - bot = await Bot.findOne({ - workspace: workspaceId - }); - - if (!bot) { - // case: bot doesn't exist for workspace with id [workspaceId] - // -> create a new bot and return it - bot = await createBot({ - name: 'Infisical Bot', - workspaceId: new Types.ObjectId(workspaceId) - }); - } - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get bot for workspace' - }); - } + let bot = await Bot.findOne({ + workspace: workspaceId + }); + + if (!bot) { + // case: bot doesn't exist for workspace with id [workspaceId] + // -> create a new bot and return it + bot = await createBot({ + name: 'Infisical Bot', + workspaceId: new Types.ObjectId(workspaceId) + }); + } - return res.status(200).send({ - bot - }); + return res.status(200).send({ + bot + }); }; /** @@ -53,54 +43,44 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => { * @returns */ export const setBotActiveState = async (req: Request, res: Response) => { - let bot; - try { - const { isActive, botKey }: { isActive: boolean, botKey: BotKey } = req.body; - - if (isActive) { - // bot state set to active -> share workspace key with bot - if (!botKey?.encryptedKey || !botKey?.nonce) { - return res.status(400).send({ - message: 'Failed to set bot state to active - missing bot key' - }); - } - - await BotKey.findOneAndUpdate({ - workspace: req.bot.workspace - }, { - encryptedKey: botKey.encryptedKey, - nonce: botKey.nonce, - sender: req.user._id, - bot: req.bot._id, - workspace: req.bot.workspace - }, { - upsert: true, - new: true - }); - } else { - // case: bot state set to inactive -> delete bot's workspace key - await BotKey.deleteOne({ - bot: req.bot._id + const { isActive, botKey }: { isActive: boolean, botKey: BotKey } = req.body; + + if (isActive) { + // bot state set to active -> share workspace key with bot + if (!botKey?.encryptedKey || !botKey?.nonce) { + return res.status(400).send({ + message: 'Failed to set bot state to active - missing bot key' }); } - - bot = await Bot.findOneAndUpdate({ - _id: req.bot._id + + await BotKey.findOneAndUpdate({ + workspace: req.bot.workspace }, { - isActive + encryptedKey: botKey.encryptedKey, + nonce: botKey.nonce, + sender: req.user._id, + bot: req.bot._id, + workspace: req.bot.workspace }, { + upsert: true, new: true }); - - if (!bot) throw new Error('Failed to update bot active state'); - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to update bot active state' - }); - } + } else { + // case: bot state set to inactive -> delete bot's workspace key + await BotKey.deleteOne({ + bot: req.bot._id + }); + } + + let bot = await Bot.findOneAndUpdate({ + _id: req.bot._id + }, { + isActive + }, { + new: true + }); + + if (!bot) throw new Error('Failed to update bot active state'); return res.status(200).send({ bot diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index 97032aaa4d..a002187c82 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express'; import { Types } from 'mongoose'; -import * as Sentry from '@sentry/node'; import { IntegrationAuth, Bot @@ -22,22 +21,13 @@ import { standardRequest } from '../../config/request'; * Return integration authorization with id [integrationAuthId] */ export const getIntegrationAuth = async (req: Request, res: Response) => { - let integrationAuth; - try { - const { integrationAuthId } = req.params; - integrationAuth = await IntegrationAuth.findById(integrationAuthId); - - if (!integrationAuth) return res.status(400).send({ - message: 'Failed to find integration authorization' - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get integration authorization' - }); - } - + const { integrationAuthId } = req.params; + const integrationAuth = await IntegrationAuth.findById(integrationAuthId); + + if (!integrationAuth) return res.status(400).send({ + message: 'Failed to find integration authorization' + }); + return res.status(200).send({ integrationAuth }); @@ -61,33 +51,25 @@ export const oAuthExchange = async ( req: Request, res: Response ) => { - try { - const { workspaceId, code, integration } = req.body; - if (!INTEGRATION_SET.has(integration)) - throw new Error('Failed to validate integration'); - - const environments = req.membership.workspace?.environments || []; - if(environments.length === 0){ - throw new Error("Failed to get environments") - } - - const integrationAuth = await IntegrationService.handleOAuthExchange({ - workspaceId, - integration, - code, - environment: environments[0].slug, - }); - - return res.status(200).send({ - integrationAuth - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get OAuth2 code-token exchange' - }); - } + const { workspaceId, code, integration } = req.body; + if (!INTEGRATION_SET.has(integration)) + throw new Error('Failed to validate integration'); + + const environments = req.membership.workspace?.environments || []; + if(environments.length === 0){ + throw new Error("Failed to get environments") + } + + const integrationAuth = await IntegrationService.handleOAuthExchange({ + workspaceId, + integration, + code, + environment: environments[0].slug, + }); + + return res.status(200).send({ + integrationAuth + }); }; /** @@ -104,55 +86,47 @@ export const saveIntegrationAccessToken = async ( // TODO: check if access token is valid for each integration let integrationAuth; - try { - const { - workspaceId, - accessId, - accessToken, - integration - }: { - workspaceId: string; - accessId: string | null; - accessToken: string; - integration: string; - } = req.body; + const { + workspaceId, + accessId, + accessToken, + integration + }: { + workspaceId: string; + accessId: string | null; + accessToken: string; + integration: string; + } = req.body; - const bot = await Bot.findOne({ - workspace: new Types.ObjectId(workspaceId), - isActive: true - }); - - if (!bot) throw new Error('Bot must be enabled to save integration access token'); + const bot = await Bot.findOne({ + workspace: new Types.ObjectId(workspaceId), + isActive: true + }); + + if (!bot) throw new Error('Bot must be enabled to save integration access token'); - integrationAuth = await IntegrationAuth.findOneAndUpdate({ - workspace: new Types.ObjectId(workspaceId), - integration - }, { - workspace: new Types.ObjectId(workspaceId), - integration, - algorithm: ALGORITHM_AES_256_GCM, - keyEncoding: ENCODING_SCHEME_UTF8 - }, { - new: true, - upsert: true - }); - - // encrypt and save integration access details - integrationAuth = await IntegrationService.setIntegrationAuthAccess({ - integrationAuthId: integrationAuth._id.toString(), - accessId, - accessToken, - accessExpiresAt: undefined - }); - - if (!integrationAuth) throw new Error('Failed to save integration access token'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to save access token for integration' - }); - } + integrationAuth = await IntegrationAuth.findOneAndUpdate({ + workspace: new Types.ObjectId(workspaceId), + integration + }, { + workspace: new Types.ObjectId(workspaceId), + integration, + algorithm: ALGORITHM_AES_256_GCM, + keyEncoding: ENCODING_SCHEME_UTF8 + }, { + new: true, + upsert: true + }); + + // encrypt and save integration access details + integrationAuth = await IntegrationService.setIntegrationAuthAccess({ + integrationAuthId: integrationAuth._id.toString(), + accessId, + accessToken, + accessExpiresAt: undefined + }); + + if (!integrationAuth) throw new Error('Failed to save integration access token'); return res.status(200).send({ integrationAuth @@ -166,22 +140,13 @@ export const saveIntegrationAccessToken = async ( * @returns */ export const getIntegrationAuthApps = async (req: Request, res: Response) => { - let apps; - try { - const teamId = req.query.teamId as string; - - apps = await getApps({ - integrationAuth: req.integrationAuth, - accessToken: req.accessToken, - ...teamId && { teamId } - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get integration authorization applications", - }); - } + const teamId = req.query.teamId as string; + + const apps = await getApps({ + integrationAuth: req.integrationAuth, + accessToken: req.accessToken, + ...teamId && { teamId } + }); return res.status(200).send({ apps @@ -402,19 +367,10 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo * @returns */ export const deleteIntegrationAuth = async (req: Request, res: Response) => { - let integrationAuth; - try { - integrationAuth = await revokeAccess({ - integrationAuth: req.integrationAuth, - accessToken: req.accessToken, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to delete integration authorization", - }); - } + const integrationAuth = await revokeAccess({ + integrationAuth: req.integrationAuth, + accessToken: req.accessToken, + }); return res.status(200).send({ integrationAuth, diff --git a/backend/src/controllers/v1/integrationController.ts b/backend/src/controllers/v1/integrationController.ts index 060f89b339..83f7c784a4 100644 --- a/backend/src/controllers/v1/integrationController.ts +++ b/backend/src/controllers/v1/integrationController.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express'; import { Types } from 'mongoose'; -import * as Sentry from '@sentry/node'; import { Integration } from '../../models'; @@ -14,62 +13,50 @@ import { eventPushSecrets } from '../../events'; * @returns */ export const createIntegration = async (req: Request, res: Response) => { - let integration; - - try { - const { - integrationAuthId, - app, - appId, - isActive, - sourceEnvironment, - targetEnvironment, - targetEnvironmentId, - targetService, - targetServiceId, - owner, - path, - region - } = req.body; - - // TODO: validate [sourceEnvironment] and [targetEnvironment] - - // initialize new integration after saving integration access token - integration = await new Integration({ - workspace: req.integrationAuth.workspace._id, - environment: sourceEnvironment, - isActive, - app, - appId, - targetEnvironment, - targetEnvironmentId, - targetService, - targetServiceId, - owner, - path, - region, - integration: req.integrationAuth.integration, - integrationAuth: new Types.ObjectId(integrationAuthId) - }).save(); - - if (integration) { - // trigger event - push secrets - EventService.handleEvent({ - event: eventPushSecrets({ - workspaceId: integration.workspace, - environment: sourceEnvironment - }) - }); - } - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to create integration' - }); - } + const { + integrationAuthId, + app, + appId, + isActive, + sourceEnvironment, + targetEnvironment, + targetEnvironmentId, + targetService, + targetServiceId, + owner, + path, + region + } = req.body; + + // TODO: validate [sourceEnvironment] and [targetEnvironment] + // initialize new integration after saving integration access token + const integration = await new Integration({ + workspace: req.integrationAuth.workspace._id, + environment: sourceEnvironment, + isActive, + app, + appId, + targetEnvironment, + targetEnvironmentId, + targetService, + targetServiceId, + owner, + path, + region, + integration: req.integrationAuth.integration, + integrationAuth: new Types.ObjectId(integrationAuthId) + }).save(); + + if (integration) { + // trigger event - push secrets + EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: integration.workspace, + environment: sourceEnvironment + }) + }); + } return res.status(200).send({ integration, }); @@ -82,52 +69,43 @@ export const createIntegration = async (req: Request, res: Response) => { * @returns */ export const updateIntegration = async (req: Request, res: Response) => { - let integration; // TODO: add integration-specific validation to ensure that each // integration has the correct fields populated in [Integration] - try { - const { + const { + environment, + isActive, + app, + appId, + targetEnvironment, + owner, // github-specific integration param + } = req.body; + + const integration = await Integration.findOneAndUpdate( + { + _id: req.integration._id, + }, + { environment, isActive, app, appId, targetEnvironment, - owner, // github-specific integration param - } = req.body; - - integration = await Integration.findOneAndUpdate( - { - _id: req.integration._id, - }, - { - environment, - isActive, - app, - appId, - targetEnvironment, - owner, - }, - { - new: true, - } - ); - - if (integration) { - // trigger event - push secrets - EventService.handleEvent({ - event: eventPushSecrets({ - workspaceId: integration.workspace, - environment - }), - }); + owner, + }, + { + new: true, } - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to update integration", + ); + + if (integration) { + // trigger event - push secrets + EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: integration.workspace, + environment + }), }); } @@ -144,22 +122,13 @@ export const updateIntegration = async (req: Request, res: Response) => { * @returns */ export const deleteIntegration = async (req: Request, res: Response) => { - let integration; - try { - const { integrationId } = req.params; + const { integrationId } = req.params; - integration = await Integration.findOneAndDelete({ - _id: integrationId, - }); + const integration = await Integration.findOneAndDelete({ + _id: integrationId, + }); - if (!integration) throw new Error("Failed to find integration"); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to delete integration", - }); - } + if (!integration) throw new Error("Failed to find integration"); return res.status(200).send({ integration, diff --git a/backend/src/controllers/v1/keyController.ts b/backend/src/controllers/v1/keyController.ts index ffcb16d9cc..beb4d0d073 100644 --- a/backend/src/controllers/v1/keyController.ts +++ b/backend/src/controllers/v1/keyController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { Key } from '../../models'; import { findMembership } from '../../helpers/membership'; @@ -11,34 +10,26 @@ import { findMembership } from '../../helpers/membership'; * @returns */ export const uploadKey = async (req: Request, res: Response) => { - try { - const { workspaceId } = req.params; - const { key } = req.body; + const { workspaceId } = req.params; + const { key } = req.body; - // validate membership of receiver - const receiverMembership = await findMembership({ - user: key.userId, - workspace: workspaceId - }); + // validate membership of receiver + const receiverMembership = await findMembership({ + user: key.userId, + workspace: workspaceId + }); - if (!receiverMembership) { - throw new Error('Failed receiver membership validation for workspace'); - } + if (!receiverMembership) { + throw new Error('Failed receiver membership validation for workspace'); + } - await new Key({ - encryptedKey: key.encryptedKey, - nonce: key.nonce, - sender: req.user._id, - receiver: key.userId, - workspace: workspaceId - }).save(); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to upload key to workspace' - }); - } + await new Key({ + encryptedKey: key.encryptedKey, + nonce: key.nonce, + sender: req.user._id, + receiver: key.userId, + workspace: workspaceId + }).save(); return res.status(200).send({ message: 'Successfully uploaded key to workspace' @@ -52,25 +43,16 @@ export const uploadKey = async (req: Request, res: Response) => { * @returns */ export const getLatestKey = async (req: Request, res: Response) => { - let latestKey; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - // get latest key - latestKey = await Key.find({ - workspace: workspaceId, - receiver: req.user._id - }) - .sort({ createdAt: -1 }) - .limit(1) - .populate('sender', '+publicKey'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get latest key' - }); - } + // get latest key + const latestKey = await Key.find({ + workspace: workspaceId, + receiver: req.user._id + }) + .sort({ createdAt: -1 }) + .limit(1) + .populate('sender', '+publicKey'); const resObj: any = {}; @@ -79,4 +61,4 @@ export const getLatestKey = async (req: Request, res: Response) => { } return res.status(200).send(resObj); -}; \ No newline at end of file +}; diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index 67dd41a0df..d7e512adbd 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import { Membership, MembershipOrg, User, Key } from '../../models'; import { @@ -16,25 +15,16 @@ import { getSiteURL } from '../../config'; * @returns */ export const validateMembership = async (req: Request, res: Response) => { - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; + // validate membership + const membership = await findMembership({ + user: req.user._id, + workspace: workspaceId + }); - // validate membership - const membership = await findMembership({ - user: req.user._id, - workspace: workspaceId - }); - - if (!membership) { - throw new Error('Failed to validate membership'); - } - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed workspace connection check' - }); - } + if (!membership) { + throw new Error('Failed to validate membership'); + } return res.status(200).send({ message: 'Workspace membership confirmed' @@ -48,48 +38,39 @@ export const validateMembership = async (req: Request, res: Response) => { * @returns */ export const deleteMembership = async (req: Request, res: Response) => { - let deletedMembership; - try { - const { membershipId } = req.params; + const { membershipId } = req.params; - // check if membership to delete exists - const membershipToDelete = await Membership.findOne({ - _id: membershipId - }).populate('user'); + // check if membership to delete exists + const membershipToDelete = await Membership.findOne({ + _id: membershipId + }).populate('user'); - if (!membershipToDelete) { - throw new Error( - "Failed to delete workspace membership that doesn't exist" - ); - } + if (!membershipToDelete) { + throw new Error( + "Failed to delete workspace membership that doesn't exist" + ); + } - // check if user is a member and admin of the workspace - // whose membership we wish to delete - const membership = await Membership.findOne({ - user: req.user._id, - workspace: membershipToDelete.workspace - }); + // check if user is a member and admin of the workspace + // whose membership we wish to delete + const membership = await Membership.findOne({ + user: req.user._id, + workspace: membershipToDelete.workspace + }); - if (!membership) { - throw new Error('Failed to validate workspace membership'); - } + if (!membership) { + throw new Error('Failed to validate workspace membership'); + } - if (membership.role !== ADMIN) { - // user is not an admin member of the workspace - throw new Error('Insufficient role for deleting workspace membership'); - } + if (membership.role !== ADMIN) { + // user is not an admin member of the workspace + throw new Error('Insufficient role for deleting workspace membership'); + } - // delete workspace membership - deletedMembership = await deleteMember({ - membershipId: membershipToDelete._id.toString() - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to delete membership' - }); - } + // delete workspace membership + const deletedMembership = await deleteMember({ + membershipId: membershipToDelete._id.toString() + }); return res.status(200).send({ deletedMembership @@ -103,49 +84,40 @@ export const deleteMembership = async (req: Request, res: Response) => { * @returns */ export const changeMembershipRole = async (req: Request, res: Response) => { - let membershipToChangeRole; - try { - const { membershipId } = req.params; - const { role } = req.body; + const { membershipId } = req.params; + const { role } = req.body; - if (![ADMIN, MEMBER].includes(role)) { - throw new Error('Failed to validate role'); - } + if (![ADMIN, MEMBER].includes(role)) { + throw new Error('Failed to validate role'); + } - // validate target membership - membershipToChangeRole = await findMembership({ - _id: membershipId - }); + // validate target membership + const membershipToChangeRole = await findMembership({ + _id: membershipId + }); - if (!membershipToChangeRole) { - throw new Error('Failed to find membership to change role'); - } + if (!membershipToChangeRole) { + throw new Error('Failed to find membership to change role'); + } - // check if user is a member and admin of target membership's - // workspace - const membership = await findMembership({ - user: req.user._id, - workspace: membershipToChangeRole.workspace - }); + // check if user is a member and admin of target membership's + // workspace + const membership = await findMembership({ + user: req.user._id, + workspace: membershipToChangeRole.workspace + }); - if (!membership) { - throw new Error('Failed to validate membership'); - } + if (!membership) { + throw new Error('Failed to validate membership'); + } - if (membership.role !== ADMIN) { - // user is not an admin member of the workspace - throw new Error('Insufficient role for changing member roles'); - } + if (membership.role !== ADMIN) { + // user is not an admin member of the workspace + throw new Error('Insufficient role for changing member roles'); + } - membershipToChangeRole.role = role; - await membershipToChangeRole.save(); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to change membership role' - }); - } + membershipToChangeRole.role = role; + await membershipToChangeRole.save(); return res.status(200).send({ membership: membershipToChangeRole @@ -159,75 +131,66 @@ export const changeMembershipRole = async (req: Request, res: Response) => { * @returns */ export const inviteUserToWorkspace = async (req: Request, res: Response) => { - let invitee, latestKey; - try { - const { workspaceId } = req.params; - const { email }: { email: string } = req.body; + const { workspaceId } = req.params; + const { email }: { email: string } = req.body; - invitee = await User.findOne({ - email - }).select('+publicKey'); + const invitee = await User.findOne({ + email + }).select('+publicKey'); - if (!invitee || !invitee?.publicKey) - throw new Error('Failed to validate invitee'); + if (!invitee || !invitee?.publicKey) + throw new Error('Failed to validate invitee'); - // validate invitee's workspace membership - ensure member isn't - // already a member of the workspace - const inviteeMembership = await Membership.findOne({ - user: invitee._id, - workspace: workspaceId - }); + // validate invitee's workspace membership - ensure member isn't + // already a member of the workspace + const inviteeMembership = await Membership.findOne({ + user: invitee._id, + workspace: workspaceId + }); - if (inviteeMembership) - throw new Error('Failed to add existing member of workspace'); + if (inviteeMembership) + throw new Error('Failed to add existing member of workspace'); - // validate invitee's organization membership - ensure that only - // (accepted) organization members can be added to the workspace - const membershipOrg = await MembershipOrg.findOne({ - user: invitee._id, - organization: req.membership.workspace.organization, - status: ACCEPTED - }); + // validate invitee's organization membership - ensure that only + // (accepted) organization members can be added to the workspace + const membershipOrg = await MembershipOrg.findOne({ + user: invitee._id, + organization: req.membership.workspace.organization, + status: ACCEPTED + }); - if (!membershipOrg) - throw new Error("Failed to validate invitee's organization membership"); + if (!membershipOrg) + throw new Error("Failed to validate invitee's organization membership"); - // get latest key - latestKey = await Key.findOne({ - workspace: workspaceId, - receiver: req.user._id - }) - .sort({ createdAt: -1 }) - .populate('sender', '+publicKey'); + // get latest key + const latestKey = await Key.findOne({ + workspace: workspaceId, + receiver: req.user._id + }) + .sort({ createdAt: -1 }) + .populate('sender', '+publicKey'); - // create new workspace membership - const m = await new Membership({ - user: invitee._id, - workspace: workspaceId, - role: MEMBER - }).save(); + // create new workspace membership + const m = await new Membership({ + user: invitee._id, + workspace: workspaceId, + role: MEMBER + }).save(); - await sendMail({ - template: 'workspaceInvitation.handlebars', - subjectLine: 'Infisical workspace invitation', - recipients: [invitee.email], - substitutions: { - inviterFirstName: req.user.firstName, - inviterEmail: req.user.email, - workspaceName: req.membership.workspace.name, - callback_url: (await getSiteURL()) + '/login' - } - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to invite user to workspace' - }); - } + await sendMail({ + template: 'workspaceInvitation.handlebars', + subjectLine: 'Infisical workspace invitation', + recipients: [invitee.email], + substitutions: { + inviterFirstName: req.user.firstName, + inviterEmail: req.user.email, + workspaceName: req.membership.workspace.name, + callback_url: (await getSiteURL()) + '/login' + } + }); return res.status(200).send({ invitee, latestKey }); -}; \ No newline at end of file +}; diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index 60cdc96911..f6b0ee3ccb 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -1,6 +1,5 @@ import { Types } from 'mongoose'; import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { MembershipOrg, Organization, User } from '../../models'; import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg'; import { createToken } from '../../helpers/auth'; @@ -17,52 +16,43 @@ import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured * @returns */ export const deleteMembershipOrg = async (req: Request, res: Response) => { - let membershipOrgToDelete; - try { - const { membershipOrgId } = req.params; + const { membershipOrgId } = req.params; - // check if organization membership to delete exists - membershipOrgToDelete = await MembershipOrg.findOne({ - _id: membershipOrgId - }).populate('user'); + // check if organization membership to delete exists + const membershipOrgToDelete = await MembershipOrg.findOne({ + _id: membershipOrgId + }).populate('user'); - if (!membershipOrgToDelete) { - throw new Error( - "Failed to delete organization membership that doesn't exist" - ); - } + if (!membershipOrgToDelete) { + throw new Error( + "Failed to delete organization membership that doesn't exist" + ); + } - // check if user is a member and admin of the organization - // whose membership we wish to delete - const membershipOrg = await MembershipOrg.findOne({ - user: req.user._id, - organization: membershipOrgToDelete.organization - }); + // check if user is a member and admin of the organization + // whose membership we wish to delete + const membershipOrg = await MembershipOrg.findOne({ + user: req.user._id, + organization: membershipOrgToDelete.organization + }); - if (!membershipOrg) { - throw new Error('Failed to validate organization membership'); - } + if (!membershipOrg) { + throw new Error('Failed to validate organization membership'); + } - if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) { - // user is not an admin member of the organization - throw new Error('Insufficient role for deleting organization membership'); - } + if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) { + // user is not an admin member of the organization + throw new Error('Insufficient role for deleting organization membership'); + } - // delete organization membership - const deletedMembershipOrg = await deleteMemberFromOrg({ - membershipOrgId: membershipOrgToDelete._id.toString() - }); + // delete organization membership + const deletedMembershipOrg = await deleteMemberFromOrg({ + membershipOrgId: membershipOrgToDelete._id.toString() + }); - await updateSubscriptionOrgQuantity({ - organizationId: membershipOrg.organization.toString() - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to delete organization membership' - }); - } + await updateSubscriptionOrgQuantity({ + organizationId: membershipOrg.organization.toString() + }); return membershipOrgToDelete; }; @@ -78,14 +68,6 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => { // [membershipOrgId] let membershipToChangeRole; - // try { - // } catch (err) { - // Sentry.setUser({ email: req.user.email }); - // Sentry.captureException(err); - // return res.status(400).send({ - // message: 'Failed to change organization membership role' - // }); - // } return res.status(200).send({ membershipOrg: membershipToChangeRole @@ -101,106 +83,98 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => { */ export const inviteUserToOrganization = async (req: Request, res: Response) => { let invitee, inviteeMembershipOrg, completeInviteLink; - try { - const { organizationId, inviteeEmail } = req.body; - const host = req.headers.host; - const siteUrl = `${req.protocol}://${host}`; + const { organizationId, inviteeEmail } = req.body; + const host = req.headers.host; + const siteUrl = `${req.protocol}://${host}`; - // validate membership - const membershipOrg = await MembershipOrg.findOne({ - user: req.user._id, - organization: organizationId - }); + // validate membership + const membershipOrg = await MembershipOrg.findOne({ + user: req.user._id, + organization: organizationId + }); - if (!membershipOrg) { - throw new Error('Failed to validate organization membership'); - } + if (!membershipOrg) { + throw new Error('Failed to validate organization membership'); + } - invitee = await User.findOne({ - email: inviteeEmail - }).select('+publicKey'); + invitee = await User.findOne({ + email: inviteeEmail + }).select('+publicKey'); - if (invitee) { - // case: invitee is an existing user + if (invitee) { + // case: invitee is an existing user - inviteeMembershipOrg = await MembershipOrg.findOne({ - user: invitee._id, - organization: organizationId - }); + inviteeMembershipOrg = await MembershipOrg.findOne({ + user: invitee._id, + organization: organizationId + }); - if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) { - throw new Error( - 'Failed to invite an existing member of the organization' - ); - } + if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) { + throw new Error( + 'Failed to invite an existing member of the organization' + ); + } - if (!inviteeMembershipOrg) { - - await new MembershipOrg({ - user: invitee, - inviteEmail: inviteeEmail, - organization: organizationId, - role: MEMBER, - status: INVITED - }).save(); - } - } else { - // check if invitee has been invited before - inviteeMembershipOrg = await MembershipOrg.findOne({ - inviteEmail: inviteeEmail, - organization: organizationId - }); + if (!inviteeMembershipOrg) { + + await new MembershipOrg({ + user: invitee, + inviteEmail: inviteeEmail, + organization: organizationId, + role: MEMBER, + status: INVITED + }).save(); + } + } else { + // check if invitee has been invited before + inviteeMembershipOrg = await MembershipOrg.findOne({ + inviteEmail: inviteeEmail, + organization: organizationId + }); - if (!inviteeMembershipOrg) { - // case: invitee has never been invited before + if (!inviteeMembershipOrg) { + // case: invitee has never been invited before - await new MembershipOrg({ - inviteEmail: inviteeEmail, - organization: organizationId, - role: MEMBER, - status: INVITED - }).save(); - } - } + await new MembershipOrg({ + inviteEmail: inviteeEmail, + organization: organizationId, + role: MEMBER, + status: INVITED + }).save(); + } + } - const organization = await Organization.findOne({ _id: organizationId }); + const organization = await Organization.findOne({ _id: organizationId }); - if (organization) { + if (organization) { - const token = await TokenService.createToken({ - type: TOKEN_EMAIL_ORG_INVITATION, - email: inviteeEmail, - organizationId: organization._id - }); + const token = await TokenService.createToken({ + type: TOKEN_EMAIL_ORG_INVITATION, + email: inviteeEmail, + organizationId: organization._id + }); - await sendMail({ - template: 'organizationInvitation.handlebars', - subjectLine: 'Infisical organization invitation', - recipients: [inviteeEmail], - substitutions: { - inviterFirstName: req.user.firstName, - inviterEmail: req.user.email, - organizationName: organization.name, - email: inviteeEmail, - organizationId: organization._id.toString(), - token, - callback_url: (await getSiteURL()) + '/signupinvite' - } - }); + await sendMail({ + template: 'organizationInvitation.handlebars', + subjectLine: 'Infisical organization invitation', + recipients: [inviteeEmail], + substitutions: { + inviterFirstName: req.user.firstName, + inviterEmail: req.user.email, + organizationName: organization.name, + email: inviteeEmail, + organizationId: organization._id.toString(), + token, + callback_url: (await getSiteURL()) + '/signupinvite' + } + }); - if (!(await getSmtpConfigured())) { - completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}` - } - } + if (!(await getSmtpConfigured())) { + completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}` + } + } - await updateSubscriptionOrgQuantity({ organizationId }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to send organization invite' - }); - } + await updateSubscriptionOrgQuantity({ organizationId }); return res.status(200).send({ message: `Sent an invite link to ${req.body.inviteeEmail}`, @@ -216,70 +190,62 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => { * @returns */ export const verifyUserToOrganization = async (req: Request, res: Response) => { - let user, token; - try { - const { - email, - organizationId, - code - } = req.body; + let user; + const { + email, + organizationId, + code + } = req.body; - user = await User.findOne({ email }).select('+publicKey'); + user = await User.findOne({ email }).select('+publicKey'); - const membershipOrg = await MembershipOrg.findOne({ - inviteEmail: email, - status: INVITED, - organization: new Types.ObjectId(organizationId) - }); + const membershipOrg = await MembershipOrg.findOne({ + inviteEmail: email, + status: INVITED, + organization: new Types.ObjectId(organizationId) + }); - if (!membershipOrg) - throw new Error('Failed to find any invitations for email'); + if (!membershipOrg) + throw new Error('Failed to find any invitations for email'); - await TokenService.validateToken({ - type: TOKEN_EMAIL_ORG_INVITATION, - email, - organizationId: membershipOrg.organization, - token: code - }); + await TokenService.validateToken({ + type: TOKEN_EMAIL_ORG_INVITATION, + email, + organizationId: membershipOrg.organization, + token: code + }); - if (user && user?.publicKey) { - // case: user has already completed account - // membership can be approved and redirected to login/dashboard - membershipOrg.status = ACCEPTED; - await membershipOrg.save(); - - await updateSubscriptionOrgQuantity({ - organizationId - }); + if (user && user?.publicKey) { + // case: user has already completed account + // membership can be approved and redirected to login/dashboard + membershipOrg.status = ACCEPTED; + await membershipOrg.save(); + + await updateSubscriptionOrgQuantity({ + organizationId + }); - return res.status(200).send({ - message: 'Successfully verified email', - user, - }); - } + return res.status(200).send({ + message: 'Successfully verified email', + user, + }); + } - if (!user) { - // initialize user account - user = await new User({ - email - }).save(); - } + if (!user) { + // initialize user account + user = await new User({ + email + }).save(); + } - // generate temporary signup token - token = createToken({ - payload: { - userId: user._id.toString() - }, - expiresIn: await getJwtSignupLifetime(), - secret: await getJwtSignupSecret() - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - error: 'Failed email magic link verification for organization invitation' - }); - } + // generate temporary signup token + const token = createToken({ + payload: { + userId: user._id.toString() + }, + expiresIn: await getJwtSignupLifetime(), + secret: await getJwtSignupSecret() + }); return res.status(200).send({ message: 'Successfully verified email', diff --git a/backend/src/controllers/v1/organizationController.ts b/backend/src/controllers/v1/organizationController.ts index b9fbc9f4b8..f0c73d75c6 100644 --- a/backend/src/controllers/v1/organizationController.ts +++ b/backend/src/controllers/v1/organizationController.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import Stripe from 'stripe'; import { @@ -15,21 +14,12 @@ import _ from 'lodash'; import { getStripeSecretKey, getSiteURL } from '../../config'; export const getOrganizations = async (req: Request, res: Response) => { - let organizations; - try { - organizations = ( - await MembershipOrg.find({ - user: req.user._id, - status: ACCEPTED - }).populate('organization') - ).map((m) => m.organization); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get organizations' - }); - } + const organizations = ( + await MembershipOrg.find({ + user: req.user._id, + status: ACCEPTED + }).populate('organization') + ).map((m) => m.organization); return res.status(200).send({ organizations @@ -44,33 +34,24 @@ export const getOrganizations = async (req: Request, res: Response) => { * @returns */ export const createOrganization = async (req: Request, res: Response) => { - let organization; - try { - const { organizationName } = req.body; + const { organizationName } = req.body; - if (organizationName.length < 1) { - throw new Error('Organization names must be at least 1-character long'); - } + if (organizationName.length < 1) { + throw new Error('Organization names must be at least 1-character long'); + } - // create organization and add user as member - organization = await create({ - email: req.user.email, - name: organizationName - }); + // create organization and add user as member + const organization = await create({ + email: req.user.email, + name: organizationName + }); - await addMembershipsOrg({ - userIds: [req.user._id.toString()], - organizationId: organization._id.toString(), - roles: [OWNER], - statuses: [ACCEPTED] - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to create organization' - }); - } + await addMembershipsOrg({ + userIds: [req.user._id.toString()], + organizationId: organization._id.toString(), + roles: [OWNER], + statuses: [ACCEPTED] + }); return res.status(200).send({ organization @@ -84,17 +65,7 @@ export const createOrganization = async (req: Request, res: Response) => { * @returns */ export const getOrganization = async (req: Request, res: Response) => { - let organization; - try { - organization = req.organization - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to find organization' - }); - } - + const organization = req.organization return res.status(200).send({ organization }); @@ -107,20 +78,11 @@ export const getOrganization = async (req: Request, res: Response) => { * @returns */ export const getOrganizationMembers = async (req: Request, res: Response) => { - let users; - try { - const { organizationId } = req.params; + const { organizationId } = req.params; - users = await MembershipOrg.find({ - organization: organizationId - }).populate('user', '+publicKey'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get organization members' - }); - } + const users = await MembershipOrg.find({ + organization: organizationId + }).populate('user', '+publicKey'); return res.status(200).send({ users @@ -137,35 +99,26 @@ export const getOrganizationWorkspaces = async ( req: Request, res: Response ) => { - let workspaces; - try { - const { organizationId } = req.params; + const { organizationId } = req.params; - const workspacesSet = new Set( - ( - await Workspace.find( - { - organization: organizationId - }, - '_id' - ) - ).map((w) => w._id.toString()) - ); + const workspacesSet = new Set( + ( + await Workspace.find( + { + organization: organizationId + }, + '_id' + ) + ).map((w) => w._id.toString()) + ); - workspaces = ( - await Membership.find({ - user: req.user._id - }).populate('workspace') - ) - .filter((m) => workspacesSet.has(m.workspace._id.toString())) - .map((m) => m.workspace); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get my workspaces' - }); - } + const workspaces = ( + await Membership.find({ + user: req.user._id + }).populate('workspace') + ) + .filter((m) => workspacesSet.has(m.workspace._id.toString())) + .map((m) => m.workspace); return res.status(200).send({ workspaces @@ -179,29 +132,20 @@ export const getOrganizationWorkspaces = async ( * @returns */ export const changeOrganizationName = async (req: Request, res: Response) => { - let organization; - try { - const { organizationId } = req.params; - const { name } = req.body; + const { organizationId } = req.params; + const { name } = req.body; - organization = await Organization.findOneAndUpdate( - { - _id: organizationId - }, - { - name - }, - { - new: true - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to change organization name' - }); - } + const organization = await Organization.findOneAndUpdate( + { + _id: organizationId + }, + { + name + }, + { + new: true + } + ); return res.status(200).send({ message: 'Successfully changed organization name', @@ -219,20 +163,11 @@ export const getOrganizationIncidentContacts = async ( req: Request, res: Response ) => { - let incidentContactsOrg; - try { - const { organizationId } = req.params; + const { organizationId } = req.params; - incidentContactsOrg = await IncidentContactOrg.find({ - organization: organizationId - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get organization incident contacts' - }); - } + const incidentContactsOrg = await IncidentContactOrg.find({ + organization: organizationId + }); return res.status(200).send({ incidentContactsOrg @@ -249,23 +184,14 @@ export const addOrganizationIncidentContact = async ( req: Request, res: Response ) => { - let incidentContactOrg; - try { - const { organizationId } = req.params; - const { email } = req.body; + const { organizationId } = req.params; + const { email } = req.body; - incidentContactOrg = await IncidentContactOrg.findOneAndUpdate( - { email, organization: organizationId }, - { email, organization: organizationId }, - { upsert: true, new: true } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to add incident contact for organization' - }); - } + const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate( + { email, organization: organizationId }, + { email, organization: organizationId }, + { upsert: true, new: true } + ); return res.status(200).send({ incidentContactOrg @@ -282,22 +208,13 @@ export const deleteOrganizationIncidentContact = async ( req: Request, res: Response ) => { - let incidentContactOrg; - try { - const { organizationId } = req.params; - const { email } = req.body; + const { organizationId } = req.params; + const { email } = req.body; - incidentContactOrg = await IncidentContactOrg.findOneAndDelete({ - email, - organization: organizationId - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to delete organization incident contact' - }); - } + const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({ + email, + organization: organizationId + }); return res.status(200).send({ message: 'Successfully deleted organization incident contact', @@ -317,41 +234,33 @@ export const createOrganizationPortalSession = async ( res: Response ) => { let session; - try { - const stripe = new Stripe(await getStripeSecretKey(), { - apiVersion: '2022-08-01' - }); + const stripe = new Stripe(await getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); - // check if there is a payment method on file - const paymentMethods = await stripe.paymentMethods.list({ - customer: req.organization.customerId, - type: 'card' - }); - - if (paymentMethods.data.length < 1) { - // case: no payment method on file - session = await stripe.checkout.sessions.create({ - customer: req.organization.customerId, - mode: 'setup', - payment_method_types: ['card'], - success_url: (await getSiteURL()) + '/dashboard', - cancel_url: (await getSiteURL()) + '/dashboard' - }); - } else { - session = await stripe.billingPortal.sessions.create({ - customer: req.organization.customerId, - return_url: (await getSiteURL()) + '/dashboard' - }); - } + // check if there is a payment method on file + const paymentMethods = await stripe.paymentMethods.list({ + customer: req.organization.customerId, + type: 'card' + }); + + if (paymentMethods.data.length < 1) { + // case: no payment method on file + session = await stripe.checkout.sessions.create({ + customer: req.organization.customerId, + mode: 'setup', + payment_method_types: ['card'], + success_url: (await getSiteURL()) + '/dashboard', + cancel_url: (await getSiteURL()) + '/dashboard' + }); + } else { + session = await stripe.billingPortal.sessions.create({ + customer: req.organization.customerId, + return_url: (await getSiteURL()) + '/dashboard' + }); + } - return res.status(200).send({ url: session.url }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to redirect to organization billing portal' - }); - } + return res.status(200).send({ url: session.url }); }; /** @@ -364,22 +273,13 @@ export const getOrganizationSubscriptions = async ( req: Request, res: Response ) => { - let subscriptions; - try { - const stripe = new Stripe(await getStripeSecretKey(), { - apiVersion: '2022-08-01' - }); - - subscriptions = await stripe.subscriptions.list({ - customer: req.organization.customerId - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get organization subscriptions' - }); - } + const stripe = new Stripe(await getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); + + const subscriptions = await stripe.subscriptions.list({ + customer: req.organization.customerId + }); return res.status(200).send({ subscriptions @@ -425,4 +325,4 @@ export const getOrganizationMembersAndTheirWorkspaces = async ( }); return res.json(userToWorkspaceIds); -}; \ No newline at end of file +}; diff --git a/backend/src/controllers/v1/passwordController.ts b/backend/src/controllers/v1/passwordController.ts index 85c2445053..5ce092038d 100644 --- a/backend/src/controllers/v1/passwordController.ts +++ b/backend/src/controllers/v1/passwordController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; // eslint-disable-next-line @typescript-eslint/no-var-requires const jsrp = require('jsrp'); import * as bigintConversion from 'bigint-conversion'; @@ -19,41 +18,32 @@ import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../conf * @returns */ export const emailPasswordReset = async (req: Request, res: Response) => { - let email: string; - try { - email = req.body.email; + const email = req.body.email; - const user = await User.findOne({ email }).select('+publicKey'); - if (!user || !user?.publicKey) { - // case: user has already completed account + const user = await User.findOne({ email }).select('+publicKey'); + if (!user || !user?.publicKey) { + // case: user has already completed account - return res.status(403).send({ - error: 'Failed to send email verification for password reset' - }); - } - - const token = await TokenService.createToken({ - type: TOKEN_EMAIL_PASSWORD_RESET, - email - }); - - await sendMail({ - template: 'passwordReset.handlebars', - subjectLine: 'Infisical password reset', - recipients: [email], - substitutions: { - email, - token, - callback_url: (await getSiteURL()) + '/password-reset' - } - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to send email for account recovery' - }); - } + return res.status(403).send({ + error: 'Failed to send email verification for password reset' + }); + } + + const token = await TokenService.createToken({ + type: TOKEN_EMAIL_PASSWORD_RESET, + email + }); + + await sendMail({ + template: 'passwordReset.handlebars', + subjectLine: 'Infisical password reset', + recipients: [email], + substitutions: { + email, + token, + callback_url: (await getSiteURL()) + '/password-reset' + } + }); return res.status(200).send({ message: `Sent an email for account recovery to ${email}` @@ -67,40 +57,31 @@ export const emailPasswordReset = async (req: Request, res: Response) => { * @returns */ export const emailPasswordResetVerify = async (req: Request, res: Response) => { - let user, token; - try { - const { email, code } = req.body; + const { email, code } = req.body; - user = await User.findOne({ email }).select('+publicKey'); - if (!user || !user?.publicKey) { - // case: user doesn't exist with email [email] or - // hasn't even completed their account - return res.status(403).send({ - error: 'Failed email verification for password reset' - }); - } - - await TokenService.validateToken({ - type: TOKEN_EMAIL_PASSWORD_RESET, - email, - token: code - }); + const user = await User.findOne({ email }).select('+publicKey'); + if (!user || !user?.publicKey) { + // case: user doesn't exist with email [email] or + // hasn't even completed their account + return res.status(403).send({ + error: 'Failed email verification for password reset' + }); + } + + await TokenService.validateToken({ + type: TOKEN_EMAIL_PASSWORD_RESET, + email, + token: code + }); - // generate temporary password-reset token - token = createToken({ - payload: { - userId: user._id.toString() - }, - expiresIn: await getJwtSignupLifetime(), - secret: await getJwtSignupSecret() - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed email verification for password reset' - }); - } + // generate temporary password-reset token + const token = createToken({ + payload: { + userId: user._id.toString() + }, + expiresIn: await getJwtSignupLifetime(), + secret: await getJwtSignupSecret() + }); return res.status(200).send({ message: 'Successfully verified email', @@ -117,43 +98,35 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => { */ export const srp1 = async (req: Request, res: Response) => { // return salt, serverPublicKey as part of first step of SRP protocol - try { - const { clientPublicKey } = req.body; - const user = await User.findOne({ - email: req.user.email - }).select('+salt +verifier'); + const { clientPublicKey } = req.body; + const user = await User.findOne({ + email: req.user.email + }).select('+salt +verifier'); - if (!user) throw new Error('Failed to find user'); + if (!user) throw new Error('Failed to find user'); - const server = new jsrp.server(); - server.init( - { - salt: user.salt, - verifier: user.verifier - }, - async () => { - // generate server-side public key - const serverPublicKey = server.getPublicKey(); + const server = new jsrp.server(); + server.init( + { + salt: user.salt, + verifier: user.verifier + }, + async () => { + // generate server-side public key + const serverPublicKey = server.getPublicKey(); - await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, { - email: req.user.email, - clientPublicKey: clientPublicKey, - serverBInt: bigintConversion.bigintToBuf(server.bInt), - }, { upsert: true, returnNewDocument: false }) + await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, { + email: req.user.email, + clientPublicKey: clientPublicKey, + serverBInt: bigintConversion.bigintToBuf(server.bInt), + }, { upsert: true, returnNewDocument: false }) - return res.status(200).send({ - serverPublicKey, - salt: user.salt - }); - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - error: 'Failed to start change password process' - }); - } + return res.status(200).send({ + serverPublicKey, + salt: user.salt + }); + } + ); }; /** @@ -165,80 +138,72 @@ export const srp1 = async (req: Request, res: Response) => { * @returns */ export const changePassword = async (req: Request, res: Response) => { - try { - const { - clientProof, - protectedKey, - protectedKeyIV, - protectedKeyTag, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier - } = req.body; + const { + clientProof, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier + } = req.body; - const user = await User.findOne({ - email: req.user.email - }).select('+salt +verifier'); + const user = await User.findOne({ + email: req.user.email + }).select('+salt +verifier'); - if (!user) throw new Error('Failed to find user'); + if (!user) throw new Error('Failed to find user'); - const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email }) + const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email }) - if (!loginSRPDetailFromDB) { - return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) - } + if (!loginSRPDetailFromDB) { + return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) + } - const server = new jsrp.server(); - server.init( - { - salt: user.salt, - verifier: user.verifier, - b: loginSRPDetailFromDB.serverBInt - }, - async () => { - server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); + const server = new jsrp.server(); + server.init( + { + salt: user.salt, + verifier: user.verifier, + b: loginSRPDetailFromDB.serverBInt + }, + async () => { + server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); - // compare server and client shared keys - if (server.checkClientProof(clientProof)) { - // change password + // compare server and client shared keys + if (server.checkClientProof(clientProof)) { + // change password - await User.findByIdAndUpdate( - req.user._id.toString(), - { - encryptionVersion: 2, - protectedKey, - protectedKeyIV, - protectedKeyTag, - encryptedPrivateKey, - iv: encryptedPrivateKeyIV, - tag: encryptedPrivateKeyTag, - salt, - verifier - }, - { - new: true - } - ); + await User.findByIdAndUpdate( + req.user._id.toString(), + { + encryptionVersion: 2, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag, + salt, + verifier + }, + { + new: true + } + ); - return res.status(200).send({ - message: 'Successfully changed password' - }); - } + return res.status(200).send({ + message: 'Successfully changed password' + }); + } - return res.status(400).send({ - error: 'Failed to change password. Try again?' - }); - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - error: 'Failed to change password. Try again?' - }); - } + return res.status(400).send({ + error: 'Failed to change password. Try again?' + }); + } + ); }; /** @@ -252,69 +217,61 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => { // requires verifying [clientProof] as part of second step of SRP protocol // as initiated in /srp1 - try { - const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = - req.body; - const user = await User.findOne({ - email: req.user.email - }).select('+salt +verifier'); + const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = + req.body; + const user = await User.findOne({ + email: req.user.email + }).select('+salt +verifier'); - if (!user) throw new Error('Failed to find user'); + if (!user) throw new Error('Failed to find user'); - const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email }) + const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email }) - if (!loginSRPDetailFromDB) { - return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) - } + if (!loginSRPDetailFromDB) { + return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) + } - const server = new jsrp.server(); - server.init( - { - salt: user.salt, - verifier: user.verifier, - b: loginSRPDetailFromDB.serverBInt - }, - async () => { - server.setClientPublicKey( - loginSRPDetailFromDB.clientPublicKey - ); + const server = new jsrp.server(); + server.init( + { + salt: user.salt, + verifier: user.verifier, + b: loginSRPDetailFromDB.serverBInt + }, + async () => { + server.setClientPublicKey( + loginSRPDetailFromDB.clientPublicKey + ); - // compare server and client shared keys - if (server.checkClientProof(clientProof)) { - // create new or replace backup private key + // compare server and client shared keys + if (server.checkClientProof(clientProof)) { + // create new or replace backup private key - const backupPrivateKey = await BackupPrivateKey.findOneAndUpdate( - { user: req.user._id }, - { - user: req.user._id, - encryptedPrivateKey, - iv, - tag, - salt, - verifier - }, - { upsert: true, new: true } - ).select('+user, encryptedPrivateKey'); + const backupPrivateKey = await BackupPrivateKey.findOneAndUpdate( + { user: req.user._id }, + { + user: req.user._id, + encryptedPrivateKey, + iv, + tag, + salt, + verifier + }, + { upsert: true, new: true } + ).select('+user, encryptedPrivateKey'); - // issue tokens - return res.status(200).send({ - message: 'Successfully updated backup private key', - backupPrivateKey - }); - } + // issue tokens + return res.status(200).send({ + message: 'Successfully updated backup private key', + backupPrivateKey + }); + } - return res.status(400).send({ - message: 'Failed to update backup private key' - }); - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to update backup private key' - }); - } + return res.status(400).send({ + message: 'Failed to update backup private key' + }); + } + ); }; /** @@ -324,20 +281,11 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => { * @returns */ export const getBackupPrivateKey = async (req: Request, res: Response) => { - let backupPrivateKey; - try { - backupPrivateKey = await BackupPrivateKey.findOne({ - user: req.user._id - }).select('+encryptedPrivateKey +iv +tag'); + const backupPrivateKey = await BackupPrivateKey.findOne({ + user: req.user._id + }).select('+encryptedPrivateKey +iv +tag'); - if (!backupPrivateKey) throw new Error('Failed to find backup private key'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get backup private key' - }); - } + if (!backupPrivateKey) throw new Error('Failed to find backup private key'); return res.status(200).send({ backupPrivateKey @@ -345,44 +293,36 @@ export const getBackupPrivateKey = async (req: Request, res: Response) => { } export const resetPassword = async (req: Request, res: Response) => { - try { - const { - protectedKey, - protectedKeyIV, - protectedKeyTag, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier, - } = req.body; + const { + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier, + } = req.body; - await User.findByIdAndUpdate( - req.user._id.toString(), - { - encryptionVersion: 2, - protectedKey, - protectedKeyIV, - protectedKeyTag, - encryptedPrivateKey, - iv: encryptedPrivateKeyIV, - tag: encryptedPrivateKeyTag, - salt, - verifier - }, - { - new: true - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get backup private key' - }); - } + await User.findByIdAndUpdate( + req.user._id.toString(), + { + encryptionVersion: 2, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag, + salt, + verifier + }, + { + new: true + } + ); return res.status(200).send({ message: 'Successfully reset password' }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/v1/secretController.ts b/backend/src/controllers/v1/secretController.ts index 316f6cc3e5..e3d6818ae0 100644 --- a/backend/src/controllers/v1/secretController.ts +++ b/backend/src/controllers/v1/secretController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { Types } from 'mongoose'; import { Key, Secret } from '../../models'; import { @@ -37,66 +36,56 @@ interface PushSecret { */ export const pushSecrets = async (req: Request, res: Response) => { // upload (encrypted) secrets to workspace with id [workspaceId] + const postHogClient = await TelemetryService.getPostHogClient(); + let { secrets }: { secrets: PushSecret[] } = req.body; + const { keys, environment, channel } = req.body; + const { workspaceId } = req.params; - try { - const postHogClient = await TelemetryService.getPostHogClient(); - let { secrets }: { secrets: PushSecret[] } = req.body; - const { keys, environment, channel } = req.body; - const { workspaceId } = req.params; + // validate environment + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { + throw new Error('Failed to validate environment'); + } - // validate environment - const workspaceEnvs = req.membership.workspace.environments; - if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { - throw new Error('Failed to validate environment'); - } + // sanitize secrets + secrets = secrets.filter( + (s: PushSecret) => s.ciphertextKey !== '' && s.ciphertextValue !== '' + ); - // sanitize secrets - secrets = secrets.filter( - (s: PushSecret) => s.ciphertextKey !== '' && s.ciphertextValue !== '' - ); + await push({ + userId: req.user._id, + workspaceId, + environment, + secrets + }); - await push({ - userId: req.user._id, - workspaceId, - environment, - secrets - }); + await pushKeys({ + userId: req.user._id, + workspaceId, + keys + }); + + + if (postHogClient) { + postHogClient.capture({ + event: 'secrets pushed', + distinctId: req.user.email, + properties: { + numberOfSecrets: secrets.length, + environment, + workspaceId, + channel: channel ? channel : 'cli' + } + }); + } - await pushKeys({ - userId: req.user._id, - workspaceId, - keys - }); - - - if (postHogClient) { - postHogClient.capture({ - event: 'secrets pushed', - distinctId: req.user.email, - properties: { - numberOfSecrets: secrets.length, - environment, - workspaceId, - channel: channel ? channel : 'cli' - } - }); - } - - // trigger event - push secrets - EventService.handleEvent({ - event: eventPushSecrets({ - workspaceId: new Types.ObjectId(workspaceId), - environment - }) - }); - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to upload workspace secrets' - }); - } + // trigger event - push secrets + EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: new Types.ObjectId(workspaceId), + environment + }) + }); return res.status(200).send({ message: 'Successfully uploaded workspace secrets' @@ -111,59 +100,50 @@ export const pushSecrets = async (req: Request, res: Response) => { * @returns */ export const pullSecrets = async (req: Request, res: Response) => { - let secrets; - let key; - try { - const postHogClient = await TelemetryService.getPostHogClient(); - const environment: string = req.query.environment as string; - const channel: string = req.query.channel as string; - const { workspaceId } = req.params; + const postHogClient = await TelemetryService.getPostHogClient(); + const environment: string = req.query.environment as string; + const channel: string = req.query.channel as string; + const { workspaceId } = req.params; - // validate environment - const workspaceEnvs = req.membership.workspace.environments; - if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { - throw new Error('Failed to validate environment'); - } + // validate environment + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { + throw new Error('Failed to validate environment'); + } - secrets = await pull({ - userId: req.user._id.toString(), - workspaceId, - environment, - channel: channel ? channel : 'cli', - ipAddress: req.ip - }); + let secrets = await pull({ + userId: req.user._id.toString(), + workspaceId, + environment, + channel: channel ? channel : 'cli', + ipAddress: req.ip + }); - key = await Key.findOne({ - workspace: workspaceId, - receiver: req.user._id - }) - .sort({ createdAt: -1 }) - .populate('sender', '+publicKey'); - - if (channel !== 'cli') { - secrets = reformatPullSecrets({ secrets }); - } + const key = await Key.findOne({ + workspace: workspaceId, + receiver: req.user._id + }) + .sort({ createdAt: -1 }) + .populate('sender', '+publicKey'); + + if (channel !== 'cli') { + // FIX: Fix this any + secrets = reformatPullSecrets({ secrets }) as any; + } - if (postHogClient) { - // capture secrets pushed event in production - postHogClient.capture({ - distinctId: req.user.email, - event: 'secrets pulled', - properties: { - numberOfSecrets: secrets.length, - environment, - workspaceId, - channel: channel ? channel : 'cli' - } - }); - } - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to pull workspace secrets' - }); - } + if (postHogClient) { + // capture secrets pushed event in production + postHogClient.capture({ + distinctId: req.user.email, + event: 'secrets pulled', + properties: { + numberOfSecrets: secrets.length, + environment, + workspaceId, + channel: channel ? channel : 'cli' + } + }); + } return res.status(200).send({ secrets, @@ -180,61 +160,51 @@ export const pullSecrets = async (req: Request, res: Response) => { * @returns */ export const pullSecretsServiceToken = async (req: Request, res: Response) => { - let secrets; - let key; - try { - const postHogClient = await TelemetryService.getPostHogClient(); - const environment: string = req.query.environment as string; - const channel: string = req.query.channel as string; - const { workspaceId } = req.params; + const postHogClient = await TelemetryService.getPostHogClient(); + const environment: string = req.query.environment as string; + const channel: string = req.query.channel as string; + const { workspaceId } = req.params; - // validate environment - const workspaceEnvs = req.membership.workspace.environments; - if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { - throw new Error('Failed to validate environment'); - } + // validate environment + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { + throw new Error('Failed to validate environment'); + } - secrets = await pull({ - userId: req.serviceToken.user._id.toString(), - workspaceId, - environment, - channel: 'cli', - ipAddress: req.ip - }); + const secrets = await pull({ + userId: req.serviceToken.user._id.toString(), + workspaceId, + environment, + channel: 'cli', + ipAddress: req.ip + }); - key = { - encryptedKey: req.serviceToken.encryptedKey, - nonce: req.serviceToken.nonce, - sender: { - publicKey: req.serviceToken.publicKey - }, - receiver: req.serviceToken.user, - workspace: req.serviceToken.workspace - }; + const key = { + encryptedKey: req.serviceToken.encryptedKey, + nonce: req.serviceToken.nonce, + sender: { + publicKey: req.serviceToken.publicKey + }, + receiver: req.serviceToken.user, + workspace: req.serviceToken.workspace + }; - if (postHogClient) { - // capture secrets pulled event in production - postHogClient.capture({ - distinctId: req.serviceToken.user.email, - event: 'secrets pulled', - properties: { - numberOfSecrets: secrets.length, - environment, - workspaceId, - channel: channel ? channel : 'cli' - } - }); - } - } catch (err) { - Sentry.setUser({ email: req.serviceToken.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to pull workspace secrets' - }); - } + if (postHogClient) { + // capture secrets pulled event in production + postHogClient.capture({ + distinctId: req.serviceToken.user.email, + event: 'secrets pulled', + properties: { + numberOfSecrets: secrets.length, + environment, + workspaceId, + channel: channel ? channel : 'cli' + } + }); + } return res.status(200).send({ secrets: reformatPullSecrets({ secrets }), key }); -}; \ No newline at end of file +}; diff --git a/backend/src/controllers/v1/signupController.ts b/backend/src/controllers/v1/signupController.ts index 681b654599..9fede9b1a4 100644 --- a/backend/src/controllers/v1/signupController.ts +++ b/backend/src/controllers/v1/signupController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { User } from '../../models'; import { sendEmailVerification, @@ -17,29 +16,20 @@ import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpC * @returns */ export const beginEmailSignup = async (req: Request, res: Response) => { - let email: string; - try { - email = req.body.email; + const email = req.body.email; - const user = await User.findOne({ email }).select('+publicKey'); - if (user && user?.publicKey) { - // case: user has already completed account + const user = await User.findOne({ email }).select('+publicKey'); + if (user && user?.publicKey) { + // case: user has already completed account - return res.status(403).send({ - error: 'Failed to send email verification code for complete account' - }); - } + return res.status(403).send({ + error: 'Failed to send email verification code for complete account' + }); + } + + // send send verification email + await sendEmailVerification({ email }); - // send send verification email - await sendEmailVerification({ email }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - error: 'Failed to send email verification code' - }); - } - return res.status(200).send({ message: `Sent an email verification code to ${email}` }); @@ -54,55 +44,47 @@ export const beginEmailSignup = async (req: Request, res: Response) => { */ export const verifyEmailSignup = async (req: Request, res: Response) => { let user, token; - try { - const { email, code } = req.body; + const { email, code } = req.body; - // initialize user account - user = await User.findOne({ email }).select('+publicKey'); - if (user && user?.publicKey) { - // case: user has already completed account - return res.status(403).send({ - error: 'Failed email verification for complete user' - }); - } + // initialize user account + user = await User.findOne({ email }).select('+publicKey'); + if (user && user?.publicKey) { + // case: user has already completed account + return res.status(403).send({ + error: 'Failed email verification for complete user' + }); + } - if (await getInviteOnlySignup()) { - // Only one user can create an account without being invited. The rest need to be invited in order to make an account - const userCount = await User.countDocuments({}) - if (userCount != 0) { - throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." }) - } - } + if (await getInviteOnlySignup()) { + // Only one user can create an account without being invited. The rest need to be invited in order to make an account + const userCount = await User.countDocuments({}) + if (userCount != 0) { + throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." }) + } + } - // verify email - if (await getSmtpConfigured()) { - await checkEmailVerification({ - email, - code - }); - } + // verify email + if (await getSmtpConfigured()) { + await checkEmailVerification({ + email, + code + }); + } - if (!user) { - user = await new User({ - email - }).save(); - } + if (!user) { + user = await new User({ + email + }).save(); + } - // generate temporary signup token - token = createToken({ - payload: { - userId: user._id.toString() - }, - expiresIn: await getJwtSignupLifetime(), - secret: await getJwtSignupSecret() - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - error: 'Failed email verification' - }); - } + // generate temporary signup token + token = createToken({ + payload: { + userId: user._id.toString() + }, + expiresIn: await getJwtSignupLifetime(), + secret: await getJwtSignupSecret() + }); return res.status(200).send({ message: 'Successfuly verified email', diff --git a/backend/src/controllers/v1/stripeController.ts b/backend/src/controllers/v1/stripeController.ts index 809107ad9c..4aa7adce98 100644 --- a/backend/src/controllers/v1/stripeController.ts +++ b/backend/src/controllers/v1/stripeController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import Stripe from 'stripe'; import { getStripeSecretKey, getStripeWebhookSecret } from '../../config'; @@ -10,26 +9,17 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../config'; * @returns */ export const handleWebhook = async (req: Request, res: Response) => { - let event; - try { - // check request for valid stripe signature - const stripe = new Stripe(await getStripeSecretKey(), { - apiVersion: '2022-08-01' - }); + // check request for valid stripe signature + const stripe = new Stripe(await getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); - const sig = req.headers['stripe-signature'] as string; - event = stripe.webhooks.constructEvent( - req.body, - sig, - await getStripeWebhookSecret() - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - error: 'Failed to process webhook' - }); - } + const sig = req.headers['stripe-signature'] as string; + const event = stripe.webhooks.constructEvent( + req.body, + sig, + await getStripeWebhookSecret() + ); switch (event.type) { case '': diff --git a/backend/src/controllers/v1/userActionController.ts b/backend/src/controllers/v1/userActionController.ts index c7a3c2337d..cba78c7b83 100644 --- a/backend/src/controllers/v1/userActionController.ts +++ b/backend/src/controllers/v1/userActionController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { UserAction } from '../../models'; /** @@ -11,28 +10,19 @@ import { UserAction } from '../../models'; export const addUserAction = async (req: Request, res: Response) => { // add/record new action [action] for user with id [req.user._id] - let userAction; - try { - const { action } = req.body; + const { action } = req.body; - userAction = await UserAction.findOneAndUpdate( - { - user: req.user._id, - action - }, - { user: req.user._id, action }, - { - new: true, - upsert: true - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to record user action' - }); - } + const userAction = await UserAction.findOneAndUpdate( + { + user: req.user._id, + action + }, + { user: req.user._id, action }, + { + new: true, + upsert: true + } + ); return res.status(200).send({ message: 'Successfully recorded user action', @@ -48,21 +38,12 @@ export const addUserAction = async (req: Request, res: Response) => { */ export const getUserAction = async (req: Request, res: Response) => { // get user action [action] for user with id [req.user._id] - let userAction; - try { - const action: string = req.query.action as string; + const action: string = req.query.action as string; - userAction = await UserAction.findOne({ - user: req.user._id, - action - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get user action' - }); - } + const userAction = await UserAction.findOne({ + user: req.user._id, + action + }); return res.status(200).send({ userAction diff --git a/backend/src/controllers/v1/workspaceController.ts b/backend/src/controllers/v1/workspaceController.ts index 2b0a89f431..92ae52b11f 100644 --- a/backend/src/controllers/v1/workspaceController.ts +++ b/backend/src/controllers/v1/workspaceController.ts @@ -1,5 +1,4 @@ import { Request, Response } from "express"; -import * as Sentry from "@sentry/node"; import { Workspace, Membership, @@ -24,27 +23,18 @@ import { ADMIN } from "../../variables"; * @returns */ export const getWorkspacePublicKeys = async (req: Request, res: Response) => { - let publicKeys; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - publicKeys = ( - await Membership.find({ - workspace: workspaceId, - }).populate<{ user: IUser }>("user", "publicKey") - ).map((member) => { - return { - publicKey: member.user.publicKey, - userId: member.user._id, - }; - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspace member public keys", - }); - } + const publicKeys = ( + await Membership.find({ + workspace: workspaceId, + }).populate<{ user: IUser }>("user", "publicKey") + ).map((member) => { + return { + publicKey: member.user.publicKey, + userId: member.user._id, + }; + }); return res.status(200).send({ publicKeys, @@ -58,20 +48,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => { * @returns */ export const getWorkspaceMemberships = async (req: Request, res: Response) => { - let users; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - users = await Membership.find({ - workspace: workspaceId, - }).populate("user", "+publicKey"); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspace members", - }); - } + const users = await Membership.find({ + workspace: workspaceId, + }).populate("user", "+publicKey"); return res.status(200).send({ users, @@ -85,20 +66,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => { * @returns */ export const getWorkspaces = async (req: Request, res: Response) => { - let workspaces; - try { - workspaces = ( - await Membership.find({ - user: req.user._id, - }).populate("workspace") - ).map((m) => m.workspace); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspaces", - }); - } + const workspaces = ( + await Membership.find({ + user: req.user._id, + }).populate("workspace") + ).map((m) => m.workspace); return res.status(200).send({ workspaces, @@ -112,20 +84,11 @@ export const getWorkspaces = async (req: Request, res: Response) => { * @returns */ export const getWorkspace = async (req: Request, res: Response) => { - let workspace; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - workspace = await Workspace.findOne({ - _id: workspaceId, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspace", - }); - } + const workspace = await Workspace.findOne({ + _id: workspaceId, + }); return res.status(200).send({ workspace, @@ -140,43 +103,34 @@ export const getWorkspace = async (req: Request, res: Response) => { * @returns */ export const createWorkspace = async (req: Request, res: Response) => { - let workspace; - try { - const { workspaceName, organizationId } = req.body; + const { workspaceName, organizationId } = req.body; - // validate organization membership - const membershipOrg = await MembershipOrg.findOne({ - user: req.user._id, - organization: organizationId, - }); + // validate organization membership + const membershipOrg = await MembershipOrg.findOne({ + user: req.user._id, + organization: organizationId, + }); - if (!membershipOrg) { - throw new Error("Failed to validate organization membership"); - } - - if (workspaceName.length < 1) { - throw new Error("Workspace names must be at least 1-character long"); - } - - // create workspace and add user as member - workspace = await create({ - name: workspaceName, - organizationId, - }); - - await addMemberships({ - userIds: [req.user._id], - workspaceId: workspace._id.toString(), - roles: [ADMIN], - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to create workspace", - }); + if (!membershipOrg) { + throw new Error("Failed to validate organization membership"); } + if (workspaceName.length < 1) { + throw new Error("Workspace names must be at least 1-character long"); + } + + // create workspace and add user as member + const workspace = await create({ + name: workspaceName, + organizationId, + }); + + await addMemberships({ + userIds: [req.user._id], + workspaceId: workspace._id.toString(), + roles: [ADMIN], + }); + return res.status(200).send({ workspace, }); @@ -189,20 +143,12 @@ export const createWorkspace = async (req: Request, res: Response) => { * @returns */ export const deleteWorkspace = async (req: Request, res: Response) => { - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - // delete workspace - await deleteWork({ - id: workspaceId, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to delete workspace", - }); - } + // delete workspace + await deleteWork({ + id: workspaceId, + }); return res.status(200).send({ message: "Successfully deleted workspace", @@ -216,29 +162,20 @@ export const deleteWorkspace = async (req: Request, res: Response) => { * @returns */ export const changeWorkspaceName = async (req: Request, res: Response) => { - let workspace; - try { - const { workspaceId } = req.params; - const { name } = req.body; + const { workspaceId } = req.params; + const { name } = req.body; - workspace = await Workspace.findOneAndUpdate( - { - _id: workspaceId, - }, - { - name, - }, - { - new: true, - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to change workspace name", - }); - } + const workspace = await Workspace.findOneAndUpdate( + { + _id: workspaceId, + }, + { + name, + }, + { + new: true, + } + ); return res.status(200).send({ message: "Successfully changed workspace name", @@ -253,20 +190,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => { * @returns */ export const getWorkspaceIntegrations = async (req: Request, res: Response) => { - let integrations; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - integrations = await Integration.find({ - workspace: workspaceId, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspace integrations", - }); - } + const integrations = await Integration.find({ + workspace: workspaceId, + }); return res.status(200).send({ integrations, @@ -283,20 +211,11 @@ export const getWorkspaceIntegrationAuthorizations = async ( req: Request, res: Response ) => { - let authorizations; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - authorizations = await IntegrationAuth.find({ - workspace: workspaceId, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspace integration authorizations", - }); - } + const authorizations = await IntegrationAuth.find({ + workspace: workspaceId, + }); return res.status(200).send({ authorizations, @@ -313,21 +232,12 @@ export const getWorkspaceServiceTokens = async ( req: Request, res: Response ) => { - let serviceTokens; - try { - const { workspaceId } = req.params; - // ?? FIX. - serviceTokens = await ServiceToken.find({ - user: req.user._id, - workspace: workspaceId, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspace service tokens", - }); - } + const { workspaceId } = req.params; + // ?? FIX. + const serviceTokens = await ServiceToken.find({ + user: req.user._id, + workspace: workspaceId, + }); return res.status(200).send({ serviceTokens, diff --git a/backend/src/controllers/v2/apiKeyDataController.ts b/backend/src/controllers/v2/apiKeyDataController.ts index 86533c733a..73fd1afbff 100644 --- a/backend/src/controllers/v2/apiKeyDataController.ts +++ b/backend/src/controllers/v2/apiKeyDataController.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import crypto from 'crypto'; import bcrypt from 'bcrypt'; @@ -14,18 +13,9 @@ import { getSaltRounds } from '../../config'; * @returns */ export const getAPIKeyData = async (req: Request, res: Response) => { - let apiKeyData; - try { - apiKeyData = await APIKeyData.find({ - user: req.user._id - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get API key data' - }); - } + const apiKeyData = await APIKeyData.find({ + user: req.user._id + }); return res.status(200).send({ apiKeyData @@ -38,39 +28,30 @@ export const getAPIKeyData = async (req: Request, res: Response) => { * @param res */ export const createAPIKeyData = async (req: Request, res: Response) => { - let apiKey, apiKeyData; - try { - const { name, expiresIn } = req.body; - - const secret = crypto.randomBytes(16).toString('hex'); - const secretHash = await bcrypt.hash(secret, await getSaltRounds()); - - const expiresAt = new Date(); - expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); - - apiKeyData = await new APIKeyData({ - name, - lastUsed: new Date(), - expiresAt, - user: req.user._id, - secretHash - }).save(); - - // return api key data without sensitive data - apiKeyData = await APIKeyData.findById(apiKeyData._id); - - if (!apiKeyData) throw new Error('Failed to find API key data'); - - apiKey = `ak.${apiKeyData._id.toString()}.${secret}`; - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to API key data' - }); - } + const { name, expiresIn } = req.body; + const secret = crypto.randomBytes(16).toString('hex'); + const secretHash = await bcrypt.hash(secret, await getSaltRounds()); + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); + + let apiKeyData = await new APIKeyData({ + name, + lastUsed: new Date(), + expiresAt, + user: req.user._id, + secretHash + }).save(); + + // return api key data without sensitive data + // FIX: fix this any + apiKeyData = await APIKeyData.findById(apiKeyData._id) as any + + if (!apiKeyData) throw new Error('Failed to find API key data'); + + const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`; + return res.status(200).send({ apiKey, apiKeyData @@ -84,21 +65,10 @@ export const createAPIKeyData = async (req: Request, res: Response) => { * @returns */ export const deleteAPIKeyData = async (req: Request, res: Response) => { - let apiKeyData; - try { - const { apiKeyDataId } = req.params; - - apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId); - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to delete API key data' - }); - } + const { apiKeyDataId } = req.params; + const apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId); return res.status(200).send({ apiKeyData }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/v2/authController.ts b/backend/src/controllers/v2/authController.ts index fbc4ecb401..62735d05ca 100644 --- a/backend/src/controllers/v2/authController.ts +++ b/backend/src/controllers/v2/authController.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; -import * as Sentry from '@sentry/node'; import * as bigintConversion from 'bigint-conversion'; const jsrp = require('jsrp'); import { User, LoginSRPDetail } from '../../models'; @@ -35,47 +34,40 @@ declare module 'jsonwebtoken' { * @returns */ export const login1 = async (req: Request, res: Response) => { - try { - const { - email, - clientPublicKey - }: { email: string; clientPublicKey: string } = req.body; + const { + email, + clientPublicKey + }: { email: string; clientPublicKey: string } = req.body; - const user = await User.findOne({ - email - }).select('+salt +verifier'); + const user = await User.findOne({ + email + }).select('+salt +verifier'); - if (!user) throw new Error('Failed to find user'); + if (!user) throw new Error('Failed to find user'); - const server = new jsrp.server(); - server.init( - { - salt: user.salt, - verifier: user.verifier - }, - async () => { - // generate server-side public key - const serverPublicKey = server.getPublicKey(); + const server = new jsrp.server(); + server.init( + { + salt: user.salt, + verifier: user.verifier + }, + async () => { + // generate server-side public key + const serverPublicKey = server.getPublicKey(); - await LoginSRPDetail.findOneAndReplace({ email: email }, { - email: email, - clientPublicKey: clientPublicKey, - serverBInt: bigintConversion.bigintToBuf(server.bInt), - }, { upsert: true, returnNewDocument: false }); + await LoginSRPDetail.findOneAndReplace({ email: email }, { + email: email, + clientPublicKey: clientPublicKey, + serverBInt: bigintConversion.bigintToBuf(server.bInt), + }, { upsert: true, returnNewDocument: false }); - return res.status(200).send({ - serverPublicKey, - salt: user.salt - }); - } - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to start authentication process' - }); - } + return res.status(200).send({ + serverPublicKey, + salt: user.salt + }); + } + ); + }; /** @@ -86,149 +78,140 @@ export const login1 = async (req: Request, res: Response) => { * @returns */ export const login2 = async (req: Request, res: Response) => { - try { + if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' }); - if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' }); + const { email, clientProof } = req.body; + const user = await User.findOne({ + email + }).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag'); - const { email, clientProof } = req.body; - const user = await User.findOne({ - email - }).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag'); + if (!user) throw new Error('Failed to find user'); - if (!user) throw new Error('Failed to find user'); + const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email }) - const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email }) + if (!loginSRPDetail) { + return BadRequestError(Error("Failed to find login details for SRP")) + } - if (!loginSRPDetail) { - return BadRequestError(Error("Failed to find login details for SRP")) - } + const server = new jsrp.server(); + server.init( + { + salt: user.salt, + verifier: user.verifier, + b: loginSRPDetail.serverBInt + }, + async () => { + server.setClientPublicKey(loginSRPDetail.clientPublicKey); - const server = new jsrp.server(); - server.init( - { - salt: user.salt, - verifier: user.verifier, - b: loginSRPDetail.serverBInt - }, - async () => { - server.setClientPublicKey(loginSRPDetail.clientPublicKey); + // compare server and client shared keys + if (server.checkClientProof(clientProof)) { - // compare server and client shared keys - if (server.checkClientProof(clientProof)) { + if (user.isMfaEnabled) { + // case: user has MFA enabled - if (user.isMfaEnabled) { - // case: user has MFA enabled - - // generate temporary MFA token - const token = createToken({ - payload: { - userId: user._id.toString() - }, - expiresIn: await getJwtMfaLifetime(), - secret: await getJwtMfaSecret() - }); - - const code = await TokenService.createToken({ - type: TOKEN_EMAIL_MFA, - email - }); - - // send MFA code [code] to [email] - await sendMail({ - template: 'emailMfa.handlebars', - subjectLine: 'Infisical MFA code', - recipients: [email], - substitutions: { - code - } - }); - - return res.status(200).send({ - mfaEnabled: true, - token - }); - } - - await checkUserDevice({ - user, - ip: req.ip, - userAgent: req.headers['user-agent'] ?? '' + // generate temporary MFA token + const token = createToken({ + payload: { + userId: user._id.toString() + }, + expiresIn: await getJwtMfaLifetime(), + secret: await getJwtMfaSecret() }); - // issue tokens - const tokens = await issueAuthTokens({ userId: user._id.toString() }); - - // store (refresh) token in httpOnly cookie - res.cookie('jid', tokens.refreshToken, { - httpOnly: true, - path: '/', - sameSite: 'strict', - secure: await getHttpsEnabled() + const code = await TokenService.createToken({ + type: TOKEN_EMAIL_MFA, + email }); - // case: user does not have MFA enablgged - // return (access) token in response - - interface ResponseData { - mfaEnabled: boolean; - encryptionVersion: any; - protectedKey?: string; - protectedKeyIV?: string; - protectedKeyTag?: string; - token: string; - publicKey?: string; - encryptedPrivateKey?: string; - iv?: string; - tag?: string; - } - - const response: ResponseData = { - mfaEnabled: false, - encryptionVersion: user.encryptionVersion, - token: tokens.token, - publicKey: user.publicKey, - encryptedPrivateKey: user.encryptedPrivateKey, - iv: user.iv, - tag: user.tag - } - - if ( - user?.protectedKey && - user?.protectedKeyIV && - user?.protectedKeyTag - ) { - response.protectedKey = user.protectedKey; - response.protectedKeyIV = user.protectedKeyIV - response.protectedKeyTag = user.protectedKeyTag; - } - - const loginAction = await EELogService.createAction({ - name: ACTION_LOGIN, - userId: user._id + // send MFA code [code] to [email] + await sendMail({ + template: 'emailMfa.handlebars', + subjectLine: 'Infisical MFA code', + recipients: [email], + substitutions: { + code + } }); - loginAction && await EELogService.createLog({ - userId: user._id, - actions: [loginAction], - channel: getChannelFromUserAgent(req.headers['user-agent']), - ipAddress: req.ip + return res.status(200).send({ + mfaEnabled: true, + token }); - - return res.status(200).send(response); } - return res.status(400).send({ - message: 'Failed to authenticate. Try again?' + await checkUserDevice({ + user, + ip: req.ip, + userAgent: req.headers['user-agent'] ?? '' }); + + // issue tokens + const tokens = await issueAuthTokens({ userId: user._id.toString() }); + + // store (refresh) token in httpOnly cookie + res.cookie('jid', tokens.refreshToken, { + httpOnly: true, + path: '/', + sameSite: 'strict', + secure: await getHttpsEnabled() + }); + + // case: user does not have MFA enablgged + // return (access) token in response + + interface ResponseData { + mfaEnabled: boolean; + encryptionVersion: any; + protectedKey?: string; + protectedKeyIV?: string; + protectedKeyTag?: string; + token: string; + publicKey?: string; + encryptedPrivateKey?: string; + iv?: string; + tag?: string; + } + + const response: ResponseData = { + mfaEnabled: false, + encryptionVersion: user.encryptionVersion, + token: tokens.token, + publicKey: user.publicKey, + encryptedPrivateKey: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag + } + + if ( + user?.protectedKey && + user?.protectedKeyIV && + user?.protectedKeyTag + ) { + response.protectedKey = user.protectedKey; + response.protectedKeyIV = user.protectedKeyIV + response.protectedKeyTag = user.protectedKeyTag; + } + + const loginAction = await EELogService.createAction({ + name: ACTION_LOGIN, + userId: user._id + }); + + loginAction && await EELogService.createLog({ + userId: user._id, + actions: [loginAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); + + return res.status(200).send(response); } - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to authenticate. Try again?' - }); - } + + return res.status(400).send({ + message: 'Failed to authenticate. Try again?' + }); + } + ); }; /** @@ -237,30 +220,22 @@ export const login2 = async (req: Request, res: Response) => { * @param res */ export const sendMfaToken = async (req: Request, res: Response) => { - try { - const { email } = req.body; + const { email } = req.body; - const code = await TokenService.createToken({ - type: TOKEN_EMAIL_MFA, - email - }); + const code = await TokenService.createToken({ + type: TOKEN_EMAIL_MFA, + email + }); - // send MFA code [code] to [email] - await sendMail({ - template: 'emailMfa.handlebars', - subjectLine: 'Infisical MFA code', - recipients: [email], - substitutions: { - code - } - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to send MFA code' - }); - } + // send MFA code [code] to [email] + await sendMail({ + template: 'emailMfa.handlebars', + subjectLine: 'Infisical MFA code', + recipients: [email], + substitutions: { + code + } + }); return res.status(200).send({ message: 'Successfully sent new MFA code' diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 4985420fb7..d4f91bacec 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { Secret, ServiceToken, @@ -25,30 +24,22 @@ export const createWorkspaceEnvironment = async ( ) => { const { workspaceId } = req.params; const { environmentName, environmentSlug } = req.body; - try { - const workspace = await Workspace.findById(workspaceId).exec(); - if ( - !workspace || - workspace?.environments.find( - ({ name, slug }) => slug === environmentSlug || environmentName === name - ) - ) { - throw new Error('Failed to create workspace environment'); - } - - workspace?.environments.push({ - name: environmentName, - slug: environmentSlug.toLowerCase(), - }); - await workspace.save(); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to create new workspace environment', - }); + const workspace = await Workspace.findById(workspaceId).exec(); + if ( + !workspace || + workspace?.environments.find( + ({ name, slug }) => slug === environmentSlug || environmentName === name + ) + ) { + throw new Error('Failed to create workspace environment'); } + workspace?.environments.push({ + name: environmentName, + slug: environmentSlug.toLowerCase(), + }); + await workspace.save(); + return res.status(200).send({ message: 'Successfully created new environment', workspace: workspaceId, @@ -72,75 +63,67 @@ export const renameWorkspaceEnvironment = async ( ) => { const { workspaceId } = req.params; const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body; - try { - // user should pass both new slug and env name - if (!environmentSlug || !environmentName) { - throw new Error('Invalid environment given.'); - } - - // atomic update the env to avoid conflict - const workspace = await Workspace.findById(workspaceId).exec(); - if (!workspace) { - throw new Error('Failed to create workspace environment'); - } - - const isEnvExist = workspace.environments.some( - ({ name, slug }) => - slug !== oldEnvironmentSlug && - (name === environmentName || slug === environmentSlug) - ); - if (isEnvExist) { - throw new Error('Invalid environment given'); - } - - const envIndex = workspace?.environments.findIndex( - ({ slug }) => slug === oldEnvironmentSlug - ); - if (envIndex === -1) { - throw new Error('Invalid environment given'); - } - - workspace.environments[envIndex].name = environmentName; - workspace.environments[envIndex].slug = environmentSlug.toLowerCase(); - - await workspace.save(); - await Secret.updateMany( - { workspace: workspaceId, environment: oldEnvironmentSlug }, - { environment: environmentSlug } - ); - await SecretVersion.updateMany( - { workspace: workspaceId, environment: oldEnvironmentSlug }, - { environment: environmentSlug } - ); - await ServiceToken.updateMany( - { workspace: workspaceId, environment: oldEnvironmentSlug }, - { environment: environmentSlug } - ); - await ServiceTokenData.updateMany( - { workspace: workspaceId, environment: oldEnvironmentSlug }, - { environment: environmentSlug } - ); - await Integration.updateMany( - { workspace: workspaceId, environment: oldEnvironmentSlug }, - { environment: environmentSlug } - ); - await Membership.updateMany( - { - workspace: workspaceId, - "deniedPermissions.environmentSlug": oldEnvironmentSlug - }, - { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, - { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } - ) - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to update workspace environment', - }); + // user should pass both new slug and env name + if (!environmentSlug || !environmentName) { + throw new Error('Invalid environment given.'); } + // atomic update the env to avoid conflict + const workspace = await Workspace.findById(workspaceId).exec(); + if (!workspace) { + throw new Error('Failed to create workspace environment'); + } + + const isEnvExist = workspace.environments.some( + ({ name, slug }) => + slug !== oldEnvironmentSlug && + (name === environmentName || slug === environmentSlug) + ); + if (isEnvExist) { + throw new Error('Invalid environment given'); + } + + const envIndex = workspace?.environments.findIndex( + ({ slug }) => slug === oldEnvironmentSlug + ); + if (envIndex === -1) { + throw new Error('Invalid environment given'); + } + + workspace.environments[envIndex].name = environmentName; + workspace.environments[envIndex].slug = environmentSlug.toLowerCase(); + + await workspace.save(); + await Secret.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + await SecretVersion.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + await ServiceToken.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + await ServiceTokenData.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + await Integration.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + await Membership.updateMany( + { + workspace: workspaceId, + "deniedPermissions.environmentSlug": oldEnvironmentSlug + }, + { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, + { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } + ) + + return res.status(200).send({ message: 'Successfully update environment', workspace: workspaceId, @@ -163,57 +146,48 @@ export const deleteWorkspaceEnvironment = async ( ) => { const { workspaceId } = req.params; const { environmentSlug } = req.body; - try { - // atomic update the env to avoid conflict - const workspace = await Workspace.findById(workspaceId).exec(); - if (!workspace) { - throw new Error('Failed to create workspace environment'); - } - - const envIndex = workspace?.environments.findIndex( - ({ slug }) => slug === environmentSlug - ); - if (envIndex === -1) { - throw new Error('Invalid environment given'); - } - - workspace.environments.splice(envIndex, 1); - await workspace.save(); - - // clean up - await Secret.deleteMany({ - workspace: workspaceId, - environment: environmentSlug, - }); - await SecretVersion.deleteMany({ - workspace: workspaceId, - environment: environmentSlug, - }); - await ServiceToken.deleteMany({ - workspace: workspaceId, - environment: environmentSlug, - }); - await ServiceTokenData.deleteMany({ - workspace: workspaceId, - environment: environmentSlug, - }); - await Integration.deleteMany({ - workspace: workspaceId, - environment: environmentSlug, - }); - await Membership.updateMany( - { workspace: workspaceId }, - { $pull: { deniedPermissions: { environmentSlug: environmentSlug } } } - ) - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to delete workspace environment', - }); + // atomic update the env to avoid conflict + const workspace = await Workspace.findById(workspaceId).exec(); + if (!workspace) { + throw new Error('Failed to create workspace environment'); } + const envIndex = workspace?.environments.findIndex( + ({ slug }) => slug === environmentSlug + ); + if (envIndex === -1) { + throw new Error('Invalid environment given'); + } + + workspace.environments.splice(envIndex, 1); + await workspace.save(); + + // clean up + await Secret.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + await SecretVersion.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + await ServiceToken.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + await ServiceTokenData.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + await Integration.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + await Membership.updateMany( + { workspace: workspaceId }, + { $pull: { deniedPermissions: { environmentSlug: environmentSlug } } } + ) + return res.status(200).send({ message: 'Successfully deleted environment', workspace: workspaceId, diff --git a/backend/src/controllers/v2/organizationsController.ts b/backend/src/controllers/v2/organizationsController.ts index 613206ba3c..3bfd9085e1 100644 --- a/backend/src/controllers/v2/organizationsController.ts +++ b/backend/src/controllers/v2/organizationsController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { Types } from 'mongoose'; import { MembershipOrg, @@ -49,20 +48,11 @@ export const getOrganizationMemberships = async (req: Request, res: Response) => } } */ - let memberships; - try { - const { organizationId } = req.params; + const { organizationId } = req.params; - memberships = await MembershipOrg.find({ + const memberships = await MembershipOrg.find({ organization: organizationId }).populate('user', '+publicKey'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get organization memberships' - }); - } return res.status(200).send({ memberships @@ -128,26 +118,17 @@ export const updateOrganizationMembership = async (req: Request, res: Response) } } */ - let membership; - try { - const { membershipId } = req.params; - const { role } = req.body; - - membership = await MembershipOrg.findByIdAndUpdate( - membershipId, - { - role - }, { - new: true - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to update organization membership' - }); - } + const { membershipId } = req.params; + const { role } = req.body; + + const membership = await MembershipOrg.findByIdAndUpdate( + membershipId, + { + role + }, { + new: true + } + ); return res.status(200).send({ membership @@ -197,25 +178,16 @@ export const deleteOrganizationMembership = async (req: Request, res: Response) } } */ - let membership; - try { - const { membershipId } = req.params; - - // delete organization membership - membership = await deleteMembershipOrg({ - membershipOrgId: membershipId - }); + const { membershipId } = req.params; + + // delete organization membership + const membership = await deleteMembershipOrg({ + membershipOrgId: membershipId + }); - await updateSubscriptionOrgQuantity({ + await updateSubscriptionOrgQuantity({ organizationId: membership.organization.toString() }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to delete organization membership' - }); - } return res.status(200).send({ membership @@ -303,4 +275,4 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response return res.status(200).send({ serviceAccounts }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/v2/serviceTokenDataController.ts b/backend/src/controllers/v2/serviceTokenDataController.ts index 597548f2fe..772ad8bde4 100644 --- a/backend/src/controllers/v2/serviceTokenDataController.ts +++ b/backend/src/controllers/v2/serviceTokenDataController.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import crypto from 'crypto'; import bcrypt from 'bcrypt'; @@ -144,4 +143,4 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => { return res.status(200).send({ serviceTokenData }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/v2/signupController.ts b/backend/src/controllers/v2/signupController.ts index d9e9a0447f..cbaa7a6b6b 100644 --- a/backend/src/controllers/v2/signupController.ts +++ b/backend/src/controllers/v2/signupController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { User, MembershipOrg } from '../../models'; import { completeAccount } from '../../helpers/user'; import { @@ -20,136 +19,128 @@ import { updateSubscriptionOrgQuantity } from '../../helpers/organization'; */ export const completeAccountSignup = async (req: Request, res: Response) => { let user, token, refreshToken; - try { - const { - email, - firstName, - lastName, - protectedKey, - protectedKeyIV, - protectedKeyTag, - publicKey, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier, - organizationName - }: { - email: string; - firstName: string; - lastName: string; - protectedKey: string; - protectedKeyIV: string; - protectedKeyTag: string; - publicKey: string; - encryptedPrivateKey: string; - encryptedPrivateKeyIV: string; - encryptedPrivateKeyTag: string; - salt: string; - verifier: string; - organizationName: string; - } = req.body; + const { + email, + firstName, + lastName, + protectedKey, + protectedKeyIV, + protectedKeyTag, + publicKey, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier, + organizationName + }: { + email: string; + firstName: string; + lastName: string; + protectedKey: string; + protectedKeyIV: string; + protectedKeyTag: string; + publicKey: string; + encryptedPrivateKey: string; + encryptedPrivateKeyIV: string; + encryptedPrivateKeyTag: string; + salt: string; + verifier: string; + organizationName: string; + } = req.body; - // get user - user = await User.findOne({ email }); + // get user + user = await User.findOne({ email }); - if (!user || (user && user?.publicKey)) { - // case 1: user doesn't exist. - // case 2: user has already completed account - return res.status(403).send({ - error: 'Failed to complete account for complete user' - }); - } + if (!user || (user && user?.publicKey)) { + // case 1: user doesn't exist. + // case 2: user has already completed account + return res.status(403).send({ + error: 'Failed to complete account for complete user' + }); + } - // complete setting up user's account - user = await completeAccount({ - userId: user._id.toString(), - firstName, - lastName, - encryptionVersion: 2, - protectedKey, - protectedKeyIV, - protectedKeyTag, - publicKey, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier - }); + // complete setting up user's account + user = await completeAccount({ + userId: user._id.toString(), + firstName, + lastName, + encryptionVersion: 2, + protectedKey, + protectedKeyIV, + protectedKeyTag, + publicKey, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier + }); - if (!user) - throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null + if (!user) + throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null - // initialize default organization and workspace - await initializeDefaultOrg({ - organizationName, - user - }); + // initialize default organization and workspace + await initializeDefaultOrg({ + organizationName, + user + }); - // update organization membership statuses that are - // invited to completed with user attached - const membershipsToUpdate = await MembershipOrg.find({ - inviteEmail: email, - status: INVITED - }); - - membershipsToUpdate.forEach(async (membership) => { - await updateSubscriptionOrgQuantity({ - organizationId: membership.organization.toString() - }); - }); + // update organization membership statuses that are + // invited to completed with user attached + const membershipsToUpdate = await MembershipOrg.find({ + inviteEmail: email, + status: INVITED + }); + + membershipsToUpdate.forEach(async (membership) => { + await updateSubscriptionOrgQuantity({ + organizationId: membership.organization.toString() + }); + }); - // update organization membership statuses that are - // invited to completed with user attached - await MembershipOrg.updateMany( - { - inviteEmail: email, - status: INVITED - }, - { - user, - status: ACCEPTED - } - ); + // update organization membership statuses that are + // invited to completed with user attached + await MembershipOrg.updateMany( + { + inviteEmail: email, + status: INVITED + }, + { + user, + status: ACCEPTED + } + ); - // issue tokens - const tokens = await issueAuthTokens({ - userId: user._id.toString() - }); + // issue tokens + const tokens = await issueAuthTokens({ + userId: user._id.toString() + }); - token = tokens.token; + token = tokens.token; - // sending a welcome email to new users - if (await getLoopsApiKey()) { - await standardRequest.post("https://app.loops.so/api/v1/events/send", { - "email": email, - "eventName": "Sign Up", - "firstName": firstName, - "lastName": lastName - }, { - headers: { - "Accept": "application/json", - "Authorization": "Bearer " + (await getLoopsApiKey()) - }, - }); - } + // sending a welcome email to new users + if (await getLoopsApiKey()) { + await standardRequest.post("https://app.loops.so/api/v1/events/send", { + "email": email, + "eventName": "Sign Up", + "firstName": firstName, + "lastName": lastName + }, { + headers: { + "Accept": "application/json", + "Authorization": "Bearer " + (await getLoopsApiKey()) + }, + }); + } - // store (refresh) token in httpOnly cookie - res.cookie('jid', tokens.refreshToken, { - httpOnly: true, - path: '/', - sameSite: 'strict', - secure: await getHttpsEnabled() - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to complete account setup' - }); - } + // store (refresh) token in httpOnly cookie + res.cookie('jid', tokens.refreshToken, { + httpOnly: true, + path: '/', + sameSite: 'strict', + secure: await getHttpsEnabled() + }); return res.status(200).send({ message: 'Successfully set up account', @@ -167,109 +158,101 @@ export const completeAccountSignup = async (req: Request, res: Response) => { */ export const completeAccountInvite = async (req: Request, res: Response) => { let user, token, refreshToken; - try { - const { - email, - firstName, - lastName, - protectedKey, - protectedKeyIV, - protectedKeyTag, - publicKey, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier - } = req.body; + const { + email, + firstName, + lastName, + protectedKey, + protectedKeyIV, + protectedKeyTag, + publicKey, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier + } = req.body; - // get user - user = await User.findOne({ email }); + // get user + user = await User.findOne({ email }); - if (!user || (user && user?.publicKey)) { - // case 1: user doesn't exist. - // case 2: user has already completed account - return res.status(403).send({ - error: 'Failed to complete account for complete user' - }); - } + if (!user || (user && user?.publicKey)) { + // case 1: user doesn't exist. + // case 2: user has already completed account + return res.status(403).send({ + error: 'Failed to complete account for complete user' + }); + } - const membershipOrg = await MembershipOrg.findOne({ - inviteEmail: email, - status: INVITED - }); + const membershipOrg = await MembershipOrg.findOne({ + inviteEmail: email, + status: INVITED + }); - if (!membershipOrg) throw new Error('Failed to find invitations for email'); + if (!membershipOrg) throw new Error('Failed to find invitations for email'); - // complete setting up user's account - user = await completeAccount({ - userId: user._id.toString(), - firstName, - lastName, - encryptionVersion: 2, - protectedKey, - protectedKeyIV, - protectedKeyTag, - publicKey, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier - }); + // complete setting up user's account + user = await completeAccount({ + userId: user._id.toString(), + firstName, + lastName, + encryptionVersion: 2, + protectedKey, + protectedKeyIV, + protectedKeyTag, + publicKey, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier + }); - if (!user) - throw new Error('Failed to complete account for non-existent user'); - - // update organization membership statuses that are - // invited to completed with user attached - const membershipsToUpdate = await MembershipOrg.find({ - inviteEmail: email, - status: INVITED - }); - - membershipsToUpdate.forEach(async (membership) => { - await updateSubscriptionOrgQuantity({ - organizationId: membership.organization.toString() - }); - }); + if (!user) + throw new Error('Failed to complete account for non-existent user'); + + // update organization membership statuses that are + // invited to completed with user attached + const membershipsToUpdate = await MembershipOrg.find({ + inviteEmail: email, + status: INVITED + }); + + membershipsToUpdate.forEach(async (membership) => { + await updateSubscriptionOrgQuantity({ + organizationId: membership.organization.toString() + }); + }); - await MembershipOrg.updateMany( - { - inviteEmail: email, - status: INVITED - }, - { - user, - status: ACCEPTED - } - ); + await MembershipOrg.updateMany( + { + inviteEmail: email, + status: INVITED + }, + { + user, + status: ACCEPTED + } + ); - // issue tokens - const tokens = await issueAuthTokens({ - userId: user._id.toString() - }); + // issue tokens + const tokens = await issueAuthTokens({ + userId: user._id.toString() + }); - token = tokens.token; + token = tokens.token; - // store (refresh) token in httpOnly cookie - res.cookie('jid', tokens.refreshToken, { - httpOnly: true, - path: '/', - sameSite: 'strict', - secure: await getHttpsEnabled() - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to complete account setup' - }); - } + // store (refresh) token in httpOnly cookie + res.cookie('jid', tokens.refreshToken, { + httpOnly: true, + path: '/', + sameSite: 'strict', + secure: await getHttpsEnabled() + }); return res.status(200).send({ message: 'Successfully set up account', user, token }); -}; \ No newline at end of file +}; diff --git a/backend/src/controllers/v2/tagController.ts b/backend/src/controllers/v2/tagController.ts index 199f2401e9..0175b359a8 100644 --- a/backend/src/controllers/v2/tagController.ts +++ b/backend/src/controllers/v2/tagController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { Types } from 'mongoose'; import { Membership, Secret, @@ -69,4 +68,4 @@ export const getWorkspaceTags = async (req: Request, res: Response) => { return res.json({ workspaceTags }) -} \ No newline at end of file +} diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts index 9940ddc56b..2d5cdc51ac 100644 --- a/backend/src/controllers/v2/usersController.ts +++ b/backend/src/controllers/v2/usersController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { User, MembershipOrg @@ -37,18 +36,9 @@ export const getMe = async (req: Request, res: Response) => { } } */ - let user; - try { - user = await User - .findById(req.user._id) - .select('+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get current user' - }); - } + const user = await User + .findById(req.user._id) + .select('+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag'); return res.status(200).send({ user @@ -64,29 +54,20 @@ export const getMe = async (req: Request, res: Response) => { * @returns */ export const updateMyMfaEnabled = async (req: Request, res: Response) => { - let user; - try { - const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body; - req.user.isMfaEnabled = isMfaEnabled; - - if (isMfaEnabled) { - // TODO: adapt this route/controller - // to work for different forms of MFA - req.user.mfaMethods = ['email']; - } else { - req.user.mfaMethods = []; - } - - await req.user.save(); - - user = req.user; - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to update current user's MFA status" - }); + const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body; + req.user.isMfaEnabled = isMfaEnabled; + + if (isMfaEnabled) { + // TODO: adapt this route/controller + // to work for different forms of MFA + req.user.mfaMethods = ['email']; + } else { + req.user.mfaMethods = []; } + + await req.user.save(); + + const user = req.user; return res.status(200).send({ user @@ -126,22 +107,13 @@ export const getMyOrganizations = async (req: Request, res: Response) => { } } */ - let organizations; - try { - organizations = ( - await MembershipOrg.find({ - user: req.user._id - }).populate('organization') - ).map((m) => m.organization); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get current user's organizations" - }); - } + const organizations = ( + await MembershipOrg.find({ + user: req.user._id + }).populate('organization') + ).map((m) => m.organization); return res.status(200).send({ organizations }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index ea673d428e..2f67bea86b 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { Types } from 'mongoose'; import { Workspace, @@ -47,66 +46,57 @@ interface V2PushSecret { */ export const pushWorkspaceSecrets = async (req: Request, res: Response) => { // upload (encrypted) secrets to workspace with id [workspaceId] - try { - const postHogClient = await TelemetryService.getPostHogClient(); - let { secrets }: { secrets: V2PushSecret[] } = req.body; - const { keys, environment, channel } = req.body; - const { workspaceId } = req.params; + const postHogClient = await TelemetryService.getPostHogClient(); + let { secrets }: { secrets: V2PushSecret[] } = req.body; + const { keys, environment, channel } = req.body; + const { workspaceId } = req.params; - // validate environment - const workspaceEnvs = req.membership.workspace.environments; - if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { - throw new Error('Failed to validate environment'); - } + // validate environment + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { + throw new Error('Failed to validate environment'); + } - // sanitize secrets - secrets = secrets.filter( - (s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== '' - ); + // sanitize secrets + secrets = secrets.filter( + (s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== '' + ); - await push({ - userId: req.user._id, - workspaceId, - environment, - secrets, - channel: channel ? channel : 'cli', - ipAddress: req.ip - }); + await push({ + userId: req.user._id, + workspaceId, + environment, + secrets, + channel: channel ? channel : 'cli', + ipAddress: req.ip + }); - await pushKeys({ - userId: req.user._id, - workspaceId, - keys - }); + await pushKeys({ + userId: req.user._id, + workspaceId, + keys + }); - if (postHogClient) { - postHogClient.capture({ - event: 'secrets pushed', - distinctId: req.user.email, - properties: { - numberOfSecrets: secrets.length, - environment, - workspaceId, - channel: channel ? channel : 'cli' - } - }); - } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets pushed', + distinctId: req.user.email, + properties: { + numberOfSecrets: secrets.length, + environment, + workspaceId, + channel: channel ? channel : 'cli' + } + }); + } - // trigger event - push secrets - EventService.handleEvent({ - event: eventPushSecrets({ - workspaceId: new Types.ObjectId(workspaceId), - environment - }) - }); - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to upload workspace secrets' - }); - } + // trigger event - push secrets + EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: new Types.ObjectId(workspaceId), + environment + }) + }); return res.status(200).send({ message: 'Successfully uploaded workspace secrets' @@ -121,57 +111,49 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => { * @returns */ export const pullSecrets = async (req: Request, res: Response) => { - let secrets; - try { - const postHogClient = await TelemetryService.getPostHogClient(); - const environment: string = req.query.environment as string; - const channel: string = req.query.channel as string; - const { workspaceId } = req.params; + const postHogClient = await TelemetryService.getPostHogClient(); + const environment: string = req.query.environment as string; + const channel: string = req.query.channel as string; + const { workspaceId } = req.params; - let userId; - if (req.user) { - userId = req.user._id.toString(); - } else if (req.serviceTokenData) { - userId = req.serviceTokenData.user.toString(); - } - // validate environment - const workspaceEnvs = req.membership.workspace.environments; - if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { - throw new Error('Failed to validate environment'); - } + let userId; + if (req.user) { + userId = req.user._id.toString(); + } else if (req.serviceTokenData) { + userId = req.serviceTokenData.user.toString(); + } + // validate environment + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { + throw new Error('Failed to validate environment'); + } - secrets = await pull({ - userId, - workspaceId, - environment, - channel: channel ? channel : 'cli', - ipAddress: req.ip - }); + let secrets = await pull({ + userId, + workspaceId, + environment, + channel: channel ? channel : 'cli', + ipAddress: req.ip + }); - if (channel !== 'cli') { - secrets = reformatPullSecrets({ secrets }); - } + if (channel !== 'cli') { + // FIX: Fix this any + secrets = reformatPullSecrets({ secrets }) as any; + } - if (postHogClient) { - // capture secrets pushed event in production - postHogClient.capture({ - distinctId: req.user.email, - event: 'secrets pulled', - properties: { - numberOfSecrets: secrets.length, - environment, - workspaceId, - channel: channel ? channel : 'cli' - } - }); - } - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to pull workspace secrets' - }); - } + if (postHogClient) { + // capture secrets pushed event in production + postHogClient.capture({ + distinctId: req.user.email, + event: 'secrets pulled', + properties: { + numberOfSecrets: secrets.length, + environment, + workspaceId, + channel: channel ? channel : 'cli' + } + }); + } return res.status(200).send({ secrets @@ -208,22 +190,14 @@ export const getWorkspaceKey = async (req: Request, res: Response) => { } */ let key; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - key = await Key.findOne({ - workspace: workspaceId, - receiver: req.user._id - }).populate('sender', '+publicKey'); + key = await Key.findOne({ + workspace: workspaceId, + receiver: req.user._id + }).populate('sender', '+publicKey'); - if (!key) throw new Error('Failed to find workspace key'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get workspace key' - }); - } + if (!key) throw new Error('Failed to find workspace key'); return res.status(200).json(key); } @@ -231,23 +205,13 @@ export const getWorkspaceServiceTokenData = async ( req: Request, res: Response ) => { - let serviceTokenData; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - serviceTokenData = await ServiceTokenData - .find({ - workspace: workspaceId - }) - .select('+encryptedKey +iv +tag'); - - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get workspace service token data' - }); - } + const serviceTokenData = await ServiceTokenData + .find({ + workspace: workspaceId + }) + .select('+encryptedKey +iv +tag'); return res.status(200).send({ serviceTokenData @@ -294,20 +258,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => { } } */ - let memberships; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - memberships = await Membership.find({ - workspace: workspaceId - }).populate('user', '+publicKey'); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to get workspace memberships' - }); - } + const memberships = await Membership.find({ + workspace: workspaceId + }).populate('user', '+publicKey'); return res.status(200).send({ memberships @@ -374,29 +329,20 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) => } } */ - let membership; - try { - const { - membershipId - } = req.params; - const { role } = req.body; - - membership = await Membership.findByIdAndUpdate( - membershipId, - { - role - }, { - new: true - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to update workspace membership' - }); - } - + const { + membershipId + } = req.params; + const { role } = req.body; + + const membership = await Membership.findByIdAndUpdate( + membershipId, + { + role + }, { + new: true + } + ); + return res.status(200).send({ membership }); @@ -445,27 +391,18 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) => } } */ - let membership; - try { - const { - membershipId - } = req.params; - - membership = await Membership.findByIdAndDelete(membershipId); - - if (!membership) throw new Error('Failed to delete workspace membership'); - - await Key.deleteMany({ - receiver: membership.user, - workspace: membership.workspace - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to delete workspace membership' - }); - } + const { + membershipId + } = req.params; + + const membership = await Membership.findByIdAndDelete(membershipId); + + if (!membership) throw new Error('Failed to delete workspace membership'); + + await Key.deleteMany({ + receiver: membership.user, + workspace: membership.workspace + }); return res.status(200).send({ membership @@ -479,32 +416,23 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) => * @returns */ export const toggleAutoCapitalization = async (req: Request, res: Response) => { - let workspace; - try { - const { workspaceId } = req.params; - const { autoCapitalization } = req.body; + const { workspaceId } = req.params; + const { autoCapitalization } = req.body; - workspace = await Workspace.findOneAndUpdate( - { - _id: workspaceId - }, - { - autoCapitalization - }, - { - new: true - } - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: 'Failed to change autoCapitalization setting' - }); - } + const workspace = await Workspace.findOneAndUpdate( + { + _id: workspaceId + }, + { + autoCapitalization + }, + { + new: true + } + ); return res.status(200).send({ message: 'Successfully changed autoCapitalization setting', workspace }); -}; \ No newline at end of file +}; diff --git a/backend/src/ee/controllers/v1/actionController.ts b/backend/src/ee/controllers/v1/actionController.ts index b136b0fa4b..4b3117b207 100644 --- a/backend/src/ee/controllers/v1/actionController.ts +++ b/backend/src/ee/controllers/v1/actionController.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import * as Sentry from '@sentry/node'; import { Action, SecretVersion } from '../../models'; import { ActionNotFoundError } from '../../../utils/errors'; @@ -28,4 +27,4 @@ export const getAction = async (req: Request, res: Response) => { return res.status(200).send({ action }); -} \ No newline at end of file +} diff --git a/backend/src/ee/controllers/v1/cloudProductsController.ts b/backend/src/ee/controllers/v1/cloudProductsController.ts index 54584ce821..c7d60ca1a6 100644 --- a/backend/src/ee/controllers/v1/cloudProductsController.ts +++ b/backend/src/ee/controllers/v1/cloudProductsController.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import { EELicenseService } from '../../services'; import { getLicenseServerUrl } from '../../../config'; @@ -12,23 +11,18 @@ import { licenseServerKeyRequest } from '../../../config/request'; * @returns */ export const getCloudProducts = async (req: Request, res: Response) => { - try { - const billingCycle = req.query['billing-cycle'] as string; + const billingCycle = req.query['billing-cycle'] as string; - if (EELicenseService.instanceType === 'cloud') { - const { data } = await licenseServerKeyRequest.get( - `${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` - ); + if (EELicenseService.instanceType === 'cloud') { + const { data } = await licenseServerKeyRequest.get( + `${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` + ); - return res.status(200).send(data); - } - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); + return res.status(200).send(data); } return res.status(200).send({ head: [], rows: [] }); -} \ No newline at end of file +} diff --git a/backend/src/ee/controllers/v1/secretController.ts b/backend/src/ee/controllers/v1/secretController.ts index 3d1e4b3b31..9ba3c77e39 100644 --- a/backend/src/ee/controllers/v1/secretController.ts +++ b/backend/src/ee/controllers/v1/secretController.ts @@ -1,5 +1,4 @@ import { Request, Response } from "express"; -import * as Sentry from "@sentry/node"; import { Secret } from "../../../models"; import { SecretVersion } from "../../models"; import { EESecretService } from "../../services"; @@ -55,29 +54,20 @@ export const getSecretVersions = async (req: Request, res: Response) => { } } */ - let secretVersions; - try { - const { secretId, workspaceId, environment, folderId } = req.params; + const { secretId, workspaceId, environment, folderId } = req.params; - const offset: number = parseInt(req.query.offset as string); - const limit: number = parseInt(req.query.limit as string); + const offset: number = parseInt(req.query.offset as string); + const limit: number = parseInt(req.query.limit as string); - secretVersions = await SecretVersion.find({ - secret: secretId, - workspace: workspaceId, - environment, - folder: folderId, - }) - .sort({ createdAt: -1 }) - .skip(offset) - .limit(limit); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get secret versions", - }); - } + const secretVersions = await SecretVersion.find({ + secret: secretId, + workspace: workspaceId, + environment, + folder: folderId, + }) + .sort({ createdAt: -1 }) + .skip(offset) + .limit(limit); return res.status(200).send({ secretVersions, @@ -139,74 +129,45 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { } } */ - let secret; - try { - const { secretId } = req.params; - const { version } = req.body; + const { secretId } = req.params; + const { version } = req.body; - // validate secret version - const oldSecretVersion = await SecretVersion.findOne({ - secret: secretId, - version, - }).select("+secretBlindIndex"); + // validate secret version + const oldSecretVersion = await SecretVersion.findOne({ + secret: secretId, + version, + }).select("+secretBlindIndex"); - if (!oldSecretVersion) throw new Error("Failed to find secret version"); + if (!oldSecretVersion) throw new Error("Failed to find secret version"); - const { - workspace, - type, - user, - environment, - secretBlindIndex, - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - folder, - algorithm, - keyEncoding, - } = oldSecretVersion; + const { + workspace, + type, + user, + environment, + secretBlindIndex, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + algorithm, + folder, + keyEncoding, + } = oldSecretVersion; - // update secret - secret = await Secret.findByIdAndUpdate( - secretId, - { - $inc: { - version: 1, - }, - workspace, - type, - user, - environment, - ...(secretBlindIndex ? { secretBlindIndex } : {}), - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - folderId: folder, - algorithm, - keyEncoding, + // update secret + const secret = await Secret.findByIdAndUpdate( + secretId, + { + $inc: { + version: 1, }, - { - new: true, - } - ); - - if (!secret) throw new Error("Failed to find and update secret"); - - // add new secret version - await new SecretVersion({ - secret: secretId, - version: secret.version, workspace, type, user, environment, - isDeleted: false, ...(secretBlindIndex ? { secretBlindIndex } : {}), secretKeyCiphertext, secretKeyIV, @@ -214,24 +175,44 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { secretValueCiphertext, secretValueIV, secretValueTag, - folder, + folderId: folder, algorithm, keyEncoding, - }).save(); + }, + { + new: true, + } + ); - // take secret snapshot - await EESecretService.takeSecretSnapshot({ - workspaceId: secret.workspace, - environment, - folderId: folder, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to roll back secret version", - }); - } + if (!secret) throw new Error("Failed to find and update secret"); + + // add new secret version + await new SecretVersion({ + secret: secretId, + version: secret.version, + workspace, + type, + user, + environment, + isDeleted: false, + ...(secretBlindIndex ? { secretBlindIndex } : {}), + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + folder, + algorithm, + keyEncoding, + }).save(); + + // take secret snapshot + await EESecretService.takeSecretSnapshot({ + workspaceId: secret.workspace, + environment, + folderId: folder, + }); return res.status(200).send({ secret, diff --git a/backend/src/ee/controllers/v1/secretSnapshotController.ts b/backend/src/ee/controllers/v1/secretSnapshotController.ts index a6ee2ce4d7..34640506a0 100644 --- a/backend/src/ee/controllers/v1/secretSnapshotController.ts +++ b/backend/src/ee/controllers/v1/secretSnapshotController.ts @@ -1,5 +1,4 @@ import { Request, Response } from "express"; -import * as Sentry from "@sentry/node"; import { ISecretVersion, SecretSnapshot, @@ -13,23 +12,14 @@ import { * @returns */ export const getSecretSnapshot = async (req: Request, res: Response) => { - let secretSnapshot; - try { - const { secretSnapshotId } = req.params; + const { secretSnapshotId } = req.params; + const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId) + .lean() + .populate<{ secretVersions: ISecretVersion[] }>("secretVersions") + .populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion"); - secretSnapshot = await SecretSnapshot.findById(secretSnapshotId) - .lean() - .populate<{ secretVersions: ISecretVersion[] }>("secretVersions") - .populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion"); + if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); - if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get secret snapshot", - }); - } const folderId = secretSnapshot.folderId; // to show only the folder required secrets secretSnapshot.secretVersions = secretSnapshot.secretVersions.filter( diff --git a/backend/src/ee/controllers/v1/stripeController.ts b/backend/src/ee/controllers/v1/stripeController.ts index 69858c94fe..172df32205 100644 --- a/backend/src/ee/controllers/v1/stripeController.ts +++ b/backend/src/ee/controllers/v1/stripeController.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import Stripe from 'stripe'; import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config'; @@ -10,26 +9,17 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config'; * @returns */ export const handleWebhook = async (req: Request, res: Response) => { - let event; - try { - const stripe = new Stripe(await getStripeSecretKey(), { - apiVersion: '2022-08-01' - }); + const stripe = new Stripe(await getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); - // check request for valid stripe signature - const sig = req.headers['stripe-signature'] as string; - event = stripe.webhooks.constructEvent( - req.body, - sig, - await getStripeWebhookSecret() - ); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - error: 'Failed to process webhook' - }); - } + // check request for valid stripe signature + const sig = req.headers['stripe-signature'] as string; + const event = stripe.webhooks.constructEvent( + req.body, + sig, + await getStripeWebhookSecret() + ); switch (event.type) { case '': diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 409da56b9a..166171ccdc 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -1,5 +1,4 @@ import { Request, Response } from "express"; -import * as Sentry from "@sentry/node"; import { PipelineStage, Types } from "mongoose"; import { Secret } from "../../../models"; import { @@ -69,29 +68,20 @@ export const getWorkspaceSecretSnapshots = async ( } } */ - let secretSnapshots; - try { - const { workspaceId } = req.params; - const { environment, folderId } = req.query; + const { workspaceId } = req.params; + const { environment, folderId } = req.query; - const offset: number = parseInt(req.query.offset as string); - const limit: number = parseInt(req.query.limit as string); + const offset: number = parseInt(req.query.offset as string); + const limit: number = parseInt(req.query.limit as string); - secretSnapshots = await SecretSnapshot.find({ - workspace: workspaceId, - environment, - folderId: folderId || "root", - }) - .sort({ createdAt: -1 }) - .skip(offset) - .limit(limit); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get secret snapshots", - }); - } + const secretSnapshots = await SecretSnapshot.find({ + workspace: workspaceId, + environment, + folderId: folderId || "root", + }) + .sort({ createdAt: -1 }) + .skip(offset) + .limit(limit); return res.status(200).send({ secretSnapshots, @@ -107,23 +97,14 @@ export const getWorkspaceSecretSnapshotsCount = async ( req: Request, res: Response ) => { - let count; - try { - const { workspaceId } = req.params; - const { environment, folderId } = req.query; + const { workspaceId } = req.params; + const { environment, folderId } = req.query; - count = await SecretSnapshot.countDocuments({ - workspace: workspaceId, - environment, - folderId: folderId || "root", - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to count number of secret snapshots", - }); - } + const count = await SecretSnapshot.countDocuments({ + workspace: workspaceId, + environment, + folderId: folderId || "root", + }); return res.status(200).send({ count, @@ -191,324 +172,315 @@ export const rollbackWorkspaceSecretSnapshot = async ( } */ - let secrets; - try { - const { workspaceId } = req.params; - const { version, environment, folderId = "root" } = req.body; + const { workspaceId } = req.params; + const { version, environment, folderId = "root" } = req.body; - // validate secret snapshot - const secretSnapshot = await SecretSnapshot.findOne({ - workspace: workspaceId, - version, - environment, - folderId: folderId, + // validate secret snapshot + const secretSnapshot = await SecretSnapshot.findOne({ + workspace: workspaceId, + version, + environment, + folderId: folderId, + }) + .populate<{ secretVersions: ISecretVersion[] }>({ + path: "secretVersions", + select: "+secretBlindIndex", }) - .populate<{ secretVersions: ISecretVersion[] }>({ - path: "secretVersions", - select: "+secretBlindIndex", - }) - .populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion"); + .populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion"); - if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); + if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); - const snapshotFolderTree = secretSnapshot.folderVersion; - const latestFolderTree = await Folder.findOne({ - workspace: workspaceId, - environment, - }); + const snapshotFolderTree = secretSnapshot.folderVersion; + const latestFolderTree = await Folder.findOne({ + workspace: workspaceId, + environment, + }); - const latestFolderVersion = await FolderVersion.findOne({ - environment, - workspace: workspaceId, - "nodes.id": folderId, - }).sort({ "nodes.version": -1 }); + const latestFolderVersion = await FolderVersion.findOne({ + environment, + workspace: workspaceId, + "nodes.id": folderId, + }).sort({ "nodes.version": -1 }); - const oldSecretVersionsObj: Record = {}; - const secretIds: Types.ObjectId[] = []; - const folderIds: string[] = [folderId]; + const oldSecretVersionsObj: Record = {}; + const secretIds: Types.ObjectId[] = []; + const folderIds: string[] = [folderId]; - secretSnapshot.secretVersions.forEach((snapSecVer) => { - oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer; - secretIds.push(snapSecVer.secret); - }); + secretSnapshot.secretVersions.forEach((snapSecVer) => { + oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer; + secretIds.push(snapSecVer.secret); + }); - // the parent node from current latest one - // this will be modified according to the snapshot and latest snapshots - const newFolderTree = - latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId); + // the parent node from current latest one + // this will be modified according to the snapshot and latest snapshots + const newFolderTree = + latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId); - if (newFolderTree) { - newFolderTree.children = snapshotFolderTree?.nodes?.children || []; - const queue = [newFolderTree]; - // a bfs algorithm in which we take the latest snapshots of all the folders in a level + if (newFolderTree) { + newFolderTree.children = snapshotFolderTree?.nodes?.children || []; + const queue = [newFolderTree]; + // a bfs algorithm in which we take the latest snapshots of all the folders in a level + while (queue.length) { + const groupByFolderId: Record = {}; + // the original queue is popped out completely to get what ever in a level + // subqueue is filled with all the children thus next level folders + // subQueue will then be transfered to the oriinal queue + const subQueue: TFolderSchema[] = []; + // get everything inside a level while (queue.length) { - const groupByFolderId: Record = {}; - // the original queue is popped out completely to get what ever in a level - // subqueue is filled with all the children thus next level folders - // subQueue will then be transfered to the oriinal queue - const subQueue: TFolderSchema[] = []; - // get everything inside a level - while (queue.length) { - const folder = queue.pop() as TFolderSchema; - folder.children.forEach((el) => { - folderIds.push(el.id); // push ids and data into queu - subQueue.push(el); - // to modify the original tree very fast we keep a reference object - // key with folder id and pointing to the various nodes - groupByFolderId[el.id] = el; - }); - } - // get latest snapshots of all the folder - const matchWsFoldersPipeline = { - $match: { - workspace: new Types.ObjectId(workspaceId), - environment, - folderId: { - $in: Object.keys(groupByFolderId), - }, - }, - }; - const sortByFolderIdAndVersion: PipelineStage = { - $sort: { folderId: 1, version: -1 }, - }; - const pickLatestVersionOfEachFolder = { - $group: { - _id: "$folderId", - latestVersion: { $first: "$version" }, - doc: { - $first: "$$ROOT", - }, - }, - }; - const populateSecVersion = { - $lookup: { - from: SecretVersion.collection.name, - localField: "doc.secretVersions", - foreignField: "_id", - as: "doc.secretVersions", - }, - }; - const populateFolderVersion = { - $lookup: { - from: FolderVersion.collection.name, - localField: "doc.folderVersion", - foreignField: "_id", - as: "doc.folderVersion", - }, - }; - const unwindFolderVerField = { - $unwind: { - path: "$doc.folderVersion", - preserveNullAndEmptyArrays: true, - }, - }; - const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> = - await SecretSnapshot.aggregate([ - matchWsFoldersPipeline, - sortByFolderIdAndVersion, - pickLatestVersionOfEachFolder, - populateSecVersion, - populateFolderVersion, - unwindFolderVerField, - ]); - - // recursive snapshotting each level - latestSnapshotsByFolders.forEach((snap) => { - // mutate the folder tree to update the nodes to the latest version tree - // we are reconstructing the folder tree by latest snapshots here - if (groupByFolderId[snap.doc.folderId]) { - groupByFolderId[snap.doc.folderId].children = - snap.doc?.folderVersion?.nodes?.children || []; - } - - // push all children of next level snapshots - if (snap.doc.folderVersion?.nodes?.children) { - queue.push(...snap.doc.folderVersion.nodes.children); - } - - snap.doc.secretVersions.forEach((snapSecVer) => { - // record all the secrets - oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer; - secretIds.push(snapSecVer.secret); - }); + const folder = queue.pop() as TFolderSchema; + folder.children.forEach((el) => { + folderIds.push(el.id); // push ids and data into queu + subQueue.push(el); + // to modify the original tree very fast we keep a reference object + // key with folder id and pointing to the various nodes + groupByFolderId[el.id] = el; }); - - queue.push(...subQueue); } - } - - // TODO: fix any - const latestSecretVersionIds = await getLatestSecretVersionIds({ - secretIds, - }); - - // TODO: fix any - const latestSecretVersions: any = ( - await SecretVersion.find( - { - _id: { - $in: latestSecretVersionIds.map((s) => s.versionId), + // get latest snapshots of all the folder + const matchWsFoldersPipeline = { + $match: { + workspace: new Types.ObjectId(workspaceId), + environment, + folderId: { + $in: Object.keys(groupByFolderId), }, }, - "secret version" - ) - ).reduce( - (accumulator, s) => ({ - ...accumulator, - [`${s.secret.toString()}`]: s, - }), - {} - ); + }; + const sortByFolderIdAndVersion: PipelineStage = { + $sort: { folderId: 1, version: -1 }, + }; + const pickLatestVersionOfEachFolder = { + $group: { + _id: "$folderId", + latestVersion: { $first: "$version" }, + doc: { + $first: "$$ROOT", + }, + }, + }; + const populateSecVersion = { + $lookup: { + from: SecretVersion.collection.name, + localField: "doc.secretVersions", + foreignField: "_id", + as: "doc.secretVersions", + }, + }; + const populateFolderVersion = { + $lookup: { + from: FolderVersion.collection.name, + localField: "doc.folderVersion", + foreignField: "_id", + as: "doc.folderVersion", + }, + }; + const unwindFolderVerField = { + $unwind: { + path: "$doc.folderVersion", + preserveNullAndEmptyArrays: true, + }, + }; + const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> = + await SecretSnapshot.aggregate([ + matchWsFoldersPipeline, + sortByFolderIdAndVersion, + pickLatestVersionOfEachFolder, + populateSecVersion, + populateFolderVersion, + unwindFolderVerField, + ]); - const secDelQuery: Record = { - workspace: workspaceId, - environment, - // undefined means root thus collect all secrets - }; - if (folderId !== "root" && folderIds.length) - secDelQuery.folder = { $in: folderIds }; + // recursive snapshotting each level + latestSnapshotsByFolders.forEach((snap) => { + // mutate the folder tree to update the nodes to the latest version tree + // we are reconstructing the folder tree by latest snapshots here + if (groupByFolderId[snap.doc.folderId]) { + groupByFolderId[snap.doc.folderId].children = + snap.doc?.folderVersion?.nodes?.children || []; + } - // delete existing secrets - await Secret.deleteMany(secDelQuery); - await Folder.deleteOne({ - workspace: workspaceId, - environment, - }); + // push all children of next level snapshots + if (snap.doc.folderVersion?.nodes?.children) { + queue.push(...snap.doc.folderVersion.nodes.children); + } - // add secrets - secrets = await Secret.insertMany( - Object.keys(oldSecretVersionsObj).map((sv) => { - const { - secret: secretId, - workspace, - type, - user, - environment, - secretBlindIndex, - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - createdAt, - algorithm, - keyEncoding, - folder: secFolderId, - } = oldSecretVersionsObj[sv]; - - return { - _id: secretId, - version: latestSecretVersions[secretId.toString()].version + 1, - workspace, - type, - user, - environment, - secretBlindIndex: secretBlindIndex ?? undefined, - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - secretCommentCiphertext: "", - secretCommentIV: "", - secretCommentTag: "", - createdAt, - algorithm, - keyEncoding, - folder: secFolderId, - }; - }) - ); - - // add secret versions - const secretV = await SecretVersion.insertMany( - secrets.map( - ({ - _id, - version, - workspace, - type, - user, - environment, - secretBlindIndex, - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - algorithm, - keyEncoding, - folder: secFolderId, - }) => ({ - _id: new Types.ObjectId(), - secret: _id, - version, - workspace, - type, - user, - environment, - isDeleted: false, - secretBlindIndex: secretBlindIndex ?? undefined, - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - algorithm, - keyEncoding, - folder: secFolderId, - }) - ) - ); - - if (newFolderTree && latestFolderTree) { - // save the updated folder tree to the present one - newFolderTree.version = (latestFolderVersion?.nodes?.version || 0) + 1; - latestFolderTree._id = new Types.ObjectId(); - latestFolderTree.isNew = true; - await latestFolderTree.save(); - - // create new folder version - const newFolderVersion = new FolderVersion({ - workspace: workspaceId, - environment, - nodes: newFolderTree, + snap.doc.secretVersions.forEach((snapSecVer) => { + // record all the secrets + oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer; + secretIds.push(snapSecVer.secret); + }); }); - await newFolderVersion.save(); - } - // update secret versions of restored secrets as not deleted - await SecretVersion.updateMany( + queue.push(...subQueue); + } + } + + // TODO: fix any + const latestSecretVersionIds = await getLatestSecretVersionIds({ + secretIds, + }); + + // TODO: fix any + const latestSecretVersions: any = ( + await SecretVersion.find( { - secret: { - $in: Object.keys(oldSecretVersionsObj).map( - (sv) => oldSecretVersionsObj[sv].secret - ), + _id: { + $in: latestSecretVersionIds.map((s) => s.versionId), }, }, - { - isDeleted: false, - } - ); + "secret version" + ) + ).reduce( + (accumulator, s) => ({ + ...accumulator, + [`${s.secret.toString()}`]: s, + }), + {} + ); - // take secret snapshot - await EESecretService.takeSecretSnapshot({ - workspaceId: new Types.ObjectId(workspaceId), + const secDelQuery: Record = { + workspace: workspaceId, + environment, + // undefined means root thus collect all secrets + }; + if (folderId !== "root" && folderIds.length) + secDelQuery.folder = { $in: folderIds }; + + // delete existing secrets + await Secret.deleteMany(secDelQuery); + await Folder.deleteOne({ + workspace: workspaceId, + environment, + }); + + // add secrets + const secrets = await Secret.insertMany( + Object.keys(oldSecretVersionsObj).map((sv) => { + const { + secret: secretId, + workspace, + type, + user, + environment, + secretBlindIndex, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + createdAt, + algorithm, + keyEncoding, + folder: secFolderId, + } = oldSecretVersionsObj[sv]; + + return { + _id: secretId, + version: latestSecretVersions[secretId.toString()].version + 1, + workspace, + type, + user, + environment, + secretBlindIndex: secretBlindIndex ?? undefined, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + secretCommentCiphertext: "", + secretCommentIV: "", + secretCommentTag: "", + createdAt, + algorithm, + keyEncoding, + folder: secFolderId, + }; + }) + ); + + // add secret versions + const secretV = await SecretVersion.insertMany( + secrets.map( + ({ + _id, + version, + workspace, + type, + user, + environment, + secretBlindIndex, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + algorithm, + keyEncoding, + folder: secFolderId, + }) => ({ + _id: new Types.ObjectId(), + secret: _id, + version, + workspace, + type, + user, + environment, + isDeleted: false, + secretBlindIndex: secretBlindIndex ?? undefined, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + algorithm, + keyEncoding, + folder: secFolderId, + }) + ) + ); + + if (newFolderTree && latestFolderTree) { + // save the updated folder tree to the present one + newFolderTree.version = (latestFolderVersion?.nodes?.version || 0) + 1; + latestFolderTree._id = new Types.ObjectId(); + latestFolderTree.isNew = true; + await latestFolderTree.save(); + + // create new folder version + const newFolderVersion = new FolderVersion({ + workspace: workspaceId, environment, - folderId, - }); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to roll back secret snapshot", + nodes: newFolderTree, }); + await newFolderVersion.save(); } + // update secret versions of restored secrets as not deleted + await SecretVersion.updateMany( + { + secret: { + $in: Object.keys(oldSecretVersionsObj).map( + (sv) => oldSecretVersionsObj[sv].secret + ), + }, + }, + { + isDeleted: false, + } + ); + + // take secret snapshot + await EESecretService.takeSecretSnapshot({ + workspaceId: new Types.ObjectId(workspaceId), + environment, + folderId, + }); + return res.status(200).send({ secrets, }); @@ -587,39 +559,30 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => { } } */ - let logs; - try { - const { workspaceId } = req.params; + const { workspaceId } = req.params; - const offset: number = parseInt(req.query.offset as string); - const limit: number = parseInt(req.query.limit as string); - const sortBy: string = req.query.sortBy as string; - const userId: string = req.query.userId as string; - const actionNames: string = req.query.actionNames as string; + const offset: number = parseInt(req.query.offset as string); + const limit: number = parseInt(req.query.limit as string); + const sortBy: string = req.query.sortBy as string; + const userId: string = req.query.userId as string; + const actionNames: string = req.query.actionNames as string; - logs = await Log.find({ - workspace: workspaceId, - ...(userId ? { user: userId } : {}), - ...(actionNames - ? { - actionNames: { - $in: actionNames.split(","), - }, - } - : {}), - }) - .sort({ createdAt: sortBy === "recent" ? -1 : 1 }) - .skip(offset) - .limit(limit) - .populate("actions") - .populate("user serviceAccount serviceTokenData"); - } catch (err) { - Sentry.setUser({ email: req.user.email }); - Sentry.captureException(err); - return res.status(400).send({ - message: "Failed to get workspace logs", - }); - } + const logs = await Log.find({ + workspace: workspaceId, + ...(userId ? { user: userId } : {}), + ...(actionNames + ? { + actionNames: { + $in: actionNames.split(","), + }, + } + : {}), + }) + .sort({ createdAt: sortBy === "recent" ? -1 : 1 }) + .skip(offset) + .limit(limit) + .populate("actions") + .populate("user serviceAccount serviceTokenData"); return res.status(200).send({ logs, diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index a4726073fe..5e39420e54 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -1,5 +1,4 @@ import NodeCache from 'node-cache'; -import * as Sentry from '@sentry/node'; import { getLicenseKey, getLicenseServerKey, @@ -98,35 +97,29 @@ class EELicenseService { const licenseServerKey = await getLicenseServerKey(); const licenseKey = await getLicenseKey(); - try { - if (licenseServerKey) { - // license server key is present -> validate it - const token = await refreshLicenseServerKeyToken() - - if (token) { - this.instanceType = 'cloud'; - } + if (licenseServerKey) { + // license server key is present -> validate it + const token = await refreshLicenseServerKeyToken() - return; + if (token) { + this.instanceType = 'cloud'; } - if (licenseKey) { - // license key is present -> validate it - const token = await refreshLicenseKeyToken(); - - if (token) { - const { data: { currentPlan } } = await licenseKeyRequest.get( - `${await getLicenseServerUrl()}/api/license/v1/plan` - ); - - this.globalFeatureSet = currentPlan; - this.instanceType = 'enterprise-self-hosted'; - } + return; + } + + if (licenseKey) { + // license key is present -> validate it + const token = await refreshLicenseKeyToken(); + + if (token) { + const { data: { currentPlan } } = await licenseKeyRequest.get( + `${await getLicenseServerUrl()}/api/license/v1/plan` + ); + + this.globalFeatureSet = currentPlan; + this.instanceType = 'enterprise-self-hosted'; } - } catch (err) { - // case: self-hosted free - Sentry.setUser(null); - Sentry.captureException(err); } } @@ -135,4 +128,4 @@ class EELicenseService { } } -export default new EELicenseService(); \ No newline at end of file +export default new EELicenseService(); diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts index 4d26bded9c..90f8b8bdb7 100644 --- a/backend/src/helpers/integration.ts +++ b/backend/src/helpers/integration.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Types } from 'mongoose'; import { Bot, @@ -48,66 +47,59 @@ const handleOAuthExchangeHelper = async ({ code: string; environment: string; }) => { - let integrationAuth; - try { - const bot = await Bot.findOne({ - workspace: workspaceId, - isActive: true + const bot = await Bot.findOne({ + workspace: workspaceId, + isActive: true + }); + + if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange'); + + // exchange code for access and refresh tokens + const res = await exchangeCode({ + integration, + code + }); + + const update: Update = { + workspace: workspaceId, + integration + } + + switch (integration) { + case INTEGRATION_VERCEL: + update.teamId = res.teamId; + break; + case INTEGRATION_NETLIFY: + update.accountId = res.accountId; + break; + } + + const integrationAuth = await IntegrationAuth.findOneAndUpdate({ + workspace: workspaceId, + integration + }, update, { + new: true, + upsert: true + }); + + if (res.refreshToken) { + // case: refresh token returned from exchange + // set integration auth refresh token + await setIntegrationAuthRefreshHelper({ + integrationAuthId: integrationAuth._id.toString(), + refreshToken: res.refreshToken }); - - if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange'); - - // exchange code for access and refresh tokens - const res = await exchangeCode({ - integration, - code + } + + if (res.accessToken) { + // case: access token returned from exchange + // set integration auth access token + await setIntegrationAuthAccessHelper({ + integrationAuthId: integrationAuth._id.toString(), + accessId: null, + accessToken: res.accessToken, + accessExpiresAt: res.accessExpiresAt }); - - const update: Update = { - workspace: workspaceId, - integration - } - - switch (integration) { - case INTEGRATION_VERCEL: - update.teamId = res.teamId; - break; - case INTEGRATION_NETLIFY: - update.accountId = res.accountId; - break; - } - - integrationAuth = await IntegrationAuth.findOneAndUpdate({ - workspace: workspaceId, - integration - }, update, { - new: true, - upsert: true - }); - - if (res.refreshToken) { - // case: refresh token returned from exchange - // set integration auth refresh token - await setIntegrationAuthRefreshHelper({ - integrationAuthId: integrationAuth._id.toString(), - refreshToken: res.refreshToken - }); - } - - if (res.accessToken) { - // case: access token returned from exchange - // set integration auth access token - await setIntegrationAuthAccessHelper({ - integrationAuthId: integrationAuth._id.toString(), - accessId: null, - accessToken: res.accessToken, - accessExpiresAt: res.accessExpiresAt - }); - } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to handle OAuth2 code-token exchange') } return integrationAuth; @@ -125,47 +117,40 @@ const syncIntegrationsHelper = async ({ workspaceId: Types.ObjectId; environment?: string; }) => { - let integrations; - try { - integrations = await Integration.find({ - workspace: workspaceId, - ...(environment ? { - environment - } : {}), - isActive: true, - app: { $ne: null } + const integrations = await Integration.find({ + workspace: workspaceId, + ...(environment ? { + environment + } : {}), + isActive: true, + app: { $ne: null } + }); + + // for each workspace integration, sync/push secrets + // to that integration + for await (const integration of integrations) { + // get workspace, environment (shared) secrets + const secrets = await BotService.getSecrets({ // issue here? + workspaceId: integration.workspace, + environment: integration.environment }); - // for each workspace integration, sync/push secrets - // to that integration - for await (const integration of integrations) { - // get workspace, environment (shared) secrets - const secrets = await BotService.getSecrets({ // issue here? - workspaceId: integration.workspace, - environment: integration.environment - }); + const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth); + if (!integrationAuth) throw new Error('Failed to find integration auth'); + + // get integration auth access token + const access = await getIntegrationAuthAccessHelper({ + integrationAuthId: integration.integrationAuth + }); - const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth); - if (!integrationAuth) throw new Error('Failed to find integration auth'); - - // get integration auth access token - const access = await getIntegrationAuthAccessHelper({ - integrationAuthId: integration.integrationAuth - }); - - // sync secrets to integration - await syncSecrets({ - integration, - integrationAuth, - secrets, - accessId: access.accessId === undefined ? null : access.accessId, - accessToken: access.accessToken - }); - } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to sync secrets to integrations'); + // sync secrets to integration + await syncSecrets({ + integration, + integrationAuth, + secrets, + accessId: access.accessId === undefined ? null : access.accessId, + accessToken: access.accessToken + }); } } @@ -178,30 +163,18 @@ const syncIntegrationsHelper = async ({ * @param {String} refreshToken - decrypted refresh token */ const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => { - let refreshToken; - - try { - const integrationAuth = await IntegrationAuth - .findById(integrationAuthId) - .select('+refreshCiphertext +refreshIV +refreshTag'); + const integrationAuth = await IntegrationAuth + .findById(integrationAuthId) + .select('+refreshCiphertext +refreshIV +refreshTag'); - if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); + if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); - refreshToken = await BotService.decryptSymmetric({ - workspaceId: integrationAuth.workspace, - ciphertext: integrationAuth.refreshCiphertext as string, - iv: integrationAuth.refreshIV as string, - tag: integrationAuth.refreshTag as string - }); - - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - if(err instanceof RequestError) - throw err - else - throw new Error('Failed to get integration refresh token'); - } + const refreshToken = await BotService.decryptSymmetric({ + workspaceId: integrationAuth.workspace, + ciphertext: integrationAuth.refreshCiphertext as string, + iv: integrationAuth.refreshIV as string, + tag: integrationAuth.refreshTag as string + }); return refreshToken; } @@ -217,50 +190,40 @@ const syncIntegrationsHelper = async ({ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => { let accessId; let accessToken; - try { - const integrationAuth = await IntegrationAuth - .findById(integrationAuthId) - .select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag'); + const integrationAuth = await IntegrationAuth + .findById(integrationAuthId) + .select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag'); - if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); + if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); - accessToken = await BotService.decryptSymmetric({ - workspaceId: integrationAuth.workspace, - ciphertext: integrationAuth.accessCiphertext as string, - iv: integrationAuth.accessIV as string, - tag: integrationAuth.accessTag as string - }); + accessToken = await BotService.decryptSymmetric({ + workspaceId: integrationAuth.workspace, + ciphertext: integrationAuth.accessCiphertext as string, + iv: integrationAuth.accessIV as string, + tag: integrationAuth.accessTag as string + }); - if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) { - // there is a access token expiration date - // and refresh token to exchange with the OAuth2 server - - if (integrationAuth.accessExpiresAt < new Date()) { - // access token is expired - const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId }); - accessToken = await exchangeRefresh({ - integrationAuth, - refreshToken - }); - } - } + if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) { + // there is a access token expiration date + // and refresh token to exchange with the OAuth2 server - if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) { - accessId = await BotService.decryptSymmetric({ - workspaceId: integrationAuth.workspace, - ciphertext: integrationAuth.accessIdCiphertext as string, - iv: integrationAuth.accessIdIV as string, - tag: integrationAuth.accessIdTag as string + if (integrationAuth.accessExpiresAt < new Date()) { + // access token is expired + const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId }); + accessToken = await exchangeRefresh({ + integrationAuth, + refreshToken }); } - - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - if(err instanceof RequestError) - throw err - else - throw new Error('Failed to get integration access token'); + } + + if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) { + accessId = await BotService.decryptSymmetric({ + workspaceId: integrationAuth.workspace, + ciphertext: integrationAuth.accessIdCiphertext as string, + iv: integrationAuth.accessIdIV as string, + tag: integrationAuth.accessIdTag as string + }); } return ({ @@ -285,34 +248,27 @@ const setIntegrationAuthRefreshHelper = async ({ refreshToken: string; }) => { - let integrationAuth; - try { - integrationAuth = await IntegrationAuth - .findById(integrationAuthId); - - if (!integrationAuth) throw new Error('Failed to find integration auth'); - - const obj = await BotService.encryptSymmetric({ - workspaceId: integrationAuth.workspace, - plaintext: refreshToken - }); - - integrationAuth = await IntegrationAuth.findOneAndUpdate({ - _id: integrationAuthId - }, { - refreshCiphertext: obj.ciphertext, - refreshIV: obj.iv, - refreshTag: obj.tag, - algorithm: ALGORITHM_AES_256_GCM, - keyEncoding: ENCODING_SCHEME_UTF8 - }, { - new: true - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to set integration auth refresh token'); - } + let integrationAuth = await IntegrationAuth + .findById(integrationAuthId); + + if (!integrationAuth) throw new Error('Failed to find integration auth'); + + const obj = await BotService.encryptSymmetric({ + workspaceId: integrationAuth.workspace, + plaintext: refreshToken + }); + + integrationAuth = await IntegrationAuth.findOneAndUpdate({ + _id: integrationAuthId + }, { + refreshCiphertext: obj.ciphertext, + refreshIV: obj.iv, + refreshTag: obj.tag, + algorithm: ALGORITHM_AES_256_GCM, + keyEncoding: ENCODING_SCHEME_UTF8 + }, { + new: true + }); return integrationAuth; } @@ -337,46 +293,39 @@ const setIntegrationAuthAccessHelper = async ({ accessToken: string; accessExpiresAt: Date | undefined; }) => { - let integrationAuth; - try { - integrationAuth = await IntegrationAuth.findById(integrationAuthId); - - if (!integrationAuth) throw new Error('Failed to find integration auth'); - - const encryptedAccessTokenObj = await BotService.encryptSymmetric({ + let integrationAuth = await IntegrationAuth.findById(integrationAuthId); + + if (!integrationAuth) throw new Error('Failed to find integration auth'); + + const encryptedAccessTokenObj = await BotService.encryptSymmetric({ + workspaceId: integrationAuth.workspace, + plaintext: accessToken + }); + + let encryptedAccessIdObj; + if (accessId) { + encryptedAccessIdObj = await BotService.encryptSymmetric({ workspaceId: integrationAuth.workspace, - plaintext: accessToken - }); - - let encryptedAccessIdObj; - if (accessId) { - encryptedAccessIdObj = await BotService.encryptSymmetric({ - workspaceId: integrationAuth.workspace, - plaintext: accessId - }); - } - - integrationAuth = await IntegrationAuth.findOneAndUpdate({ - _id: integrationAuthId - }, { - accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined, - accessIdIV: encryptedAccessIdObj?.iv ?? undefined, - accessIdTag: encryptedAccessIdObj?.tag ?? undefined, - accessCiphertext: encryptedAccessTokenObj.ciphertext, - accessIV: encryptedAccessTokenObj.iv, - accessTag: encryptedAccessTokenObj.tag, - accessExpiresAt, - algorithm: ALGORITHM_AES_256_GCM, - keyEncoding: ENCODING_SCHEME_UTF8 - }, { - new: true - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to save integration auth access token'); + plaintext: accessId + }); } + integrationAuth = await IntegrationAuth.findOneAndUpdate({ + _id: integrationAuthId + }, { + accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined, + accessIdIV: encryptedAccessIdObj?.iv ?? undefined, + accessIdTag: encryptedAccessIdObj?.tag ?? undefined, + accessCiphertext: encryptedAccessTokenObj.ciphertext, + accessIV: encryptedAccessTokenObj.iv, + accessTag: encryptedAccessTokenObj.tag, + accessExpiresAt, + algorithm: ALGORITHM_AES_256_GCM, + keyEncoding: ENCODING_SCHEME_UTF8 + }, { + new: true + }); + return integrationAuth; } diff --git a/backend/src/helpers/membership.ts b/backend/src/helpers/membership.ts index ee85fbb382..074ebe777d 100644 --- a/backend/src/helpers/membership.ts +++ b/backend/src/helpers/membership.ts @@ -1,7 +1,6 @@ -import * as Sentry from '@sentry/node'; -import { Types } from 'mongoose'; -import { Membership, Key } from '../models'; -import { MembershipNotFoundError, BadRequestError } from '../utils/errors'; +import { Types } from "mongoose"; +import { Membership, Key } from "../models"; +import { MembershipNotFoundError, BadRequestError } from "../utils/errors"; /** * Validate that user with id [userId] is a member of workspace with id [workspaceId] @@ -18,23 +17,23 @@ const validateMembership = async ({ }: { userId: Types.ObjectId | string; workspaceId: Types.ObjectId | string; - acceptedRoles?: Array<'admin' | 'member'>; + acceptedRoles?: Array<"admin" | "member">; }) => { const membership = await Membership.findOne({ user: userId, workspace: workspaceId, - }).populate('workspace'); + }).populate("workspace"); if (!membership) { throw MembershipNotFoundError({ - message: 'Failed to find workspace membership', + message: "Failed to find workspace membership", }); } if (acceptedRoles) { if (!acceptedRoles.includes(membership.role)) { throw BadRequestError({ - message: 'Failed authorization for membership role', + message: "Failed authorization for membership role", }); } } @@ -48,15 +47,7 @@ const validateMembership = async ({ * @return {Object} membership - membership */ const findMembership = async (queryObj: any) => { - let membership; - try { - membership = await Membership.findOne(queryObj); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to find membership'); - } - + const membership = await Membership.findOne(queryObj); return membership; }; @@ -77,31 +68,24 @@ const addMemberships = async ({ workspaceId: string; roles: string[]; }): Promise => { - try { - const operations = userIds.map((userId, idx) => { - return { - updateOne: { - filter: { - user: userId, - workspace: workspaceId, - role: roles[idx], - }, - update: { - user: userId, - workspace: workspaceId, - role: roles[idx], - }, - upsert: true, + const operations = userIds.map((userId, idx) => { + return { + updateOne: { + filter: { + user: userId, + workspace: workspaceId, + role: roles[idx], }, - }; - }); - - await Membership.bulkWrite(operations as any); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to add users to workspace'); - } + update: { + user: userId, + workspace: workspaceId, + role: roles[idx], + }, + upsert: true, + }, + }; + }); + await Membership.bulkWrite(operations as any); }; /** @@ -110,24 +94,17 @@ const addMemberships = async ({ * @param {String} obj.membershipId - id of membership to delete */ const deleteMembership = async ({ membershipId }: { membershipId: string }) => { - let deletedMembership; - try { - deletedMembership = await Membership.findOneAndDelete({ - _id: membershipId, - }); + const deletedMembership = await Membership.findOneAndDelete({ + _id: membershipId, + }); - // delete keys associated with the membership - if (deletedMembership?.user) { - // case: membership had a registered user - await Key.deleteMany({ - receiver: deletedMembership.user, - workspace: deletedMembership.workspace, - }); - } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to delete membership'); + // delete keys associated with the membership + if (deletedMembership?.user) { + // case: membership had a registered user + await Key.deleteMany({ + receiver: deletedMembership.user, + workspace: deletedMembership.workspace, + }); } return deletedMembership; diff --git a/backend/src/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index 386db2c392..111573916b 100644 --- a/backend/src/helpers/nodemailer.ts +++ b/backend/src/helpers/nodemailer.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import fs from 'fs'; import path from 'path'; import handlebars from 'handlebars'; @@ -26,24 +25,19 @@ const sendMail = async ({ substitutions: any; }) => { if (await getSmtpConfigured()) { - try { - const html = fs.readFileSync( - path.resolve(__dirname, '../templates/' + template), - 'utf8' - ); - const temp = handlebars.compile(html); - const htmlToSend = temp(substitutions); + const html = fs.readFileSync( + path.resolve(__dirname, '../templates/' + template), + 'utf8' + ); + const temp = handlebars.compile(html); + const htmlToSend = temp(substitutions); - await smtpTransporter.sendMail({ - from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`, - to: recipients.join(', '), - subject: subjectLine, - html: htmlToSend - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - } + await smtpTransporter.sendMail({ + from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`, + to: recipients.join(', '), + subject: subjectLine, + html: htmlToSend + }); } }; diff --git a/backend/src/helpers/signup.ts b/backend/src/helpers/signup.ts index dfca967369..9a70d0a57b 100644 --- a/backend/src/helpers/signup.ts +++ b/backend/src/helpers/signup.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { IUser } from '../models'; import { createOrganization } from './organization'; import { addMembershipsOrg } from './membershipOrg'; @@ -15,28 +14,20 @@ import { TOKEN_EMAIL_CONFIRMATION } from '../variables'; * @returns {Boolean} success - whether or not operation was successful */ const sendEmailVerification = async ({ email }: { email: string }) => { - try { - const token = await TokenService.createToken({ - type: TOKEN_EMAIL_CONFIRMATION, - email - }); + const token = await TokenService.createToken({ + type: TOKEN_EMAIL_CONFIRMATION, + email + }); - // send mail - await sendMail({ - template: 'emailVerification.handlebars', - subjectLine: 'Infisical confirmation code', - recipients: [email], - substitutions: { - code: token - } - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error( - "Ouch. We weren't able to send your email verification code" - ); - } + // send mail + await sendMail({ + template: 'emailVerification.handlebars', + subjectLine: 'Infisical confirmation code', + recipients: [email], + substitutions: { + code: token + } + }); }; /** @@ -52,17 +43,11 @@ const checkEmailVerification = async ({ email: string; code: string; }) => { - try { - await TokenService.validateToken({ - type: TOKEN_EMAIL_CONFIRMATION, - email, - token: code - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Oops. We weren't able to verify"); - } + await TokenService.validateToken({ + type: TOKEN_EMAIL_CONFIRMATION, + email, + token: code + }); }; /** diff --git a/backend/src/helpers/workspace.ts b/backend/src/helpers/workspace.ts index 6bc8809812..eb74bf06dc 100644 --- a/backend/src/helpers/workspace.ts +++ b/backend/src/helpers/workspace.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/node'; import { Workspace, Bot, @@ -23,31 +22,24 @@ const createWorkspace = async ({ name: string; organizationId: string; }) => { - let workspace; - try { - // create workspace - workspace = await new Workspace({ - name, - organization: organizationId, - autoCapitalization: true - }).save(); - - // initialize bot for workspace - await createBot({ - name: 'Infisical Bot', - workspaceId: workspace._id - }); - - // initialize blind index salt for workspace - await SecretService.createSecretBlindIndexData({ - workspaceId: workspace._id - }); + // create workspace + const workspace = await new Workspace({ + name, + organization: organizationId, + autoCapitalization: true + }).save(); + + // initialize bot for workspace + await createBot({ + name: 'Infisical Bot', + workspaceId: workspace._id + }); + + // initialize blind index salt for workspace + await SecretService.createSecretBlindIndexData({ + workspaceId: workspace._id + }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to create workspace'); - } return workspace; }; @@ -59,25 +51,19 @@ const createWorkspace = async ({ * @param {String} obj.id - id of workspace to delete */ const deleteWorkspace = async ({ id }: { id: string }) => { - try { - await Workspace.deleteOne({ _id: id }); - await Bot.deleteOne({ - workspace: id - }); - await Membership.deleteMany({ - workspace: id - }); - await Secret.deleteMany({ - workspace: id - }); - await Key.deleteMany({ - workspace: id - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to delete workspace'); - } + await Workspace.deleteOne({ _id: id }); + await Bot.deleteOne({ + workspace: id + }); + await Membership.deleteMany({ + workspace: id + }); + await Secret.deleteMany({ + workspace: id + }); + await Key.deleteMany({ + workspace: id + }); }; export { diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index e6082d7c5e..751d52793e 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1,4 +1,3 @@ -import * as Sentry from "@sentry/node"; import _ from 'lodash'; import AWS from 'aws-sdk'; import { @@ -61,115 +60,109 @@ const syncSecrets = async ({ accessId: string | null; accessToken: string; }) => { - try { - switch (integration.integration) { - case INTEGRATION_AZURE_KEY_VAULT: - await syncSecretsAzureKeyVault({ + switch (integration.integration) { + case INTEGRATION_AZURE_KEY_VAULT: + await syncSecretsAzureKeyVault({ + integration, + secrets, + accessToken + }); + break; + case INTEGRATION_AWS_PARAMETER_STORE: + await syncSecretsAWSParameterStore({ + integration, + secrets, + accessId, + accessToken + }); + break; + case INTEGRATION_AWS_SECRET_MANAGER: + await syncSecretsAWSSecretManager({ + integration, + secrets, + accessId, + accessToken + }); + break; + case INTEGRATION_HEROKU: + await syncSecretsHeroku({ + integration, + secrets, + accessToken, + }); + break; + case INTEGRATION_VERCEL: + await syncSecretsVercel({ + integration, + integrationAuth, + secrets, + accessToken, + }); + break; + case INTEGRATION_NETLIFY: + await syncSecretsNetlify({ + integration, + integrationAuth, + secrets, + accessToken, + }); + break; + case INTEGRATION_GITHUB: + await syncSecretsGitHub({ + integration, + secrets, + accessToken, + }); + break; + case INTEGRATION_GITLAB: + await syncSecretsGitLab({ + integration, + secrets, + accessToken, + }); + break; + case INTEGRATION_RENDER: + await syncSecretsRender({ + integration, + secrets, + accessToken, + }); + break; + case INTEGRATION_RAILWAY: + await syncSecretsRailway({ + integration, + secrets, + accessToken + }); + break; + case INTEGRATION_FLYIO: + await syncSecretsFlyio({ + integration, + secrets, + accessToken, + }); + break; + case INTEGRATION_CIRCLECI: + await syncSecretsCircleCI({ + integration, + secrets, + accessToken, + }); + break; + case INTEGRATION_TRAVISCI: + await syncSecretsTravisCI({ + integration, + secrets, + accessToken, + }); + break; + case INTEGRATION_SUPABASE: + await syncSecretsSupabase({ integration, secrets, accessToken - }); - break; - case INTEGRATION_AWS_PARAMETER_STORE: - await syncSecretsAWSParameterStore({ - integration, - secrets, - accessId, - accessToken - }); - break; - case INTEGRATION_AWS_SECRET_MANAGER: - await syncSecretsAWSSecretManager({ - integration, - secrets, - accessId, - accessToken - }); - break; - case INTEGRATION_HEROKU: - await syncSecretsHeroku({ - integration, - secrets, - accessToken, - }); - break; - case INTEGRATION_VERCEL: - await syncSecretsVercel({ - integration, - integrationAuth, - secrets, - accessToken, - }); - break; - case INTEGRATION_NETLIFY: - await syncSecretsNetlify({ - integration, - integrationAuth, - secrets, - accessToken, - }); - break; - case INTEGRATION_GITHUB: - await syncSecretsGitHub({ - integration, - secrets, - accessToken, - }); - break; - case INTEGRATION_GITLAB: - await syncSecretsGitLab({ - integration, - secrets, - accessToken, - }); - break; - case INTEGRATION_RENDER: - await syncSecretsRender({ - integration, - secrets, - accessToken, - }); - break; - case INTEGRATION_RAILWAY: - await syncSecretsRailway({ - integration, - secrets, - accessToken - }); - break; - case INTEGRATION_FLYIO: - await syncSecretsFlyio({ - integration, - secrets, - accessToken, - }); - break; - case INTEGRATION_CIRCLECI: - await syncSecretsCircleCI({ - integration, - secrets, - accessToken, - }); - break; - case INTEGRATION_TRAVISCI: - await syncSecretsTravisCI({ - integration, - secrets, - accessToken, - }); - break; - case INTEGRATION_SUPABASE: - await syncSecretsSupabase({ - integration, - secrets, - accessToken - }); - break; - } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to sync secrets to integration'); + }); + break; } }; @@ -189,181 +182,169 @@ const syncSecretsAzureKeyVault = async ({ secrets: any; accessToken: string; }) => { - try { - interface GetAzureKeyVaultSecret { - id: string; // secret URI - attributes: { - enabled: true, - created: number; - updated: number; - recoveryLevel: string; - recoverableDays: number; - } + interface GetAzureKeyVaultSecret { + id: string; // secret URI + attributes: { + enabled: true, + created: number; + updated: number; + recoveryLevel: string; + recoverableDays: number; } - - interface AzureKeyVaultSecret extends GetAzureKeyVaultSecret { - key: string; - } - - /** - * Return all secrets from Azure Key Vault by paginating through URL [url] - * @param {String} url - pagination URL to get next set of secrets from Azure Key Vault - * @returns - */ - const paginateAzureKeyVaultSecrets = async (url: string) => { - let result: GetAzureKeyVaultSecret[] = []; - try { - while (url) { - const res = await standardRequest.get(url, { - headers: { - Authorization: `Bearer ${accessToken}` - } - }); - - result = result.concat(res.data.value); - - url = res.data.nextLink; - } - - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - } - - return result; - } - - const getAzureKeyVaultSecrets = await paginateAzureKeyVaultSecrets(`${integration.app}/secrets?api-version=7.3`); - - let lastSlashIndex: number; - const res = (await Promise.all(getAzureKeyVaultSecrets.map(async (getAzureKeyVaultSecret) => { - if (!lastSlashIndex) { - lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf('/'); - } - - const azureKeyVaultSecret = await standardRequest.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, { + } + + interface AzureKeyVaultSecret extends GetAzureKeyVaultSecret { + key: string; + } + + /** + * Return all secrets from Azure Key Vault by paginating through URL [url] + * @param {String} url - pagination URL to get next set of secrets from Azure Key Vault + * @returns + */ + const paginateAzureKeyVaultSecrets = async (url: string) => { + let result: GetAzureKeyVaultSecret[] = []; + while (url) { + const res = await standardRequest.get(url, { headers: { - 'Authorization': `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}` } }); - - return ({ - ...azureKeyVaultSecret.data, - key: getAzureKeyVaultSecret.id.substring(lastSlashIndex + 1), - }); - }))) - .reduce((obj: any, secret: any) => ({ - ...obj, - [secret.key]: secret - }), {}); + + result = result.concat(res.data.value); + + url = res.data.nextLink; + } - const setSecrets: { - key: string; - value: string; - }[] = []; + return result; + } + + const getAzureKeyVaultSecrets = await paginateAzureKeyVaultSecrets(`${integration.app}/secrets?api-version=7.3`); + + let lastSlashIndex: number; + const res = (await Promise.all(getAzureKeyVaultSecrets.map(async (getAzureKeyVaultSecret) => { + if (!lastSlashIndex) { + lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf('/'); + } + + const azureKeyVaultSecret = await standardRequest.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, { + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); - Object.keys(secrets).forEach((key) => { - const hyphenatedKey = key.replace(/_/g, '-'); - if (!(hyphenatedKey in res)) { - // case: secret has been created + return ({ + ...azureKeyVaultSecret.data, + key: getAzureKeyVaultSecret.id.substring(lastSlashIndex + 1), + }); + }))) + .reduce((obj: any, secret: any) => ({ + ...obj, + [secret.key]: secret + }), {}); + + const setSecrets: { + key: string; + value: string; + }[] = []; + + Object.keys(secrets).forEach((key) => { + const hyphenatedKey = key.replace(/_/g, '-'); + if (!(hyphenatedKey in res)) { + // case: secret has been created + setSecrets.push({ + key: hyphenatedKey, + value: secrets[key] + }); + } else { + if (secrets[key] !== res[hyphenatedKey].value) { + // case: secret has been updated setSecrets.push({ key: hyphenatedKey, value: secrets[key] }); - } else { - if (secrets[key] !== res[hyphenatedKey].value) { - // case: secret has been updated - setSecrets.push({ - key: hyphenatedKey, - value: secrets[key] - }); - } } - }); - - const deleteSecrets: AzureKeyVaultSecret[] = []; - - Object.keys(res).forEach((key) => { - const underscoredKey = key.replace(/-/g, '_'); - if (!(underscoredKey in secrets)) { - deleteSecrets.push(res[key]); - } - }); + } + }); + + const deleteSecrets: AzureKeyVaultSecret[] = []; + + Object.keys(res).forEach((key) => { + const underscoredKey = key.replace(/-/g, '_'); + if (!(underscoredKey in secrets)) { + deleteSecrets.push(res[key]); + } + }); - const setSecretAzureKeyVault = async ({ - key, - value, - integration, - accessToken - }: { - key: string; - value: string; - integration: IIntegration; - accessToken: string; - }) => { - let isSecretSet = false; - let maxTries = 6; + const setSecretAzureKeyVault = async ({ + key, + value, + integration, + accessToken + }: { + key: string; + value: string; + integration: IIntegration; + accessToken: string; + }) => { + let isSecretSet = false; + let maxTries = 6; + + while (!isSecretSet && maxTries > 0) { + // try to set secret + try { + await standardRequest.put( + `${integration.app}/secrets/${key}?api-version=7.3`, + { + value + }, + { + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + + isSecretSet = true; - while (!isSecretSet && maxTries > 0) { - // try to set secret - try { - await standardRequest.put( - `${integration.app}/secrets/${key}?api-version=7.3`, - { - value - }, + } catch (err) { + const error: any = err; + if (error?.response?.data?.error?.innererror?.code === 'ObjectIsDeletedButRecoverable') { + await standardRequest.post( + `${integration.app}/deletedsecrets/${key}/recover?api-version=7.3`, {}, { headers: { Authorization: `Bearer ${accessToken}` } } ); - - isSecretSet = true; - - } catch (err) { - const error: any = err; - if (error?.response?.data?.error?.innererror?.code === 'ObjectIsDeletedButRecoverable') { - await standardRequest.post( - `${integration.app}/deletedsecrets/${key}/recover?api-version=7.3`, {}, - { - headers: { - Authorization: `Bearer ${accessToken}` - } - } - ); - await new Promise(resolve => setTimeout(resolve, 10000)); - } else { - await new Promise(resolve => setTimeout(resolve, 10000)); - maxTries--; - } + await new Promise(resolve => setTimeout(resolve, 10000)); + } else { + await new Promise(resolve => setTimeout(resolve, 10000)); + maxTries--; } } } - - // Sync/push set secrets - for await (const setSecret of setSecrets) { - const { key, value } = setSecret; - setSecretAzureKeyVault({ - key, - value, - integration, - accessToken - }); - } - - for await (const deleteSecret of deleteSecrets) { - const { key } = deleteSecret; - await standardRequest.delete(`${integration.app}/secrets/${key}?api-version=7.3`, { - headers: { - 'Authorization': `Bearer ${accessToken}` - } - }); - } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to sync secrets to Azure Key Vault'); + } + + // Sync/push set secrets + for await (const setSecret of setSecrets) { + const { key, value } = setSecret; + setSecretAzureKeyVault({ + key, + value, + integration, + accessToken + }); + } + + for await (const deleteSecret of deleteSecrets) { + const { key } = deleteSecret; + await standardRequest.delete(`${integration.app}/secrets/${key}?api-version=7.3`, { + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); } }; @@ -386,87 +367,81 @@ const syncSecretsAWSParameterStore = async ({ accessId: string | null; accessToken: string; }) => { - try { - if (!accessId) return; + if (!accessId) return; - AWS.config.update({ - region: integration.region, - accessKeyId: accessId, - secretAccessKey: accessToken - }); + AWS.config.update({ + region: integration.region, + accessKeyId: accessId, + secretAccessKey: accessToken + }); - const ssm = new AWS.SSM({ - apiVersion: '2014-11-06', - region: integration.region - }); - - const params = { - Path: integration.path, - Recursive: true, - WithDecryption: true - }; + const ssm = new AWS.SSM({ + apiVersion: '2014-11-06', + region: integration.region + }); + + const params = { + Path: integration.path, + Recursive: true, + WithDecryption: true + }; - const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters - - let awsParameterStoreSecretsObj: { - [key: string]: any // TODO: fix type - } = {}; + const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters + + let awsParameterStoreSecretsObj: { + [key: string]: any // TODO: fix type + } = {}; - if (parameterList) { - awsParameterStoreSecretsObj = parameterList.reduce((obj: any, secret: any) => ({ - ...obj, - [secret.Name.split("/").pop()]: secret - }), {}); - } + if (parameterList) { + awsParameterStoreSecretsObj = parameterList.reduce((obj: any, secret: any) => ({ + ...obj, + [secret.Name.split("/").pop()]: secret + }), {}); + } - // Identify secrets to create - Object.keys(secrets).map(async (key) => { - if (!(key in awsParameterStoreSecretsObj)) { - // case: secret does not exist in AWS parameter store - // -> create secret + // Identify secrets to create + Object.keys(secrets).map(async (key) => { + if (!(key in awsParameterStoreSecretsObj)) { + // case: secret does not exist in AWS parameter store + // -> create secret + await ssm.putParameter({ + Name: `${integration.path}${key}`, + Type: 'SecureString', + Value: secrets[key], + Overwrite: true + }).promise(); + } else { + // case: secret exists in AWS parameter store + + if (awsParameterStoreSecretsObj[key].Value !== secrets[key]) { + // case: secret value doesn't match one in AWS parameter store + // -> update secret await ssm.putParameter({ Name: `${integration.path}${key}`, Type: 'SecureString', Value: secrets[key], Overwrite: true }).promise(); - } else { - // case: secret exists in AWS parameter store - - if (awsParameterStoreSecretsObj[key].Value !== secrets[key]) { - // case: secret value doesn't match one in AWS parameter store - // -> update secret - await ssm.putParameter({ - Name: `${integration.path}${key}`, - Type: 'SecureString', - Value: secrets[key], - Overwrite: true - }).promise(); - } } - }); + } + }); - // Identify secrets to delete - Object.keys(awsParameterStoreSecretsObj).map(async (key) => { - if (!(key in secrets)) { - // case: - // -> delete secret - await ssm.deleteParameter({ - Name: awsParameterStoreSecretsObj[key].Name - }).promise(); - } - }); + // Identify secrets to delete + Object.keys(awsParameterStoreSecretsObj).map(async (key) => { + if (!(key in secrets)) { + // case: + // -> delete secret + await ssm.deleteParameter({ + Name: awsParameterStoreSecretsObj[key].Name + }).promise(); + } + }); - AWS.config.update({ - region: undefined, - accessKeyId: undefined, - secretAccessKey: undefined - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to sync secrets to AWS Parameter Store'); - } + AWS.config.update({ + region: undefined, + accessKeyId: undefined, + secretAccessKey: undefined + }); } /** @@ -536,11 +511,7 @@ const syncSecretsAWSSecretManager = async ({ Name: integration.app, SecretString: JSON.stringify(secrets) })); - } else { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to sync secrets to AWS Secret Manager'); - } + } AWS.config.update({ region: undefined, accessKeyId: undefined, @@ -565,29 +536,9 @@ const syncSecretsHeroku = async ({ secrets: any; accessToken: string; }) => { - try { - const herokuSecrets = ( - await standardRequest.get( - `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, - { - headers: { - Accept: "application/vnd.heroku+json; version=3", - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - }, - } - ) - ).data; - - Object.keys(herokuSecrets).forEach((key) => { - if (!(key in secrets)) { - secrets[key] = null; - } - }); - - await standardRequest.patch( + const herokuSecrets = ( + await standardRequest.get( `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, - secrets, { headers: { Accept: "application/vnd.heroku+json; version=3", @@ -595,12 +546,26 @@ const syncSecretsHeroku = async ({ 'Accept-Encoding': 'application/json' }, } - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to Heroku"); - } + ) + ).data; + + Object.keys(herokuSecrets).forEach((key) => { + if (!(key in secrets)) { + secrets[key] = null; + } + }); + + await standardRequest.patch( + `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, + secrets, + { + headers: { + Accept: "application/vnd.heroku+json; version=3", + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + }, + } + ); }; /** @@ -628,127 +593,141 @@ const syncSecretsVercel = async ({ target: string[]; gitBranch?: string; } - - try { - // Get all (decrypted) secrets back from Vercel in - // decrypted format - const params: { [key: string]: string } = { - decrypt: "true", - ...(integrationAuth?.teamId - ? { - teamId: integrationAuth.teamId, + // Get all (decrypted) secrets back from Vercel in + // decrypted format + const params: { [key: string]: string } = { + decrypt: "true", + ...(integrationAuth?.teamId + ? { + teamId: integrationAuth.teamId, + } + : {}), + }; + + const vercelSecrets: VercelSecret[] = (await standardRequest.get( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, + { + params, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + } + } + )) + .data + .envs + .filter((secret: VercelSecret) => { + if (!secret.target.includes(integration.targetEnvironment)) { + // case: secret does not have the same target environment + return false; + } + + if (integration.targetEnvironment === 'preview' && integration.path && integration.path !== secret.gitBranch) { + // case: secret on preview environment does not have same target git branch + return false; + } + + return true; + }); + + // return secret.target.includes(integration.targetEnvironment); + + const res: { [key: string]: VercelSecret } = {}; + + for await (const vercelSecret of vercelSecrets) { + if (vercelSecret.type === 'encrypted') { + // case: secret is encrypted -> need to decrypt + const decryptedSecret = (await standardRequest.get( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`, + { + params, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + } } - : {}), - }; - - const vercelSecrets: VercelSecret[] = (await standardRequest.get( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, + )).data; + + res[vercelSecret.key] = decryptedSecret; + } else { + res[vercelSecret.key] = vercelSecret; + } + } + + const updateSecrets: VercelSecret[] = []; + const deleteSecrets: VercelSecret[] = []; + const newSecrets: VercelSecret[] = []; + + // Identify secrets to create + Object.keys(secrets).map((key) => { + if (!(key in res)) { + // case: secret has been created + newSecrets.push({ + key: key, + value: secrets[key], + type: "encrypted", + target: [integration.targetEnvironment], + ...(integration.path ? { + gitBranch: integration.path + } : {}) + }); + } + }); + + // Identify secrets to update and delete + Object.keys(res).map((key) => { + if (key in secrets) { + if (res[key].value !== secrets[key]) { + // case: secret value has changed + updateSecrets.push({ + id: res[key].id, + key: key, + value: secrets[key], + type: res[key].type, + target: res[key].target.includes(integration.targetEnvironment) + ? [...res[key].target] + : [...res[key].target, integration.targetEnvironment], + ...(integration.path ? { + gitBranch: integration.path + } : {}) + }); + } + } else { + // case: secret has been deleted + deleteSecrets.push({ + id: res[key].id, + key: key, + value: res[key].value, + type: "encrypted", // value doesn't matter + target: [integration.targetEnvironment], + ...(integration.path ? { + gitBranch: integration.path + } : {}) + }); + } + }); + + // Sync/push new secrets + if (newSecrets.length > 0) { + await standardRequest.post( + `${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`, + newSecrets, { params, headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - } - } - )) - .data - .envs - .filter((secret: VercelSecret) => { - if (!secret.target.includes(integration.targetEnvironment)) { - // case: secret does not have the same target environment - return false; + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + }, } + ); + } - if (integration.targetEnvironment === 'preview' && integration.path && integration.path !== secret.gitBranch) { - // case: secret on preview environment does not have same target git branch - return false; - } - - return true; - }); - - // return secret.target.includes(integration.targetEnvironment); - - const res: { [key: string]: VercelSecret } = {}; - - for await (const vercelSecret of vercelSecrets) { - if (vercelSecret.type === 'encrypted') { - // case: secret is encrypted -> need to decrypt - const decryptedSecret = (await standardRequest.get( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`, - { - params, - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - } - } - )).data; - - res[vercelSecret.key] = decryptedSecret; - } else { - res[vercelSecret.key] = vercelSecret; - } - } - - const updateSecrets: VercelSecret[] = []; - const deleteSecrets: VercelSecret[] = []; - const newSecrets: VercelSecret[] = []; - - // Identify secrets to create - Object.keys(secrets).map((key) => { - if (!(key in res)) { - // case: secret has been created - newSecrets.push({ - key: key, - value: secrets[key], - type: "encrypted", - target: [integration.targetEnvironment], - ...(integration.path ? { - gitBranch: integration.path - } : {}) - }); - } - }); - - // Identify secrets to update and delete - Object.keys(res).map((key) => { - if (key in secrets) { - if (res[key].value !== secrets[key]) { - // case: secret value has changed - updateSecrets.push({ - id: res[key].id, - key: key, - value: secrets[key], - type: res[key].type, - target: res[key].target.includes(integration.targetEnvironment) - ? [...res[key].target] - : [...res[key].target, integration.targetEnvironment], - ...(integration.path ? { - gitBranch: integration.path - } : {}) - }); - } - } else { - // case: secret has been deleted - deleteSecrets.push({ - id: res[key].id, - key: key, - value: res[key].value, - type: "encrypted", // value doesn't matter - target: [integration.targetEnvironment], - ...(integration.path ? { - gitBranch: integration.path - } : {}) - }); - } - }); - - // Sync/push new secrets - if (newSecrets.length > 0) { - await standardRequest.post( - `${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`, - newSecrets, + for await (const secret of updateSecrets) { + if (secret.type !== 'sensitive') { + const { id, ...updatedSecret } = secret; + await standardRequest.patch( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, + updatedSecret, { params, headers: { @@ -757,41 +736,20 @@ const syncSecretsVercel = async ({ }, } ); - } - - for await (const secret of updateSecrets) { - if (secret.type !== 'sensitive') { - const { id, ...updatedSecret } = secret; - await standardRequest.patch( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, - updatedSecret, - { - params, - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - }, - } - ); - } - } + } + } - for await (const secret of deleteSecrets) { - await standardRequest.delete( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, - { - params, - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - }, - } - ); - } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to Vercel"); + for await (const secret of deleteSecrets) { + await standardRequest.delete( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, + { + params, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + }, + } + ); } }; @@ -814,92 +772,78 @@ const syncSecretsNetlify = async ({ secrets: any; accessToken: string; }) => { - try { - interface NetlifyValue { - id?: string; - context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production', - value: string; - } + interface NetlifyValue { + id?: string; + context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production', + value: string; + } - interface NetlifySecret { - key: string; - values: NetlifyValue[]; - } + interface NetlifySecret { + key: string; + values: NetlifyValue[]; + } - interface NetlifySecretsRes { - [index: string]: NetlifySecret; - } + interface NetlifySecretsRes { + [index: string]: NetlifySecret; + } - const getParams = new URLSearchParams({ - context_name: "all", // integration.context or all - site_id: integration.appId, - }); + const getParams = new URLSearchParams({ + context_name: "all", // integration.context or all + site_id: integration.appId, + }); - const res = ( - await standardRequest.get( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, - { - params: getParams, - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' + const res = ( + await standardRequest.get( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, + { + params: getParams, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + }, + } + ) + ).data.reduce( + (obj: any, secret: any) => ({ + ...obj, + [secret.key]: secret, + }), + {} + ); + + const newSecrets: NetlifySecret[] = []; // createEnvVars + const deleteSecrets: string[] = []; // deleteEnvVar + const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue + const updateSecrets: NetlifySecret[] = []; // setEnvVarValue + + // identify secrets to create and update + Object.keys(secrets).map((key) => { + if (!(key in res)) { + // case: Infisical secret does not exist in Netlify -> create secret + newSecrets.push({ + key, + values: [ + { + value: secrets[key], + context: integration.targetEnvironment, }, - } - ) - ).data.reduce( - (obj: any, secret: any) => ({ - ...obj, - [secret.key]: secret, - }), - {} - ); + ], + }); + } else { + // case: Infisical secret exists in Netlify + const contexts = res[key].values.reduce( + (obj: any, value: NetlifyValue) => ({ + ...obj, + [value.context]: value, + }), + {} + ); - const newSecrets: NetlifySecret[] = []; // createEnvVars - const deleteSecrets: string[] = []; // deleteEnvVar - const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue - const updateSecrets: NetlifySecret[] = []; // setEnvVarValue - - // identify secrets to create and update - Object.keys(secrets).map((key) => { - if (!(key in res)) { - // case: Infisical secret does not exist in Netlify -> create secret - newSecrets.push({ - key, - values: [ - { - value: secrets[key], - context: integration.targetEnvironment, - }, - ], - }); - } else { - // case: Infisical secret exists in Netlify - const contexts = res[key].values.reduce( - (obj: any, value: NetlifyValue) => ({ - ...obj, - [value.context]: value, - }), - {} - ); - - if (integration.targetEnvironment in contexts) { - // case: Netlify secret value exists in integration context - if (secrets[key] !== contexts[integration.targetEnvironment].value) { - // case: Infisical and Netlify secret values are different - // -> update Netlify secret context and value - updateSecrets.push({ - key, - values: [ - { - context: integration.targetEnvironment, - value: secrets[key], - }, - ], - }); - } - } else { - // case: Netlify secret value does not exist in integration context - // -> add the new Netlify secret context and value + if (integration.targetEnvironment in contexts) { + // case: Netlify secret value exists in integration context + if (secrets[key] !== contexts[integration.targetEnvironment].value) { + // case: Infisical and Netlify secret values are different + // -> update Netlify secret context and value updateSecrets.push({ key, values: [ @@ -910,49 +854,80 @@ const syncSecretsNetlify = async ({ ], }); } - } - }); - - // identify secrets to delete - // TODO: revise (patch case where 1 context was deleted but others still there - Object.keys(res).map((key) => { - // loop through each key's context - if (!(key in secrets)) { - // case: Netlify secret does not exist in Infisical - - const numberOfValues = res[key].values.length; - - res[key].values.forEach((value: NetlifyValue) => { - if (value.context === integration.targetEnvironment) { - if (numberOfValues <= 1) { - // case: Netlify secret value has less than 1 context -> delete secret - deleteSecrets.push(key); - } else { - // case: Netlify secret value has more than 1 context -> delete secret value context - deleteSecretValues.push({ - key, - values: [ - { - id: value.id, - context: integration.targetEnvironment, - value: value.value, - }, - ], - }); - } - } + } else { + // case: Netlify secret value does not exist in integration context + // -> add the new Netlify secret context and value + updateSecrets.push({ + key, + values: [ + { + context: integration.targetEnvironment, + value: secrets[key], + }, + ], }); } - }); + } + }); - const syncParams = new URLSearchParams({ - site_id: integration.appId, - }); + // identify secrets to delete + // TODO: revise (patch case where 1 context was deleted but others still there + Object.keys(res).map((key) => { + // loop through each key's context + if (!(key in secrets)) { + // case: Netlify secret does not exist in Infisical - if (newSecrets.length > 0) { - await standardRequest.post( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, - newSecrets, + const numberOfValues = res[key].values.length; + + res[key].values.forEach((value: NetlifyValue) => { + if (value.context === integration.targetEnvironment) { + if (numberOfValues <= 1) { + // case: Netlify secret value has less than 1 context -> delete secret + deleteSecrets.push(key); + } else { + // case: Netlify secret value has more than 1 context -> delete secret value context + deleteSecretValues.push({ + key, + values: [ + { + id: value.id, + context: integration.targetEnvironment, + value: value.value, + }, + ], + }); + } + } + }); + } + }); + + const syncParams = new URLSearchParams({ + site_id: integration.appId, + }); + + if (newSecrets.length > 0) { + await standardRequest.post( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, + newSecrets, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + }, + } + ); + } + + if (updateSecrets.length > 0) { + updateSecrets.forEach(async (secret: NetlifySecret) => { + await standardRequest.patch( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, + { + context: secret.values[0].context, + value: secret.values[0].value, + }, { params: syncParams, headers: { @@ -961,60 +936,37 @@ const syncSecretsNetlify = async ({ }, } ); - } + }); + } - if (updateSecrets.length > 0) { - updateSecrets.forEach(async (secret: NetlifySecret) => { - await standardRequest.patch( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, - { - context: secret.values[0].context, - value: secret.values[0].value, + if (deleteSecrets.length > 0) { + deleteSecrets.forEach(async (key: string) => { + await standardRequest.delete( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, - { - params: syncParams, - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - }, - } - ); - }); - } + } + ); + }); + } - if (deleteSecrets.length > 0) { - deleteSecrets.forEach(async (key: string) => { - await standardRequest.delete( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`, - { - params: syncParams, - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - }, - } - ); - }); - } - - if (deleteSecretValues.length > 0) { - deleteSecretValues.forEach(async (secret: NetlifySecret) => { - await standardRequest.delete( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`, - { - params: syncParams, - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - }, - } - ); - }); - } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to Heroku"); + if (deleteSecretValues.length > 0) { + deleteSecretValues.forEach(async (secret: NetlifySecret) => { + await standardRequest.delete( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + }, + } + ); + }); } }; @@ -1035,102 +987,96 @@ const syncSecretsGitHub = async ({ secrets: any; accessToken: string; }) => { - try { - interface GitHubRepoKey { - key_id: string; - key: string; - } + interface GitHubRepoKey { + key_id: string; + key: string; + } - interface GitHubSecret { - name: string; - created_at: string; - updated_at: string; - } + interface GitHubSecret { + name: string; + created_at: string; + updated_at: string; + } - interface GitHubSecretRes { - [index: string]: GitHubSecret; - } + interface GitHubSecretRes { + [index: string]: GitHubSecret; + } - const deleteSecrets: GitHubSecret[] = []; + const deleteSecrets: GitHubSecret[] = []; - const octokit = new Octokit({ - auth: accessToken, - }); + const octokit = new Octokit({ + auth: accessToken, + }); - // const user = (await octokit.request('GET /user', {})).data; - const repoPublicKey: GitHubRepoKey = ( + // const user = (await octokit.request('GET /user', {})).data; + const repoPublicKey: GitHubRepoKey = ( + await octokit.request( + "GET /repos/{owner}/{repo}/actions/secrets/public-key", + { + owner: integration.owner, + repo: integration.app + } + ) + ).data; + + // Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key + const encryptedSecrets: GitHubSecretRes = ( + await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", { + owner: integration.owner, + repo: integration.app, + }) + ).data.secrets.reduce( + (obj: any, secret: any) => ({ + ...obj, + [secret.name]: secret, + }), + {} + ); + + Object.keys(encryptedSecrets).map(async (key) => { + if (!(key in secrets)) { await octokit.request( - "GET /repos/{owner}/{repo}/actions/secrets/public-key", + "DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", { owner: integration.owner, - repo: integration.app + repo: integration.app, + secret_name: key, } - ) - ).data; + ); + } + }); - // Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key - const encryptedSecrets: GitHubSecretRes = ( - await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", { - owner: integration.owner, - repo: integration.app, - }) - ).data.secrets.reduce( - (obj: any, secret: any) => ({ - ...obj, - [secret.name]: secret, - }), - {} - ); + Object.keys(secrets).map((key) => { + // let encryptedSecret; + sodium.ready.then(async () => { + // convert secret & base64 key to Uint8Array. + const binkey = sodium.from_base64( + repoPublicKey.key, + sodium.base64_variants.ORIGINAL + ); + const binsec = sodium.from_string(secrets[key]); - Object.keys(encryptedSecrets).map(async (key) => { - if (!(key in secrets)) { - await octokit.request( - "DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", - { - owner: integration.owner, - repo: integration.app, - secret_name: key, - } - ); - } + // encrypt secret using libsodium + const encBytes = sodium.crypto_box_seal(binsec, binkey); + + // convert encrypted Uint8Array to base64 + const encryptedSecret = sodium.to_base64( + encBytes, + sodium.base64_variants.ORIGINAL + ); + + await octokit.request( + "PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}", + { + owner: integration.owner, + repo: integration.app, + secret_name: key, + encrypted_value: encryptedSecret, + key_id: repoPublicKey.key_id, + } + ); }); - - Object.keys(secrets).map((key) => { - // let encryptedSecret; - sodium.ready.then(async () => { - // convert secret & base64 key to Uint8Array. - const binkey = sodium.from_base64( - repoPublicKey.key, - sodium.base64_variants.ORIGINAL - ); - const binsec = sodium.from_string(secrets[key]); - - // encrypt secret using libsodium - const encBytes = sodium.crypto_box_seal(binsec, binkey); - - // convert encrypted Uint8Array to base64 - const encryptedSecret = sodium.to_base64( - encBytes, - sodium.base64_variants.ORIGINAL - ); - - await octokit.request( - "PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}", - { - owner: integration.owner, - repo: integration.app, - secret_name: key, - encrypted_value: encryptedSecret, - key_id: repoPublicKey.key_id, - } - ); - }); - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to GitHub"); - } + }); }; /** @@ -1149,25 +1095,19 @@ const syncSecretsRender = async ({ secrets: any; accessToken: string; }) => { - try { - await standardRequest.put( - `${INTEGRATION_RENDER_API_URL}/v1/services/${integration.appId}/env-vars`, - Object.keys(secrets).map((key) => ({ - key, - value: secrets[key], - })), - { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - }, - } - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to Render"); - } + await standardRequest.put( + `${INTEGRATION_RENDER_API_URL}/v1/services/${integration.appId}/env-vars`, + Object.keys(secrets).map((key) => ({ + key, + value: secrets[key], + })), + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + }, + } + ); }; /** @@ -1186,40 +1126,32 @@ const syncSecretsRailway = async ({ secrets: any; accessToken: string; }) => { - try { + const query = ` + mutation UpsertVariables($input: VariableCollectionUpsertInput!) { + variableCollectionUpsert(input: $input) + } + `; - const query = ` - mutation UpsertVariables($input: VariableCollectionUpsertInput!) { - variableCollectionUpsert(input: $input) - } - `; + const input = { + projectId: integration.appId, + environmentId: integration.targetEnvironmentId, + ...(integration.targetServiceId ? { serviceId: integration.targetServiceId } : {}), + replace: true, + variables: secrets + }; - const input = { - projectId: integration.appId, - environmentId: integration.targetEnvironmentId, - ...(integration.targetServiceId ? { serviceId: integration.targetServiceId } : {}), - replace: true, - variables: secrets - }; - - await standardRequest.post(INTEGRATION_RAILWAY_API_URL, { - query, - variables: { - input, - }, - }, { - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - 'Accept-Encoding': 'application/json' - }, - }); - - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to Railway"); - } + await standardRequest.post(INTEGRATION_RAILWAY_API_URL, { + query, + variables: { + input, + }, + }, { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'Accept-Encoding': 'application/json' + }, + }); } /** @@ -1238,120 +1170,113 @@ const syncSecretsFlyio = async ({ secrets: any; accessToken: string; }) => { - try { - // set secrets - const SetSecrets = ` - mutation($input: SetSecretsInput!) { - setSecrets(input: $input) { - release { + // set secrets + const SetSecrets = ` + mutation($input: SetSecretsInput!) { + setSecrets(input: $input) { + release { + id + version + reason + description + user { id - version - reason - description - user { - id - email - name - } - evaluationId - createdAt + email + name } + evaluationId + createdAt } } - `; - - await standardRequest.post(INTEGRATION_FLYIO_API_URL, { - query: SetSecrets, - variables: { - input: { - appId: integration.app, - secrets: Object.entries(secrets).map(([key, value]) => ({ - key, - value, - })), - }, - }, - }, { - headers: { - Authorization: "Bearer " + accessToken, - 'Accept-Encoding': 'application/json', - }, - }); - - // get secrets - interface FlyioSecret { - name: string; - digest: string; - createdAt: string; } + `; - const GetSecrets = `query ($appName: String!) { - app(name: $appName) { - secrets { - name - digest - createdAt - } - } - }`; - - const getSecretsRes = (await standardRequest.post(INTEGRATION_FLYIO_API_URL, { - query: GetSecrets, - variables: { - appName: integration.app, + await standardRequest.post(INTEGRATION_FLYIO_API_URL, { + query: SetSecrets, + variables: { + input: { + appId: integration.app, + secrets: Object.entries(secrets).map(([key, value]) => ({ + key, + value, + })), }, - }, { - headers: { - Authorization: "Bearer " + accessToken, - 'Content-Type': 'application/json', - 'Accept-Encoding': 'application/json', - }, - })).data.data.app.secrets; + }, + }, { + headers: { + Authorization: "Bearer " + accessToken, + 'Accept-Encoding': 'application/json', + }, + }); - const deleteSecretsKeys = getSecretsRes - .filter((secret: FlyioSecret) => !(secret.name in secrets)) - .map((secret: FlyioSecret) => secret.name); - - // unset (delete) secrets - const DeleteSecrets = `mutation($input: UnsetSecretsInput!) { - unsetSecrets(input: $input) { - release { - id - version - reason - description - user { - id - email - name - } - evaluationId - createdAt - } - } - }`; - - await standardRequest.post(INTEGRATION_FLYIO_API_URL, { - query: DeleteSecrets, - variables: { - input: { - appId: integration.app, - keys: deleteSecretsKeys, - }, - }, - }, { - headers: { - Authorization: "Bearer " + accessToken, - "Content-Type": "application/json", - 'Accept-Encoding': 'application/json', - }, - }); - - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to Fly.io"); + // get secrets + interface FlyioSecret { + name: string; + digest: string; + createdAt: string; } + + const GetSecrets = `query ($appName: String!) { + app(name: $appName) { + secrets { + name + digest + createdAt + } + } + }`; + + const getSecretsRes = (await standardRequest.post(INTEGRATION_FLYIO_API_URL, { + query: GetSecrets, + variables: { + appName: integration.app, + }, + }, { + headers: { + Authorization: "Bearer " + accessToken, + 'Content-Type': 'application/json', + 'Accept-Encoding': 'application/json', + }, + })).data.data.app.secrets; + + const deleteSecretsKeys = getSecretsRes + .filter((secret: FlyioSecret) => !(secret.name in secrets)) + .map((secret: FlyioSecret) => secret.name); + + // unset (delete) secrets + const DeleteSecrets = `mutation($input: UnsetSecretsInput!) { + unsetSecrets(input: $input) { + release { + id + version + reason + description + user { + id + email + name + } + evaluationId + createdAt + } + } + }`; + + await standardRequest.post(INTEGRATION_FLYIO_API_URL, { + query: DeleteSecrets, + variables: { + input: { + appId: integration.app, + keys: deleteSecretsKeys, + }, + }, + }, { + headers: { + Authorization: "Bearer " + accessToken, + "Content-Type": "application/json", + 'Accept-Encoding': 'application/json', + }, + }); }; /** @@ -1370,68 +1295,62 @@ const syncSecretsCircleCI = async ({ secrets: any; accessToken: string; }) => { - try { - const circleciOrganizationDetail = ( - await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, { + const circleciOrganizationDetail = ( + await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, { + headers: { + "Circle-Token": accessToken, + "Accept-Encoding": "application/json", + }, + }) + ).data[0]; + + const { slug } = circleciOrganizationDetail; + + // sync secrets to CircleCI + Object.keys(secrets).forEach( + async (key) => + await standardRequest.post( + `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`, + { + name: key, + value: secrets[key], + }, + { + headers: { + "Circle-Token": accessToken, + "Content-Type": "application/json", + }, + } + ) + ); + + // get secrets from CircleCI + const getSecretsRes = ( + await standardRequest.get( + `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`, + { headers: { "Circle-Token": accessToken, "Accept-Encoding": "application/json", }, - }) - ).data[0]; + } + ) + ).data?.items; - const { slug } = circleciOrganizationDetail; - - // sync secrets to CircleCI - Object.keys(secrets).forEach( - async (key) => - await standardRequest.post( - `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`, - { - name: key, - value: secrets[key], - }, - { - headers: { - "Circle-Token": accessToken, - "Content-Type": "application/json", - }, - } - ) - ); - - // get secrets from CircleCI - const getSecretsRes = ( - await standardRequest.get( - `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`, + // delete secrets from CircleCI + getSecretsRes.forEach(async (sec: any) => { + if (!(sec.name in secrets)) { + await standardRequest.delete( + `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar/${sec.name}`, { headers: { "Circle-Token": accessToken, - "Accept-Encoding": "application/json", + "Content-Type": "application/json", }, } - ) - ).data?.items; - - // delete secrets from CircleCI - getSecretsRes.forEach(async (sec: any) => { - if (!(sec.name in secrets)) { - await standardRequest.delete( - `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar/${sec.name}`, - { - headers: { - "Circle-Token": accessToken, - "Content-Type": "application/json", - }, - } - ); - } - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to CircleCI"); - } + ); + } + }); }; /** @@ -1450,88 +1369,82 @@ const syncSecretsTravisCI = async ({ secrets: any; accessToken: string; }) => { - try { - // get secrets from travis-ci - const getSecretsRes = ( - await standardRequest.get( + // get secrets from travis-ci + const getSecretsRes = ( + await standardRequest.get( + `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`, + { + headers: { + "Authorization": `token ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ) + ) + .data + ?.env_vars + .reduce((obj: any, secret: any) => ({ + ...obj, + [secret.name]: secret + }), {}); + + // add secrets + for await (const key of Object.keys(secrets)) { + if (!(key in getSecretsRes)) { + // case: secret does not exist in travis ci + // -> add secret + await standardRequest.post( `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`, + { + env_var: { + name: key, + value: secrets[key] + } + }, { headers: { "Authorization": `token ${accessToken}`, + "Content-Type": "application/json", "Accept-Encoding": "application/json", }, } - ) - ) - .data - ?.env_vars - .reduce((obj: any, secret: any) => ({ - ...obj, - [secret.name]: secret - }), {}); - - // add secrets - for await (const key of Object.keys(secrets)) { - if (!(key in getSecretsRes)) { - // case: secret does not exist in travis ci - // -> add secret - await standardRequest.post( - `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`, - { - env_var: { - name: key, - value: secrets[key] - } - }, - { - headers: { - "Authorization": `token ${accessToken}`, - "Content-Type": "application/json", - "Accept-Encoding": "application/json", - }, + ); + } else { + // case: secret exists in travis ci + // -> update/set secret + await standardRequest.patch( + `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`, + { + env_var: { + name: key, + value: secrets[key], } - ); - } else { - // case: secret exists in travis ci - // -> update/set secret - await standardRequest.patch( - `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`, - { - env_var: { - name: key, - value: secrets[key], - } + }, + { + headers: { + "Authorization": `token ${accessToken}`, + "Content-Type": "application/json", + "Accept-Encoding": "application/json", }, - { - headers: { - "Authorization": `token ${accessToken}`, - "Content-Type": "application/json", - "Accept-Encoding": "application/json", - }, - } - ); - } + } + ); } + } - for await (const key of Object.keys(getSecretsRes)) { - if (!(key in secrets)){ - // delete secret - await standardRequest.delete( - `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`, - { - headers: { - "Authorization": `token ${accessToken}`, - "Content-Type": "application/json", - "Accept-Encoding": "application/json", - }, - } - ); - } + for await (const key of Object.keys(getSecretsRes)) { + if (!(key in secrets)){ + // delete secret + await standardRequest.delete( + `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`, + { + headers: { + "Authorization": `token ${accessToken}`, + "Content-Type": "application/json", + "Accept-Encoding": "application/json", + }, + } + ); } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to GitLab"); } } @@ -1552,57 +1465,73 @@ const syncSecretsGitLab = async ({ secrets: any; accessToken: string; }) => { - try { - interface GitLabSecret { - key: string; - value: string; - environment_scope: string; - } + interface GitLabSecret { + key: string; + value: string; + environment_scope: string; + } - const getAllEnvVariables = async (integrationAppId: string, accessToken: string) => { - const gitLabApiUrl = `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integrationAppId}/variables`; - const headers = { - "Authorization": `Bearer ${accessToken}`, - "Accept-Encoding": "application/json", - }; - - let allEnvVariables: GitLabSecret[] = []; - let url: string | null = `${gitLabApiUrl}?per_page=100`; - - while (url) { - const response: any = await standardRequest.get(url, { headers }); - allEnvVariables = [...allEnvVariables, ...response.data]; - - const linkHeader = response.headers.link; - const nextLink = linkHeader?.split(',').find((part: string) => part.includes('rel="next"')); - - if (nextLink) { - url = nextLink.trim().split(';')[0].slice(1, -1); - } else { - url = null; - } - } - - return allEnvVariables; + const getAllEnvVariables = async (integrationAppId: string, accessToken: string) => { + const gitLabApiUrl = `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integrationAppId}/variables`; + const headers = { + "Authorization": `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", }; + + let allEnvVariables: GitLabSecret[] = []; + let url: string | null = `${gitLabApiUrl}?per_page=100`; + + while (url) { + const response: any = await standardRequest.get(url, { headers }); + allEnvVariables = [...allEnvVariables, ...response.data]; + + const linkHeader = response.headers.link; + const nextLink = linkHeader?.split(',').find((part: string) => part.includes('rel="next"')); + + if (nextLink) { + url = nextLink.trim().split(';')[0].slice(1, -1); + } else { + url = null; + } + } + + return allEnvVariables; + }; - const allEnvVariables = await getAllEnvVariables(integration?.appId, accessToken); - const getSecretsRes: GitLabSecret[] = allEnvVariables.filter((secret: GitLabSecret) => - secret.environment_scope === integration.targetEnvironment - ); + const allEnvVariables = await getAllEnvVariables(integration?.appId, accessToken); + const getSecretsRes: GitLabSecret[] = allEnvVariables.filter((secret: GitLabSecret) => + secret.environment_scope === integration.targetEnvironment + ); - for await (const key of Object.keys(secrets)) { - const existingSecret = getSecretsRes.find((s: any) => s.key == key); - if (!existingSecret) { - await standardRequest.post( - `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`, + for await (const key of Object.keys(secrets)) { + const existingSecret = getSecretsRes.find((s: any) => s.key == key); + if (!existingSecret) { + await standardRequest.post( + `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`, + { + key: key, + value: secrets[key], + protected: false, + masked: false, + raw: false, + environment_scope: integration.targetEnvironment + }, + { + headers: { + "Authorization": `Bearer ${accessToken}`, + "Content-Type": "application/json", + "Accept-Encoding": "application/json", + }, + } + ) + } else { + // update secret + if (secrets[key] !== existingSecret.value) { + await standardRequest.put( + `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`, { - key: key, - value: secrets[key], - protected: false, - masked: false, - raw: false, - environment_scope: integration.targetEnvironment + ...existingSecret, + value: secrets[existingSecret.key] }, { headers: { @@ -1611,46 +1540,23 @@ const syncSecretsGitLab = async ({ "Accept-Encoding": "application/json", }, } - ) - } else { - // update secret - if (secrets[key] !== existingSecret.value) { - await standardRequest.put( - `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`, - { - ...existingSecret, - value: secrets[existingSecret.key] - }, - { - headers: { - "Authorization": `Bearer ${accessToken}`, - "Content-Type": "application/json", - "Accept-Encoding": "application/json", - }, - } - ); - } - } - } - - // delete secrets - for await (const sec of getSecretsRes) { - if (!(sec.key in secrets)) { - await standardRequest.delete( - `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`, - { - headers: { - "Authorization": `Bearer ${accessToken}`, - }, - } ); } } + } - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error("Failed to sync secrets to GitLab"); + // delete secrets + for await (const sec of getSecretsRes) { + if (!(sec.key in secrets)) { + await standardRequest.delete( + `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`, + { + headers: { + "Authorization": `Bearer ${accessToken}`, + }, + } + ); + } } } @@ -1671,61 +1577,55 @@ const syncSecretsSupabase = async ({ secrets: any; accessToken: string; }) => { - try { - const { data: getSecretsRes } = await standardRequest.get( - `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - } - } - ); - - // convert the secrets to [{}] format - const modifiedFormatForSecretInjection = Object.keys(secrets).map( - (key) => { - return { - name: key, - value: secrets[key] - }; - } - ); - - await standardRequest.post( - `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, - modifiedFormatForSecretInjection, - { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Accept-Encoding': 'application/json' - } - } - ); - - const secretsToDelete: any = []; - getSecretsRes?.forEach((secretObj: any) => { - if (!(secretObj.name in secrets)) { - secretsToDelete.push(secretObj.name); - } - }); - - await standardRequest.delete( - `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, - { - headers: { + const { data: getSecretsRes } = await standardRequest.get( + `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, + { + headers: { Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', 'Accept-Encoding': 'application/json' - }, - data: secretsToDelete } - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to sync secrets to Supabase'); - } + } + ); + + // convert the secrets to [{}] format + const modifiedFormatForSecretInjection = Object.keys(secrets).map( + (key) => { + return { + name: key, + value: secrets[key] + }; + } + ); + + await standardRequest.post( + `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, + modifiedFormatForSecretInjection, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + } + } + ); + + const secretsToDelete: any = []; + getSecretsRes?.forEach((secretObj: any) => { + if (!(secretObj.name in secrets)) { + secretsToDelete.push(secretObj.name); + } + }); + + await standardRequest.delete( + `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'Accept-Encoding': 'application/json' + }, + data: secretsToDelete + } + ); };