Skip to content

Refactor integration pages into separate steps for authorization and integration creation. #310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 18 additions & 82 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 36 additions & 10 deletions backend/src/controllers/v1/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
import { IntegrationService } from '../../services';
import { getApps, revokeAccess } from '../../integrations';

/***
* 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'
});
}

return res.status(200).send({
integrationAuth
});
}

export const getIntegrationOptions = async (
req: Request,
res: Response
Expand All @@ -31,7 +56,6 @@ export const oAuthExchange = async (
) => {
try {
const { workspaceId, code, integration } = req.body;

if (!INTEGRATION_SET.has(integration))
throw new Error('Failed to validate integration');

Expand All @@ -40,14 +64,16 @@ export const oAuthExchange = async (
throw new Error("Failed to get environments")
}

const integrationDetails = await IntegrationService.handleOAuthExchange({
const integrationAuth = await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
code,
environment: environments[0].slug,
});

return res.status(200).send(integrationDetails);
return res.status(200).send({
integrationAuth
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
Expand Down Expand Up @@ -79,6 +105,13 @@ export const saveIntegrationAccessToken = async (
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');

integrationAuth = await IntegrationAuth.findOneAndUpdate({
workspace: new Types.ObjectId(workspaceId),
integration
Expand All @@ -89,13 +122,6 @@ export const saveIntegrationAccessToken = async (
new: true,
upsert: true
});

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');

// encrypt and save integration access token
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
Expand Down
37 changes: 30 additions & 7 deletions backend/src/controllers/v1/integrationController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Integration,
Expand All @@ -16,20 +17,42 @@ import { eventPushSecrets } from '../../events';
* @returns
*/
export const createIntegration = async (req: Request, res: Response) => {

// TODO: make this more versatile

let integration;
try {
const {
integrationAuthId,
app,
appId,
isActive,
sourceEnvironment,
targetEnvironment,
owner
} = req.body;

// TODO: validate [sourceEnvironment] and [targetEnvironment]

// initialize new integration after saving integration access token
integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
isActive: false,
app: null,
environment: req.integrationAuth.workspace?.environments[0].slug,
environment: sourceEnvironment,
isActive,
app,
appId,
targetEnvironment,
owner,
integration: req.integrationAuth.integration,
integrationAuth: req.integrationAuth._id
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();

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);
Expand Down
18 changes: 2 additions & 16 deletions backend/src/helpers/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface Update {
* @param {String} obj.workspaceId - id of workspace
* @param {String} obj.integration - name of integration
* @param {String} obj.code - code
* @returns {IntegrationAuth} integrationAuth - integration auth after OAuth2 code-token exchange
*/
const handleOAuthExchangeHelper = async ({
workspaceId,
Expand All @@ -42,9 +43,7 @@ const handleOAuthExchangeHelper = async ({
code: string;
environment: string;
}) => {
let action;
let integrationAuth;
let newIntegration;
try {
const bot = await Bot.findOne({
workspace: workspaceId,
Expand Down Expand Up @@ -99,26 +98,13 @@ const handleOAuthExchangeHelper = async ({
accessExpiresAt: res.accessExpiresAt
});
}

// initialize new integration after exchange
newIntegration = await new Integration({
workspace: workspaceId,
isActive: false,
app: null,
environment,
integration,
integrationAuth: integrationAuth._id
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to handle OAuth2 code-token exchange')
}

return ({
integrationAuth,
integration: newIntegration
});
return integrationAuth;
}
/**
* Sync/push environment variables in workspace with id [workspaceId] to
Expand Down
8 changes: 4 additions & 4 deletions backend/src/integrations/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const exchangeCodeAzure = async ({
scope: 'https://vault.azure.net/.default openid offline_access', // TODO: do we need all these permissions?
client_id: CLIENT_ID_AZURE,
client_secret: CLIENT_SECRET_AZURE,
redirect_uri: `${SITE_URL}/azure-key-vault`
redirect_uri: `${SITE_URL}/integrations/azure-key-vault/oauth2/callback`
} as any)
)).data;

Expand Down Expand Up @@ -227,7 +227,7 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
code: code,
client_id: CLIENT_ID_VERCEL,
client_secret: CLIENT_SECRET_VERCEL,
redirect_uri: `${SITE_URL}/vercel`
redirect_uri: `${SITE_URL}/integrations/vercel/oauth2/callback`
} as any)
)
).data;
Expand Down Expand Up @@ -267,7 +267,7 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
code: code,
client_id: CLIENT_ID_NETLIFY,
client_secret: CLIENT_SECRET_NETLIFY,
redirect_uri: `${SITE_URL}/netlify`
redirect_uri: `${SITE_URL}/integrations/netlify/oauth2/callback`
} as any)
)
).data;
Expand Down Expand Up @@ -319,7 +319,7 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
client_id: CLIENT_ID_GITHUB,
client_secret: CLIENT_SECRET_GITHUB,
code: code,
redirect_uri: `${SITE_URL}/github`
redirect_uri: `${SITE_URL}/integrations/github/oauth2/callback`
},
headers: {
Accept: 'application/json'
Expand Down
2 changes: 0 additions & 2 deletions backend/src/integrations/sync.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import { Octokit } from '@octokit/rest';
// import * as sodium from 'libsodium-wrappers';
import sodium from 'libsodium-wrappers';
// const sodium = require('libsodium-wrappers');
import { IIntegration, IIntegrationAuth } from '../models';
import {
INTEGRATION_AZURE_KEY_VAULT,
Expand Down
Loading