Skip to content

Commit

Permalink
Merge pull request #423 from Infisical/check-integrations
Browse files Browse the repository at this point in the history
Patch create integration page on no integration projects and add support for groups in GitLab integration
  • Loading branch information
dangtony98 authored Mar 10, 2023
2 parents ef4a316 + 78cb18a commit cdbc6f5
Show file tree
Hide file tree
Showing 27 changed files with 799 additions and 238 deletions.
68 changes: 50 additions & 18 deletions backend/src/controllers/v1/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Integration,
IntegrationAuth,
Bot
} from '../../models';
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
import { IntegrationService } from '../../services';
import { getApps, revokeAccess } from '../../integrations';
import {
getApps,
getTeams,
revokeAccess
} from '../../integrations';

/***
* Return integration authorization with id [integrationAuthId]
Expand Down Expand Up @@ -154,25 +157,54 @@ export const saveIntegrationAccessToken = async (
* @returns
*/
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
let apps;
try {
apps = await getApps({
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 get integration authorization applications",
});
}
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",
});
}

return res.status(200).send({
apps,
});
return res.status(200).send({
apps
});
};

/**
* Return list of teams allowed for integration with integration authorization id [integrationAuthId]
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
let teams;
try {
teams = await getTeams({
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 get integration authorization teams"
});
}

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

/**
* Delete integration authorization with id [integrationAuthId]
* @param req
Expand Down
22 changes: 10 additions & 12 deletions backend/src/controllers/v1/integrationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Integration,
Workspace,
Bot,
BotKey
Integration
} from '../../models';
import { EventService } from '../../services';
import { eventPushSecrets } from '../../events';
Expand All @@ -18,6 +15,7 @@ import { eventPushSecrets } from '../../events';
*/
export const createIntegration = async (req: Request, res: Response) => {
let integration;

try {
const {
integrationAuthId,
Expand All @@ -34,19 +32,19 @@ export const createIntegration = async (req: Request, res: Response) => {
// 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,
integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment,
isActive,
app,
appId,
targetEnvironment,
owner,
path,
region,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();

if (integration) {
// trigger event - push secrets
Expand Down
2 changes: 1 addition & 1 deletion backend/src/helpers/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
// access token is expired
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
accessToken = await exchangeRefresh({
integration: integrationAuth.integration,
integrationAuth,
refreshToken
});
}
Expand Down
138 changes: 103 additions & 35 deletions backend/src/integrations/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,30 @@ import {
INTEGRATION_TRAVISCI_API_URL,
} from "../variables";

interface App {
name: string;
appId?: string;
owner?: string;
}

/**
* Return list of names of apps for integration named [integration]
* @param {Object} obj
* @param {String} obj.integration - name of integration
* @param {String} obj.accessToken - access token for integration
* @param {String} obj.teamId - (optional) id of team for getting integration apps (used for integrations like GitLab)
* @returns {Object[]} apps - names of integration apps
* @returns {String} apps.name - name of integration app
*/
const getApps = async ({
integrationAuth,
accessToken,
teamId
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
teamId?: string;
}) => {
interface App {
name: string;
appId?: string;
owner?: string;
}

let apps: App[] = [];
try {
Expand Down Expand Up @@ -82,6 +86,7 @@ const getApps = async ({
case INTEGRATION_GITLAB:
apps = await getAppsGitlab({
accessToken,
teamId
});
break;
case INTEGRATION_RENDER:
Expand Down Expand Up @@ -434,44 +439,107 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
* @returns {Object[]} apps - names of GitLab sites
* @returns {String} apps.name - name of GitLab site
*/
const getAppsGitlab = async ({ accessToken }: {accessToken: string}) => {
let apps;
const getAppsGitlab = async ({
accessToken,
teamId
}: {
accessToken: string;
teamId?: string;
}) => {
const apps: App[] = [];

let page = 1;
const perPage = 10;
let hasMorePages = true;
try {
const { id } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
).data;

const res = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
if (teamId) {
// case: fetch projects for group with id [teamId] in GitLab

while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});

const { data } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
{
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
);

data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id
});
});

if (data.length < perPage) {
hasMorePages = false;
}
)
).data;

page++;
}
} else {
// case: fetch projects for individual in GitLab

const { id } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
).data;

while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});

apps = res?.map((a: any) => {
return {
name: a?.name,
appId: `${a?.id}`,
const { data } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
{
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
);

data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id
});
});

if (data.length < perPage) {
hasMorePages = false;
}

page++;
}
});
}catch (err) {
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get GitLab repos");
throw new Error("Failed to get GitLab projects");
}

return apps;
Expand Down
17 changes: 11 additions & 6 deletions backend/src/integrations/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL
} from '../variables';
import {
SITE_URL,
Expand Down Expand Up @@ -73,7 +73,7 @@ interface ExchangeCodeGithubResponse {
interface ExchangeCodeGitlabResponse {
access_token: string;
token_type: string;
expires_in: string;
expires_in: number;
refresh_token: string;
scope: string;
created_at: number;
Expand Down Expand Up @@ -168,7 +168,7 @@ const exchangeCodeAzure = async ({
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err: any) {
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Azure');
Expand Down Expand Up @@ -370,6 +370,7 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
*/
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
let res: ExchangeCodeGitlabResponse;
const accessExpiresAt = new Date();

try {
res = (
Expand All @@ -389,16 +390,20 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
}
)
).data;
}catch (err) {

accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
}

return {
accessToken: res.access_token,
refreshToken: null,
accessExpiresAt: null
refreshToken: res.refresh_token,
accessExpiresAt
};
}

Expand Down
Loading

0 comments on commit cdbc6f5

Please sign in to comment.