Begin developing bot, event, and integration pipeline

This commit is contained in:
Tuan Dang
2022-12-06 00:23:16 -05:00
parent c5a422fe64
commit 46fe724012
48 changed files with 1402 additions and 176 deletions
+49
View File
@@ -0,0 +1,49 @@
import * as Sentry from '@sentry/node';
import {
Bot
} from '../models';
import { generateKeyPair, encryptSymmetric } from '../utils/crypto';
import { ENCRYPTION_KEY } from '../config';
/**
* Create an inactive bot with name [name] for workspace with id [workspaceId]
* @param {Object} obj
* @param {String} obj.name - name of bot
* @param {String} obj.workspaceId - id of workspace that bot belongs to
*/
const createBot = async ({
name,
workspaceId,
}: {
name: string;
workspaceId: string;
}) => {
let bot;
try {
const { publicKey, privateKey } = generateKeyPair();
const { ciphertext, iv, tag } = encryptSymmetric({
plaintext: privateKey,
key: ENCRYPTION_KEY
});
bot = await new Bot({
name,
workspace: workspaceId,
isActive: false,
publicKey,
encryptedPrivateKey: ciphertext,
iv,
tag
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create bot');
}
return bot;
}
export {
createBot
}
+77 -1
View File
@@ -9,6 +9,81 @@ import {
OAUTH_TOKEN_URL_HEROKU
} from '../config';
/**
* Encrypt access and refresh tokens, compute new access token expiration times [accessExpiresAt],
* and upsert them into the DB for workspace with id [workspaceId] and integration [integration].
* @param {Object} obj
* @param {String} obj.workspaceId - id of workspace
* @param {String} obj.integration - name of integration
* @param {String} obj.accessToken - access token for integration
* @param {Date} obj.accessExpiresAt - date of expiration for access token
* @param {String} obj.refreshToken - refresh token for integration
*/
const processOAuthTokenRes2 = async ({
workspaceId,
integration,
accessToken,
accessExpiresAt,
refreshToken,
}: {
workspaceId: string;
integration: string;
accessToken: string;
accessExpiresAt: Date;
refreshToken: string;
}) => {
let integrationAuth;
try {
// encrypt refresh + access tokens
const {
ciphertext: refreshCiphertext,
iv: refreshIV,
tag: refreshTag
} = encryptSymmetric({
plaintext: refreshToken,
key: ENCRYPTION_KEY
});
const {
ciphertext: accessCiphertext,
iv: accessIV,
tag: accessTag
} = encryptSymmetric({
plaintext: accessToken,
key: ENCRYPTION_KEY
});
// create or replace integration authorization with encrypted tokens
// and access token expiration date
integrationAuth = await IntegrationAuth.findOneAndUpdate(
{ workspace: workspaceId, integration },
{
workspace: workspaceId,
integration,
refreshCiphertext,
refreshIV,
refreshTag,
accessCiphertext,
accessIV,
accessTag,
accessExpiresAt
},
{ upsert: true, new: true }
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(
'Failed to process OAuth2 authorization server token response'
);
}
return integrationAuth;
}
// TODO: deprecate
/**
* Process token exchange and refresh responses from respective OAuth2 authorization servers by
* encrypting access and refresh tokens, computing new access token expiration times [accessExpiresAt],
@@ -82,6 +157,7 @@ const processOAuthTokenRes = async ({
return integrationAuth;
};
// TODO: deprecate
/**
* Return access token for integration either by decrypting a non-expired access token [accessCiphertext] on
* the integration authorization document or by requesting a new one by decrypting and exchanging the
@@ -171,4 +247,4 @@ const getOAuthAccessToken = async ({
return accessToken;
};
export { processOAuthTokenRes, getOAuthAccessToken };
export { processOAuthTokenRes, processOAuthTokenRes2, getOAuthAccessToken };
+51 -1
View File
@@ -1,6 +1,51 @@
import * as Sentry from '@sentry/node';
import { Membership, Key } from '../models';
/**
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
* and has at least one of the roles in [acceptedRoles] and statuses in [acceptedStatuses]
* @param {Object} obj
* @param {String} obj.userId - id of user to validate
* @param {String} obj.workspaceId - id of workspace
*/
const validateMembership = async ({
userId,
workspaceId,
acceptedRoles,
acceptedStatuses
}: {
userId: string;
workspaceId: string;
acceptedRoles: string[];
acceptedStatuses: string[];
}) => {
let membership;
try {
membership = await Membership.findOne({
user: userId,
workspace: workspaceId
});
if (!membership) throw new Error('Failed to find membership');
if (!acceptedRoles.includes(membership.role)) {
throw new Error('Failed to validate membership role');
}
if (!acceptedStatuses.includes(membership.status)) {
throw new Error('Failed to validate membership status');
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to validate membership');
}
return membership;
}
/**
* Return membership matching criteria specified in query [queryObj]
* @param {Object} queryObj - query object
@@ -97,4 +142,9 @@ const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
return deletedMembership;
};
export { addMemberships, findMembership, deleteMembership };
export {
validateMembership,
addMemberships,
findMembership,
deleteMembership
};
+1 -1
View File
@@ -33,7 +33,7 @@ const sendEmailVerification = async ({ email }: { email: string }) => {
// send mail
await sendMail({
template: 'emailVerification.handlebars',
subjectLine: 'Infisical workspace invitation',
subjectLine: 'Infisical confirmation code',
recipients: [email],
substitutions: {
code: token
+12
View File
@@ -1,13 +1,16 @@
import * as Sentry from '@sentry/node';
import {
Workspace,
Bot,
Membership,
Key,
Secret
} from '../models';
import { createBot } from '../helpers/bot';
/**
* Create a workspace with name [name] in organization with id [organizationId]
* and a bot for it.
* @param {String} name - name of workspace to create.
* @param {String} organizationId - id of organization to create workspace in
* @param {Object} workspace - new workspace
@@ -21,10 +24,16 @@ const createWorkspace = async ({
}) => {
let workspace;
try {
// create workspace
workspace = await new Workspace({
name,
organization: organizationId
}).save();
// const bot = await createBot({
// name: 'Infisical Bot',
// workspaceId: workspace._id.toString()
// });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@@ -43,6 +52,9 @@ const createWorkspace = async ({
const deleteWorkspace = async ({ id }: { id: string }) => {
try {
await Workspace.deleteOne({ _id: id });
await Bot.deleteOne({
workspace: id
});
await Membership.deleteMany({
workspace: id
});