Fix merge conflicts

This commit is contained in:
Tuan Dang
2022-12-21 11:36:47 -05:00
134 changed files with 5155 additions and 1488 deletions
+107
View File
@@ -0,0 +1,107 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Bot, BotKey } from '../models';
import { createBot } from '../helpers/bot';
interface BotKey {
encryptedKey: string;
nonce: string;
}
/**
* Return bot for workspace with id [workspaceId]. If a workspace bot doesn't exist,
* then create and return a new bot.
* @param req
* @param res
* @returns
*/
export const getBotByWorkspaceId = async (req: Request, res: Response) => {
let bot;
try {
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
});
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get bot for workspace'
});
}
return res.status(200).send({
bot
});
};
/**
* Return bot with id [req.bot._id] with active state set to [isActive].
* @param req
* @param res
* @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
});
}
bot = await Bot.findOneAndUpdate({
_id: req.bot._id
}, {
isActive
}, {
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'
});
}
return res.status(200).send({
bot
});
};
+2
View File
@@ -1,4 +1,5 @@
import * as authController from './authController';
import * as botController from './botController';
import * as integrationAuthController from './integrationAuthController';
import * as integrationController from './integrationController';
import * as keyController from './keyController';
@@ -17,6 +18,7 @@ import * as logController from './logController';
export {
authController,
botController,
integrationAuthController,
integrationController,
keyController,
@@ -3,69 +3,45 @@ import * as Sentry from '@sentry/node';
import axios from 'axios';
import { readFileSync } from 'fs';
import { IntegrationAuth, Integration } from '../models';
import { processOAuthTokenRes } from '../helpers/integrationAuth';
import { INTEGRATION_SET, ENV_DEV } from '../variables';
import { OAUTH_CLIENT_SECRET_HEROKU, OAUTH_TOKEN_URL_HEROKU } from '../config';
import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../variables';
import { IntegrationService } from '../services';
import { getApps, revokeAccess } from '../integrations';
export const getIntegrationOptions = async (
req: Request,
res: Response
) => {
return res.status(200).send({
integrationOptions: INTEGRATION_OPTIONS
});
}
/**
* Perform OAuth2 code-token exchange as part of integration [integration] for workspace with id [workspaceId]
* Note: integration [integration] must be set up compatible/designed for OAuth2
* @param req
* @param res
* @returns
*/
export const integrationAuthOauthExchange = async (
export const oAuthExchange = async (
req: Request,
res: Response
) => {
try {
let clientSecret;
const { workspaceId, code, integration } = req.body;
if (!INTEGRATION_SET.has(integration))
throw new Error('Failed to validate integration');
// use correct client secret
switch (integration) {
case 'heroku':
clientSecret = OAUTH_CLIENT_SECRET_HEROKU;
}
// TODO: unfinished - make compatible with other integration types
const res = await axios.post(
OAUTH_TOKEN_URL_HEROKU!,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_secret: clientSecret
} as any)
);
const integrationAuth = await processOAuthTokenRes({
await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
res
code
});
// create or replace integration
const integrationObj = await Integration.findOneAndUpdate(
{ workspace: workspaceId, integration },
{
workspace: workspaceId,
environment: ENV_DEV,
isActive: false,
app: null,
integration,
integrationAuth: integrationAuth._id
},
{ upsert: true, new: true }
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get OAuth2 token'
message: 'Failed to get OAuth2 code-token exchange'
});
}
@@ -75,26 +51,25 @@ export const integrationAuthOauthExchange = async (
};
/**
* Return list of applications allowed for integration with id [integrationAuthId]
* Return list of applications allowed for integration with integration authorization id [integrationAuthId]
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
// TODO: unfinished - make compatible with other integration types
let apps;
try {
const res = await axios.get('https://api.heroku.com/apps', {
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: 'Bearer ' + req.accessToken
}
apps = await getApps({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
});
apps = res.data.map((a: any) => ({
name: a.name
}));
} catch (err) {}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get integration authorization applications'
});
}
return res.status(200).send({
apps
@@ -108,46 +83,22 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
* @returns
*/
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
// TODO: unfinished - disable application via Heroku API and make compatible with other integration types
try {
const { integrationAuthId } = req.params;
// TODO: disable application via Heroku API; figure out what authorization id is
const integrations = JSON.parse(
readFileSync('./src/json/integrations.json').toString()
);
let authorizationId;
switch (req.integrationAuth.integration) {
case 'heroku':
authorizationId = integrations.heroku.clientId;
}
// not sure what authorizationId is?
// // revoke authorization
// const res2 = await axios.delete(
// `https://api.heroku.com/oauth/authorizations/${authorizationId}`,
// {
// headers: {
// 'Accept': 'application/vnd.heroku+json; version=3',
// 'Authorization': 'Bearer ' + req.accessToken
// }
// }
// );
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
_id: integrationAuthId
await revokeAccess({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
});
if (deletedIntegrationAuth) {
await Integration.deleteMany({
integrationAuth: deletedIntegrationAuth._id
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete integration authorization'
});
}
};
return res.status(200).send({
message: 'Successfully deleted integration authorization'
});
}
@@ -1,11 +1,9 @@
import { Request, Response } from 'express';
import { readFileSync } from 'fs';
import * as Sentry from '@sentry/node';
import axios from 'axios';
import { Integration } from '../models';
import { decryptAsymmetric } from '../utils/crypto';
import { decryptSecrets } from '../helpers/secret';
import { PRIVATE_KEY } from '../config';
import { Integration, Bot, BotKey } from '../models';
import { EventService } from '../services';
import { eventPushSecrets } from '../events';
interface Key {
encryptedKey: string;
@@ -24,104 +22,58 @@ interface PushSecret {
type: 'shared' | 'personal';
}
/**
* Return list of all available integrations on Infisical
* @param req
* @param res
* @returns
*/
export const getIntegrations = async (req: Request, res: Response) => {
let integrations;
try {
integrations = JSON.parse(
readFileSync('./src/json/integrations.json').toString()
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get integrations'
});
}
return res.status(200).send({
integrations
});
};
/**
* Sync secrets [secrets] to integration with id [integrationId]
* @param req
* @param res
* @returns
*/
export const syncIntegration = async (req: Request, res: Response) => {
// TODO: unfinished - make more versatile to accomodate for other integrations
try {
const { key, secrets }: { key: Key; secrets: PushSecret[] } = req.body;
const symmetricKey = decryptAsymmetric({
ciphertext: key.encryptedKey,
nonce: key.nonce,
publicKey: req.user.publicKey,
privateKey: PRIVATE_KEY
});
// decrypt secrets with symmetric key
const content = decryptSecrets({
secrets,
key: symmetricKey,
format: 'object'
});
// TODO: make integration work for other integrations as well
const res = await axios.patch(
`https://api.heroku.com/apps/${req.integration.app}/config-vars`,
content,
{
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: 'Bearer ' + req.accessToken
}
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to sync secrets with integration'
});
}
return res.status(200).send({
message: 'Successfully synced secrets with integration'
});
};
/**
* Change environment or name of integration with id [integrationId]
* @param req
* @param res
* @returns
*/
export const modifyIntegration = async (req: Request, res: Response) => {
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 { update } = req.body;
const {
app,
environment,
isActive,
target, // vercel-specific integration param
context, // netlify-specific integration param
siteId // netlify-specific integration param
} = req.body;
integration = await Integration.findOneAndUpdate(
{
_id: req.integration._id
},
update,
{
environment,
isActive,
app,
target,
context,
siteId
},
{
new: true
}
);
if (integration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: integration.workspace.toString()
})
});
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to modify integration'
message: 'Failed to update integration'
});
}
@@ -131,7 +83,8 @@ export const modifyIntegration = async (req: Request, res: Response) => {
};
/**
* Delete integration with id [integrationId]
* Delete integration with id [integrationId] and deactivate bot if there are
* no integrations left
* @param req
* @param res
* @returns
@@ -144,6 +97,29 @@ export const deleteIntegration = async (req: Request, res: Response) => {
deletedIntegration = await Integration.findOneAndDelete({
_id: integrationId
});
if (!deletedIntegration) throw new Error('Failed to find integration');
const integrations = await Integration.find({
workspace: deletedIntegration.workspace
});
if (integrations.length === 0) {
// case: no integrations left, deactivate bot
const bot = await Bot.findOneAndUpdate({
workspace: deletedIntegration.workspace
}, {
isActive: false
}, {
new: true
});
if (bot) {
await BotKey.deleteOne({
bot: bot._id
});
}
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
-10
View File
@@ -17,16 +17,6 @@ export const uploadKey = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const { key } = req.body;
// validate membership of sender
const senderMembership = await findMembership({
user: req.user._id,
workspace: workspaceId
});
if (!senderMembership) {
throw new Error('Failed sender membership validation for workspace');
}
// validate membership of receiver
const receiverMembership = await findMembership({
user: key.userId,
+12 -3
View File
@@ -7,8 +7,9 @@ import {
reformatPullSecrets
} from '../helpers/secret';
import { pushKeys } from '../helpers/key';
import { eventPushSecrets } from '../events';
import { EventService } from '../services';
import { ENV_SET } from '../variables';
import { postHogClient } from '../services';
interface PushSecret {
@@ -60,7 +61,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
workspaceId,
keys
});
if (postHogClient) {
postHogClient.capture({
event: 'secrets pushed',
@@ -74,6 +76,13 @@ export const pushSecrets = async (req: Request, res: Response) => {
});
}
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId
})
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
@@ -192,7 +201,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
};
if (postHogClient) {
// capture secrets pushed event in production
// capture secrets pulled event in production
postHogClient.capture({
distinctId: req.serviceToken.user.email,
event: 'secrets pulled',