mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 11:29:42 +00:00
Complete v1 loop for bot-based integrations
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { IBot } from '../models';
|
||||
import { ACTION_PUSH_TO_HEROKU } from '../variables';
|
||||
import { actionPushToHeroku } from '../actions';
|
||||
|
||||
interface Event {
|
||||
name: string;
|
||||
workspaceId: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to handle actions
|
||||
*/
|
||||
class ActionService {
|
||||
/**
|
||||
* @param {Object} obj
|
||||
* @param {String} action - name of action to trigger
|
||||
* @param {Event} event
|
||||
* @param {String} obj.event.name - name of event
|
||||
* @param {String} obj.event.workspaceId - id of workspace that event is part of
|
||||
* @param {Object} obj.event.payload - payload of event (depends on event)
|
||||
* @param bot
|
||||
* @returns
|
||||
*/
|
||||
static async handleAction({
|
||||
action,
|
||||
event,
|
||||
bot
|
||||
}: {
|
||||
action: string;
|
||||
event: Event;
|
||||
bot: IBot;
|
||||
}) {
|
||||
try {
|
||||
switch (action) {
|
||||
case ACTION_PUSH_TO_HEROKU:
|
||||
actionPushToHeroku({
|
||||
event,
|
||||
bot
|
||||
});
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('EventService err', err);
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ActionService;
|
||||
@@ -1,19 +1,8 @@
|
||||
import {
|
||||
Bot,
|
||||
IBot,
|
||||
BotKey,
|
||||
IBotKey,
|
||||
IUser,
|
||||
Secret
|
||||
} from '../models';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
decryptAsymmetric,
|
||||
decryptSymmetric
|
||||
} from '../utils/crypto';
|
||||
import {
|
||||
ENCRYPTION_KEY
|
||||
} from '../config';
|
||||
getSecretsHelper,
|
||||
encryptSymmetricHelper,
|
||||
decryptSymmetricHelper
|
||||
} from '../helpers/bot';
|
||||
|
||||
/**
|
||||
* Class to handle bot actions
|
||||
@@ -21,87 +10,72 @@ import {
|
||||
class BotService {
|
||||
|
||||
/**
|
||||
* Return decrypted secrets using bot
|
||||
* Return decrypted secrets for workspace with id [workspaceId] and
|
||||
* environment [environmen] shared to bot.
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace of secrets
|
||||
* @param {String} obj.environment - environment for secrets
|
||||
* @returns {Object} secretObj - object where keys are secret keys and values are secret values
|
||||
*/
|
||||
static async decryptSecrets({
|
||||
static async getSecrets({
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
}) {
|
||||
|
||||
let content: any = {};
|
||||
let bot;
|
||||
let botKey;
|
||||
try {
|
||||
|
||||
// find bot
|
||||
bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
if (!bot) throw new Error('Failed to find bot');
|
||||
|
||||
// find bot key
|
||||
botKey = await BotKey.findOne({
|
||||
workspace: workspaceId
|
||||
}).populate<{ sender: IUser }>('sender');
|
||||
|
||||
if (!botKey) throw new Error('Failed to find bot key');
|
||||
|
||||
// decrypt bot private key
|
||||
const privateKey = decryptSymmetric({
|
||||
ciphertext: bot.encryptedPrivateKey,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
key: ENCRYPTION_KEY
|
||||
});
|
||||
|
||||
// decrypt workspace key
|
||||
const key = decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
nonce: botKey.nonce,
|
||||
publicKey: botKey.sender.publicKey as string,
|
||||
privateKey
|
||||
});
|
||||
|
||||
// decrypt secrets
|
||||
const secrets = await Secret.find({
|
||||
workspace: workspaceId,
|
||||
environment
|
||||
});
|
||||
|
||||
secrets.forEach(secret => {
|
||||
// KEY, VALUE
|
||||
const secretKey = decryptSymmetric({
|
||||
ciphertext: secret.secretKeyCiphertext,
|
||||
iv: secret.secretKeyIV,
|
||||
tag: secret.secretKeyTag,
|
||||
key
|
||||
});
|
||||
|
||||
const secretValue = decryptSymmetric({
|
||||
ciphertext: secret.secretValueCiphertext,
|
||||
iv: secret.secretValueIV,
|
||||
tag: secret.secretValueTag,
|
||||
key
|
||||
});
|
||||
|
||||
content[secretKey] = secretValue;
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('BotService');
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
|
||||
return content;
|
||||
return await getSecretsHelper({
|
||||
workspaceId,
|
||||
environment
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return symmetrically encrypted [plaintext] using the
|
||||
* bot's copy of the workspace key for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.plaintext - plaintext to encrypt
|
||||
*/
|
||||
static async encryptSymmetric({
|
||||
workspaceId,
|
||||
plaintext
|
||||
}: {
|
||||
workspaceId: string;
|
||||
plaintext: string;
|
||||
}) {
|
||||
return await encryptSymmetricHelper({
|
||||
workspaceId,
|
||||
plaintext
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return symmetrically decrypted [ciphertext] using the
|
||||
* bot's copy of the workspace key for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||
* @param {String} obj.iv - iv
|
||||
* @param {String} obj.tag - tag
|
||||
*/
|
||||
static async decryptSymmetric({
|
||||
workspaceId,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
}: {
|
||||
workspaceId: string;
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
}) {
|
||||
return await decryptSymmetricHelper({
|
||||
workspaceId,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Bot, IBot, BotSequence } from '../models';
|
||||
import { Bot, IBot } from '../models';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import ActionService from './ActionService';
|
||||
import { handleEventHelper } from '../helpers/event';
|
||||
|
||||
interface Event {
|
||||
name: string;
|
||||
@@ -9,12 +9,11 @@ interface Event {
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to handle events. TODO: elaborate DOCSTRING.
|
||||
* Class to handle events.
|
||||
*/
|
||||
class EventService {
|
||||
/**
|
||||
* Check if any bot sequences exist for event and forward
|
||||
* bot sequence details to ActionService for execution
|
||||
* Handle event [event]
|
||||
* @param {Object} obj
|
||||
* @param {Event} obj.event - an event
|
||||
* @param {String} obj.event.name - name of event
|
||||
@@ -22,53 +21,9 @@ class EventService {
|
||||
* @param {Object} obj.event.payload - payload of event (depends on event)
|
||||
*/
|
||||
static async handleEvent({ event }: { event: Event }): Promise<void> {
|
||||
let botSequences;
|
||||
let bot: IBot | null;
|
||||
try {
|
||||
|
||||
console.log('EventService');
|
||||
const { workspaceId } = event;
|
||||
|
||||
bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
console.log('A', bot);
|
||||
// case: bot doesn't exist
|
||||
if (!bot) {
|
||||
return;
|
||||
}
|
||||
|
||||
botSequences = await BotSequence.find({
|
||||
bot: bot._id,
|
||||
event: event.name
|
||||
});
|
||||
|
||||
console.log('B', botSequences);
|
||||
|
||||
// case: bot sequences don't exist
|
||||
if (botSequences.length === 0) return;
|
||||
|
||||
console.log('C');
|
||||
|
||||
return;
|
||||
|
||||
// // execute event sequences
|
||||
// botSequences.forEach(botSequence => {
|
||||
// // sequence.actions
|
||||
// ActionService.handleAction({
|
||||
// action: botSequence.action,
|
||||
// event,
|
||||
// bot: bot as IBot
|
||||
// });
|
||||
// });
|
||||
|
||||
} catch (err) {
|
||||
console.error('EventService err', err);
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
await handleEventHelper({
|
||||
event
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
Integration,
|
||||
Bot,
|
||||
BotSequence
|
||||
Integration
|
||||
} from '../models';
|
||||
import {
|
||||
handleOAuthExchangeHelper,
|
||||
syncIntegrationsHelper,
|
||||
getIntegrationAuthRefreshHelper,
|
||||
getIntegrationAuthAccessHelper,
|
||||
setIntegrationAuthRefreshHelper,
|
||||
setIntegrationAuthAccessHelper,
|
||||
} from '../helpers/integration';
|
||||
import { exchangeCode } from '../integrations';
|
||||
import { processOAuthTokenRes2 } from '../helpers/integrationAuth';
|
||||
import {
|
||||
ENV_DEV,
|
||||
EVENT_PUSH_SECRETS
|
||||
} from '../variables';
|
||||
|
||||
// should sync stuff be here too? Probably.
|
||||
// TODO: move bot functions to IntegrationService.
|
||||
|
||||
/**
|
||||
* Class to handle integrations
|
||||
*/
|
||||
@@ -36,57 +44,101 @@ class IntegrationService {
|
||||
integration: string;
|
||||
code: string;
|
||||
}) {
|
||||
console.log('IntegrationService > handleO')
|
||||
let action;
|
||||
try {
|
||||
|
||||
const bot = await Bot.find({
|
||||
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
|
||||
let res = await exchangeCode({
|
||||
integration,
|
||||
code
|
||||
});
|
||||
|
||||
const integrationAuth = await processOAuthTokenRes2({
|
||||
workspaceId,
|
||||
integration,
|
||||
accessToken: res.accessToken,
|
||||
accessExpiresAt: res.accessExpiresAt,
|
||||
refreshToken: res.refreshToken
|
||||
});
|
||||
await handleOAuthExchangeHelper({
|
||||
workspaceId,
|
||||
integration,
|
||||
code
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync/push environment variables in workspace with id [workspaceId] to
|
||||
* all associated integrations
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.workspaceId - id of workspace
|
||||
*/
|
||||
static async syncIntegrations({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}) {
|
||||
return await syncIntegrationsHelper({
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return decrypted refresh token for integration auth
|
||||
* with id [integrationAuthId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} refreshToken - decrypted refresh token
|
||||
*/
|
||||
static async getIntegrationAuthRefresh({ integrationAuthId }: { integrationAuthId: string}) {
|
||||
return await getIntegrationAuthRefreshHelper({
|
||||
integrationAuthId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return decrypted access token for integration auth
|
||||
* with id [integrationAuthId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} accessToken - decrypted access token
|
||||
*/
|
||||
static async getIntegrationAuthAccess({ integrationAuthId }: { integrationAuthId: string}) {
|
||||
return await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId
|
||||
});
|
||||
}
|
||||
|
||||
await Integration.findOneAndUpdate(
|
||||
{ workspace: workspaceId, integration },
|
||||
{
|
||||
workspace: workspaceId,
|
||||
environment: ENV_DEV,
|
||||
isActive: false,
|
||||
app: null,
|
||||
integration,
|
||||
integrationAuth: integrationAuth._id
|
||||
},
|
||||
{ upsert: true, new: true }
|
||||
);
|
||||
|
||||
// add bot sequence
|
||||
await BotSequence.findOneAndUpdate({
|
||||
bot: bot._id,
|
||||
name: integration + 'sequence',
|
||||
event: EVENT_PUSH_SECRETS,
|
||||
action
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('IntegrationService error', err);
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to handle OAuth2 code-token exchange')
|
||||
}
|
||||
/**
|
||||
* Encrypt refresh token [refreshToken] using the bot's copy
|
||||
* of the workspace key for workspace belonging to integration auth
|
||||
* with id [integrationAuthId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} obj.refreshToken - refresh token
|
||||
* @returns {IntegrationAuth} integrationAuth - updated integration auth
|
||||
*/
|
||||
static async setIntegrationAuthRefresh({
|
||||
integrationAuthId,
|
||||
refreshToken
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
refreshToken: string;
|
||||
}) {
|
||||
return await setIntegrationAuthRefreshHelper({
|
||||
integrationAuthId,
|
||||
refreshToken
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt access token [accessToken] using the bot's copy
|
||||
* of the workspace key for workspace belonging to integration auth
|
||||
* with id [integrationAuthId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} obj.accessToken - access token
|
||||
* @param {String} obj.accessExpiresAt - expiration date of access token
|
||||
* @returns {IntegrationAuth} - updated integration auth
|
||||
*/
|
||||
static async setIntegrationAuthAccess({
|
||||
integrationAuthId,
|
||||
accessToken,
|
||||
accessExpiresAt
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
accessToken: string;
|
||||
accessExpiresAt: Date;
|
||||
}) {
|
||||
return await setIntegrationAuthAccessHelper({
|
||||
integrationAuthId,
|
||||
accessToken,
|
||||
accessExpiresAt
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import postHogClient from './PostHogClient';
|
||||
import BotService from './BotService';
|
||||
import EventService from './EventService';
|
||||
import ActionService from './ActionService';
|
||||
import IntegrationService from './IntegrationService';
|
||||
|
||||
export {
|
||||
postHogClient,
|
||||
BotService,
|
||||
EventService,
|
||||
ActionService,
|
||||
IntegrationService
|
||||
}
|
||||
Reference in New Issue
Block a user