mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 15:26:20 +00:00
Finish support for GitLab groups integration
This commit is contained in:
@@ -2,7 +2,6 @@ import { Request, Response } from 'express';
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import {
|
import {
|
||||||
Integration,
|
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
Bot
|
Bot
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
@@ -161,7 +160,7 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
|||||||
let apps;
|
let apps;
|
||||||
try {
|
try {
|
||||||
const teamId = req.query.teamId as string;
|
const teamId = req.query.teamId as string;
|
||||||
|
|
||||||
apps = await getApps({
|
apps = await getApps({
|
||||||
integrationAuth: req.integrationAuth,
|
integrationAuth: req.integrationAuth,
|
||||||
accessToken: req.accessToken,
|
accessToken: req.accessToken,
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ import { Request, Response } from 'express';
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import {
|
import {
|
||||||
Integration,
|
Integration
|
||||||
Workspace,
|
|
||||||
Bot,
|
|
||||||
BotKey
|
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
@@ -18,6 +15,7 @@ import { eventPushSecrets } from '../../events';
|
|||||||
*/
|
*/
|
||||||
export const createIntegration = async (req: Request, res: Response) => {
|
export const createIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
let integration;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
@@ -34,19 +32,19 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
||||||
|
|
||||||
// initialize new integration after saving integration access token
|
// initialize new integration after saving integration access token
|
||||||
integration = await new Integration({
|
integration = await new Integration({
|
||||||
workspace: req.integrationAuth.workspace._id,
|
workspace: req.integrationAuth.workspace._id,
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment,
|
||||||
isActive,
|
isActive,
|
||||||
app,
|
app,
|
||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
region,
|
region,
|
||||||
integration: req.integrationAuth.integration,
|
integration: req.integrationAuth.integration,
|
||||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
if (integration) {
|
if (integration) {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
|||||||
// access token is expired
|
// access token is expired
|
||||||
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
|
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
|
||||||
accessToken = await exchangeRefresh({
|
accessToken = await exchangeRefresh({
|
||||||
integration: integrationAuth.integration,
|
integrationAuth,
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ import {
|
|||||||
INTEGRATION_CIRCLECI_API_URL,
|
INTEGRATION_CIRCLECI_API_URL,
|
||||||
INTEGRATION_TRAVISCI_API_URL,
|
INTEGRATION_TRAVISCI_API_URL,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { requireIntegrationAuthorizationAuth } from "../middleware";
|
|
||||||
|
interface App {
|
||||||
|
name: string;
|
||||||
|
appId?: string;
|
||||||
|
owner?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for integration named [integration]
|
* Return list of names of apps for integration named [integration]
|
||||||
@@ -45,12 +50,6 @@ const getApps = async ({
|
|||||||
teamId?: string;
|
teamId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
interface App {
|
|
||||||
name: string;
|
|
||||||
appId?: string;
|
|
||||||
owner?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let apps: App[] = [];
|
let apps: App[] = [];
|
||||||
try {
|
try {
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
@@ -87,6 +86,7 @@ const getApps = async ({
|
|||||||
case INTEGRATION_GITLAB:
|
case INTEGRATION_GITLAB:
|
||||||
apps = await getAppsGitlab({
|
apps = await getAppsGitlab({
|
||||||
accessToken,
|
accessToken,
|
||||||
|
teamId
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_RENDER:
|
case INTEGRATION_RENDER:
|
||||||
@@ -439,44 +439,107 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
* @returns {Object[]} apps - names of GitLab sites
|
* @returns {Object[]} apps - names of GitLab sites
|
||||||
* @returns {String} apps.name - name of GitLab site
|
* @returns {String} apps.name - name of GitLab site
|
||||||
*/
|
*/
|
||||||
const getAppsGitlab = async ({ accessToken }: {accessToken: string}) => {
|
const getAppsGitlab = async ({
|
||||||
let apps;
|
accessToken,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
accessToken: string;
|
||||||
|
teamId?: string;
|
||||||
|
}) => {
|
||||||
|
const apps: App[] = [];
|
||||||
|
|
||||||
|
let page = 1;
|
||||||
|
const perPage = 10;
|
||||||
|
let hasMorePages = true;
|
||||||
try {
|
try {
|
||||||
const { id } = (
|
|
||||||
await request.get(
|
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Authorization": `Bearer ${accessToken}`,
|
|
||||||
"Accept-Encoding": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
const res = (
|
if (teamId) {
|
||||||
await request.get(
|
// case: fetch projects for group with id [teamId] in GitLab
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
|
|
||||||
{
|
while (hasMorePages) {
|
||||||
headers: {
|
const params = new URLSearchParams({
|
||||||
"Authorization": `Bearer ${accessToken}`,
|
page: String(page),
|
||||||
"Accept-Encoding": "application/json",
|
per_page: String(perPage)
|
||||||
},
|
});
|
||||||
}
|
|
||||||
)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
apps = res?.map((a: any) => {
|
const { data } = (
|
||||||
return {
|
await request.get(
|
||||||
name: a?.name,
|
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
|
||||||
appId: `${a?.id}`,
|
{
|
||||||
|
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++;
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
}catch (err) {
|
// 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)
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error("Failed to get GitLab repos");
|
throw new Error("Failed to get GitLab projects");
|
||||||
}
|
}
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import {
|
|||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
INTEGRATION_GITHUB_TOKEN_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_GITLAB_TOKEN_URL,
|
INTEGRATION_GITLAB_TOKEN_URL
|
||||||
INTEGRATION_GITLAB_API_URL
|
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
@@ -169,7 +168,7 @@ const exchangeCodeAzure = async ({
|
|||||||
accessExpiresAt.setSeconds(
|
accessExpiresAt.setSeconds(
|
||||||
accessExpiresAt.getSeconds() + res.expires_in
|
accessExpiresAt.getSeconds() + res.expires_in
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Azure');
|
throw new Error('Failed OAuth2 code-token exchange with Azure');
|
||||||
@@ -392,46 +391,10 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
|||||||
)
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
// 1. try getting groups
|
|
||||||
// https://gitlab.com/api/v4/groups
|
|
||||||
|
|
||||||
// const res2 = (await request.get(
|
|
||||||
// `${INTEGRATION_GITLAB_API_URL}/v4/groups`,
|
|
||||||
// {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${res.access_token}`,
|
|
||||||
// "Accept-Encoding": "application/json"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// )).data;
|
|
||||||
|
|
||||||
// 2. try getting projects of that group
|
|
||||||
// const res3 = (await request.get(
|
|
||||||
// `${INTEGRATION_GITLAB_API_URL}/v4/groups/${res2[0].id}`,
|
|
||||||
// {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${res.access_token}`,
|
|
||||||
// "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",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// ).data;
|
|
||||||
|
|
||||||
accessExpiresAt.setSeconds(
|
accessExpiresAt.setSeconds(
|
||||||
accessExpiresAt.getSeconds() + res.expires_in
|
accessExpiresAt.getSeconds() + res.expires_in
|
||||||
);
|
);
|
||||||
}catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
|
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
import request from '../config/request';
|
import request from '../config/request';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU } from '../variables';
|
import {
|
||||||
|
IIntegrationAuth
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_HEROKU,
|
||||||
|
INTEGRATION_GITLAB,
|
||||||
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
CLIENT_ID_AZURE,
|
CLIENT_ID_AZURE,
|
||||||
|
CLIENT_ID_GITLAB,
|
||||||
CLIENT_SECRET_AZURE,
|
CLIENT_SECRET_AZURE,
|
||||||
CLIENT_SECRET_HEROKU
|
CLIENT_SECRET_HEROKU,
|
||||||
|
CLIENT_SECRET_GITLAB
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
INTEGRATION_AZURE_TOKEN_URL,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
|
INTEGRATION_GITLAB_TOKEN_URL
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
IntegrationService
|
||||||
|
} from '../services';
|
||||||
|
|
||||||
interface RefreshTokenAzureResponse {
|
interface RefreshTokenAzureResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
@@ -21,6 +34,23 @@ interface RefreshTokenAzureResponse {
|
|||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RefreshTokenHerokuResponse {
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
token_type: string;
|
||||||
|
user_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RefreshTokenGitLabResponse {
|
||||||
|
token_type: string;
|
||||||
|
scope: string;
|
||||||
|
expires_in: number;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -29,33 +59,61 @@ interface RefreshTokenAzureResponse {
|
|||||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
||||||
*/
|
*/
|
||||||
const exchangeRefresh = async ({
|
const exchangeRefresh = async ({
|
||||||
integration,
|
integrationAuth,
|
||||||
refreshToken
|
refreshToken
|
||||||
}: {
|
}: {
|
||||||
integration: string;
|
integrationAuth: IIntegrationAuth;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let accessToken;
|
|
||||||
|
interface TokenDetails {
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
accessExpiresAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenDetails: TokenDetails;
|
||||||
try {
|
try {
|
||||||
switch (integration) {
|
switch (integrationAuth.integration) {
|
||||||
case INTEGRATION_AZURE_KEY_VAULT:
|
case INTEGRATION_AZURE_KEY_VAULT:
|
||||||
accessToken = await exchangeRefreshAzure({
|
tokenDetails = await exchangeRefreshAzure({
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
accessToken = await exchangeRefreshHeroku({
|
tokenDetails = await exchangeRefreshHeroku({
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_GITLAB:
|
||||||
|
tokenDetails = await exchangeRefreshGitLab({
|
||||||
|
refreshToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Failed to exchange token for incompatible integration');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tokenDetails?.accessToken && tokenDetails?.refreshToken && tokenDetails?.accessExpiresAt) {
|
||||||
|
await IntegrationService.setIntegrationAuthAccess({
|
||||||
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
|
accessId: null,
|
||||||
|
accessToken: tokenDetails.accessToken,
|
||||||
|
accessExpiresAt: tokenDetails.accessExpiresAt
|
||||||
|
});
|
||||||
|
|
||||||
|
await IntegrationService.setIntegrationAuthRefresh({
|
||||||
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
|
refreshToken: tokenDetails.refreshToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenDetails.accessToken;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to get new OAuth2 access token');
|
throw new Error('Failed to get new OAuth2 access token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +129,8 @@ const exchangeRefreshAzure = async ({
|
|||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const res: RefreshTokenAzureResponse = (await request.post(
|
const accessExpiresAt = new Date();
|
||||||
|
const { data }: { data: RefreshTokenAzureResponse } = await request.post(
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
INTEGRATION_AZURE_TOKEN_URL,
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
client_id: CLIENT_ID_AZURE,
|
client_id: CLIENT_ID_AZURE,
|
||||||
@@ -80,9 +139,17 @@ const exchangeRefreshAzure = async ({
|
|||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
client_secret: CLIENT_SECRET_AZURE
|
client_secret: CLIENT_SECRET_AZURE
|
||||||
} as any)
|
} as any)
|
||||||
)).data;
|
);
|
||||||
|
|
||||||
return res.access_token;
|
accessExpiresAt.setSeconds(
|
||||||
|
accessExpiresAt.getSeconds() + data.expires_in
|
||||||
|
);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
@@ -102,10 +169,13 @@ const exchangeRefreshHeroku = async ({
|
|||||||
}: {
|
}: {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let accessToken;
|
|
||||||
try {
|
try {
|
||||||
const res = await request.post(
|
const accessExpiresAt = new Date();
|
||||||
|
const {
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
data: RefreshTokenHerokuResponse
|
||||||
|
} = await request.post(
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
@@ -114,14 +184,69 @@ const exchangeRefreshHeroku = async ({
|
|||||||
} as any)
|
} as any)
|
||||||
);
|
);
|
||||||
|
|
||||||
accessToken = res.data.access_token;
|
accessExpiresAt.setSeconds(
|
||||||
|
accessExpiresAt.getSeconds() + data.expires_in
|
||||||
|
);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to refresh OAuth2 access token for Heroku');
|
throw new Error('Failed to refresh OAuth2 access token for Heroku');
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return accessToken;
|
/**
|
||||||
|
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||||
|
* GitLab integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for GitLab
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const exchangeRefreshGitLab = async ({
|
||||||
|
refreshToken
|
||||||
|
}: {
|
||||||
|
refreshToken: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
const {
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
data: RefreshTokenGitLabResponse
|
||||||
|
} = await request.post(
|
||||||
|
INTEGRATION_GITLAB_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
client_id: CLIENT_ID_GITLAB,
|
||||||
|
client_secret: CLIENT_SECRET_GITLAB,
|
||||||
|
redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback`
|
||||||
|
} as any),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(
|
||||||
|
accessExpiresAt.getSeconds() + data.expires_in
|
||||||
|
);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to refresh OAuth2 access token for GitLab');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { exchangeRefresh };
|
export { exchangeRefresh };
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ const syncSecretsAzureKeyVault = async ({
|
|||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
interface GetAzureKeyVaultSecret {
|
interface GetAzureKeyVaultSecret {
|
||||||
id: string; // secret URI
|
id: string; // secret URI
|
||||||
attributes: {
|
attributes: {
|
||||||
@@ -1512,7 +1511,7 @@ const syncSecretsGitLab = async ({
|
|||||||
)
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
for (const key of Object.keys(secrets)) {
|
for await (const key of Object.keys(secrets)) {
|
||||||
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
||||||
if (!existingSecret) {
|
if (!existingSecret) {
|
||||||
await request.post(
|
await request.post(
|
||||||
@@ -1533,7 +1532,7 @@ const syncSecretsGitLab = async ({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}else {
|
} else {
|
||||||
// udpate secret
|
// udpate secret
|
||||||
await request.put(
|
await request.put(
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`,
|
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`,
|
||||||
@@ -1553,7 +1552,7 @@ const syncSecretsGitLab = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete secrets
|
// delete secrets
|
||||||
for (const sec of getSecretsRes) {
|
for await (const sec of getSecretsRes) {
|
||||||
if (!(sec.key in secrets)) {
|
if (!(sec.key in secrets)) {
|
||||||
await request.delete(
|
await request.delete(
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`,
|
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`,
|
||||||
|
|||||||
@@ -8,6 +8,20 @@ import {
|
|||||||
} from '../variables';
|
} from '../variables';
|
||||||
import request from '../config/request';
|
import request from '../config/request';
|
||||||
|
|
||||||
|
interface Team {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of teams for integration authorization [integrationAuth]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.integrationAuth - integration authorization to get teams for
|
||||||
|
* @param {String} obj.accessToken - access token for integration authorization
|
||||||
|
* @returns {Object[]} teams - teams of integration authorization
|
||||||
|
* @returns {String} teams.name - name of team
|
||||||
|
* @returns {String} teams.teamId - id of team
|
||||||
|
*/
|
||||||
const getTeams = async ({
|
const getTeams = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
@@ -15,10 +29,6 @@ const getTeams = async ({
|
|||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
interface Team {
|
|
||||||
name: string;
|
|
||||||
teamId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let teams: Team[] = [];
|
let teams: Team[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -39,12 +49,20 @@ const getTeams = async ({
|
|||||||
return teams;
|
return teams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of teams for GitLab integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for GitLab API
|
||||||
|
* @returns {Object[]} teams - teams that user is part of in GitLab
|
||||||
|
* @returns {String} teams.name - name of team
|
||||||
|
* @returns {String} teams.teamId - id of team
|
||||||
|
*/
|
||||||
const getTeamsGitLab = async ({
|
const getTeamsGitLab = async ({
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let teams = [];
|
let teams: Team[] = [];
|
||||||
try {
|
try {
|
||||||
const res = (await request.get(
|
const res = (await request.get(
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
|
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
|
||||||
|
|||||||
@@ -62,13 +62,11 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
appId: {
|
appId: {
|
||||||
// (new)
|
|
||||||
// id of app in provider
|
// id of app in provider
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
targetEnvironment: {
|
targetEnvironment: {
|
||||||
// (new)
|
|
||||||
// target environment
|
// target environment
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
|
|||||||
@@ -64,9 +64,6 @@ router.post(
|
|||||||
integrationAuthController.saveIntegrationAccessToken
|
integrationAuthController.saveIntegrationAccessToken
|
||||||
);
|
);
|
||||||
|
|
||||||
// this can optionally accept a teamId?
|
|
||||||
// IF teamId is passed in then it probably means that we want to get
|
|
||||||
// the apps for that team
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:integrationAuthId/apps',
|
'/:integrationAuthId/apps',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
@@ -76,7 +73,7 @@ router.get(
|
|||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
}),
|
}),
|
||||||
param('integrationAuthId'),
|
param('integrationAuthId'),
|
||||||
query('entity'),
|
query('teamId'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
integrationAuthController.getIntegrationAuthApps
|
integrationAuthController.getIntegrationAuthApps
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import {
|
|||||||
setIntegrationAuthAccessHelper,
|
setIntegrationAuthAccessHelper,
|
||||||
} from '../helpers/integration';
|
} from '../helpers/integration';
|
||||||
|
|
||||||
// should sync stuff be here too? Probably.
|
|
||||||
// TODO: move bot functions to IntegrationService.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to handle integrations
|
* Class to handle integrations
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ const fetchIntegrationAuthApps = async ({
|
|||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
console.log('fetchIntegrationAuthApps: ', integrationAuthId);
|
|
||||||
const searchParams = new URLSearchParams(teamId ? { teamId } : undefined);
|
const searchParams = new URLSearchParams(teamId ? { teamId } : undefined);
|
||||||
const { data } = await apiRequest.get<{ apps: App[] }>(
|
const { data } = await apiRequest.get<{ apps: App[] }>(
|
||||||
`/api/v1/integration-auth/${integrationAuthId}/apps`,
|
`/api/v1/integration-auth/${integrationAuthId}/apps`,
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ const gitLabEntities = [
|
|||||||
{ name: 'Group', value: 'group' }
|
{ name: 'Group', value: 'group' }
|
||||||
]
|
]
|
||||||
|
|
||||||
// TODO: flesh out the data flow...
|
|
||||||
|
|
||||||
export default function GitLabCreateIntegrationPage() {
|
export default function GitLabCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -33,59 +31,51 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
|
|
||||||
const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value);
|
const [targetTeamId, setTargetTeamId] = useState<string | null>(null);
|
||||||
const [targetTeam, setTargetTeam] = useState('aa'); // ?
|
|
||||||
const [targetTeamId] = useState(undefined);
|
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: integrationAuthId as string ?? '',
|
integrationAuthId: integrationAuthId as string ?? '',
|
||||||
...(targetTeamId ? { teamId: targetTeamId } : {})
|
...(targetTeamId ? { teamId: targetTeamId } : {})
|
||||||
});
|
});
|
||||||
const { data: integrationAuthTeams } = useGetIntegrationAuthTeams(integrationAuthId as string ?? '');
|
const { data: integrationAuthTeams } = useGetIntegrationAuthTeams(integrationAuthId as string ?? '');
|
||||||
console.log('integrationAuthTeams: ', integrationAuthTeams);
|
|
||||||
|
|
||||||
|
const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value);
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetAppId, setTargetAppId] = useState('');
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
if (integrationAuthApps.length > 0) {
|
if (integrationAuthApps.length > 0) {
|
||||||
console.log('AA');
|
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
|
||||||
} else {
|
} else {
|
||||||
console.log('BB');
|
setTargetAppId('none');
|
||||||
setTargetApp('none');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if (targetEntity === 'group' && integrationAuthTeams) {
|
if (targetEntity === 'group' && integrationAuthTeams && integrationAuthTeams.length > 0) {
|
||||||
// if (integrationAuthTeams.length > 0) {
|
if (integrationAuthTeams) {
|
||||||
// setTargetTeam(integrationAuthTeams[0].name);
|
if (integrationAuthTeams.length > 0) {
|
||||||
// } else {
|
// case: user is part of at least 1 group in GitLab
|
||||||
// setTargetTeam('none');
|
setTargetTeamId(integrationAuthTeams[0].teamId);
|
||||||
// }
|
} else {
|
||||||
// }
|
// case: user is not part of any groups in GitLab
|
||||||
|
setTargetTeamId('none');
|
||||||
// if (targetEntity === 'group') {
|
}
|
||||||
// if (integrationAuthTeams && integrationAuthTeams.length > 0) {
|
}
|
||||||
// setTargetTeamId(integrationAuthTeams[0].teamId);
|
} else if (targetEntity === 'individual') {
|
||||||
// } else {
|
setTargetTeamId(null);
|
||||||
// setTargetTeamId('');
|
}
|
||||||
// }
|
}, [targetEntity, integrationAuthTeams]);
|
||||||
// } else {
|
|
||||||
|
|
||||||
// }
|
|
||||||
}, [integrationAuthTeams, integrationAuthApps, targetEntity]);
|
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -95,8 +85,8 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
await createIntegration({
|
await createIntegration({
|
||||||
integrationAuthId: integrationAuth?._id,
|
integrationAuthId: integrationAuth?._id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
app: targetApp,
|
app: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId))?.name ?? null,
|
||||||
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
|
appId: targetAppId,
|
||||||
sourceEnvironment: selectedSourceEnvironment,
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
targetEnvironment: null,
|
targetEnvironment: null,
|
||||||
owner: null,
|
owner: null,
|
||||||
@@ -113,15 +103,7 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('A', (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetApp && targetTeam));
|
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetAppId) ? (
|
||||||
console.log('B', integrationAuth);
|
|
||||||
console.log('C', workspace);
|
|
||||||
console.log('D', selectedSourceEnvironment);
|
|
||||||
console.log('E', integrationAuthApps);
|
|
||||||
console.log('F', integrationAuthTeams);
|
|
||||||
console.log('G', targetApp);
|
|
||||||
console.log('H', targetTeam);
|
|
||||||
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetApp && targetTeam) ? (
|
|
||||||
<div className="h-full w-full flex justify-center items-center">
|
<div className="h-full w-full flex justify-center items-center">
|
||||||
<Card className="max-w-md p-8 rounded-md">
|
<Card className="max-w-md p-8 rounded-md">
|
||||||
<CardTitle className='text-center'>GitLab Integration</CardTitle>
|
<CardTitle className='text-center'>GitLab Integration</CardTitle>
|
||||||
@@ -165,13 +147,13 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
className='mt-4'
|
className='mt-4'
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
value={targetTeam}
|
value={targetTeamId}
|
||||||
onValueChange={(val) => setTargetTeam(val)}
|
onValueChange={(val) => setTargetTeamId(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{integrationAuthTeams.length > 0 ? (
|
{integrationAuthTeams.length > 0 ? (
|
||||||
integrationAuthTeams.map((integrationAuthTeam) => (
|
integrationAuthTeams.map((integrationAuthTeam) => (
|
||||||
<SelectItem value={integrationAuthTeam.name} key={`target-team-${integrationAuthTeam.name}`}>
|
<SelectItem value={integrationAuthTeam.teamId} key={`target-team-${integrationAuthTeam.teamId}`}>
|
||||||
{integrationAuthTeam.name}
|
{integrationAuthTeam.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
@@ -188,14 +170,14 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
className='mt-4'
|
className='mt-4'
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
value={targetApp}
|
value={targetAppId}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetAppId(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.length > 0 ? (
|
{integrationAuthApps.length > 0 ? (
|
||||||
integrationAuthApps.map((integrationAuthApp) => (
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
<SelectItem value={integrationAuthApp.appId as string} key={`target-app-${integrationAuthApp.appId}`}>
|
||||||
{integrationAuthApp.name}
|
{integrationAuthApp.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
|
|||||||
Reference in New Issue
Block a user