mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-12 14:29:34 +00:00
Merge remote-tracking branch 'refs/remotes/origin/main' into circleci-integration-branch
This commit is contained in:
Generated
+18
-82
@@ -10,13 +10,13 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@sentry/node": "^7.21.1",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/tracing": "^7.21.1",
|
||||
"@sentry/node": "^7.14.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"axios": "^1.2.0",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"await-to-js": "^3.0.0",
|
||||
"axios": "^1.1.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bigint-conversion": "^2.2.2",
|
||||
"builder-pattern": "^2.2.0",
|
||||
@@ -32,9 +32,9 @@
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
"mongoose": "^6.7.3",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.2.2",
|
||||
"query-string": "^7.1.3",
|
||||
@@ -2838,19 +2838,6 @@
|
||||
"@maxmind/geoip2-node": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.21.1.tgz",
|
||||
"integrity": "sha512-Og5wEEsy24fNvT/T7IKjcV4EvVK5ryY2kxbJzKY6GU2eX+i+aBl+n/vp7U0Es351C/AlTkS+0NOUsp2TQQFxZA==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.21.1",
|
||||
"@sentry/utils": "7.21.1",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -2905,27 +2892,10 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"node_modules/@sentry/node": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz",
|
||||
"integrity": "sha512-yG7Tx32WqOkEHVotFLrumCcT9qlaSDTkFNZ+yLSvZXx74ifsE781DzBA9W7K7bBdYO3op+p2YdsOKzf3nPpAyQ==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "7.19.0",
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"cookie": "^0.4.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru_map": "^0.3.3",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node/node_modules/@sentry/core": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.19.0.tgz",
|
||||
"integrity": "sha512-YF9cTBcAnO4R44092BJi5Wa2/EO02xn2ziCtmNgAVTN2LD31a/YVGxGBt/FDr4Y6yeuVehaqijVVvtpSmXrGJw==",
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.21.1.tgz",
|
||||
"integrity": "sha512-Og5wEEsy24fNvT/T7IKjcV4EvVK5ryY2kxbJzKY6GU2eX+i+aBl+n/vp7U0Es351C/AlTkS+0NOUsp2TQQFxZA==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.21.1",
|
||||
"@sentry/utils": "7.21.1",
|
||||
@@ -2986,26 +2956,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/types": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.21.1.tgz",
|
||||
"integrity": "sha512-3/IKnd52Ol21amQvI+kz+WB76s8/LR5YvFJzMgIoI2S8d82smIr253zGijRXxHPEif8kMLX4Yt+36VzrLxg6+A==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/utils": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.21.1.tgz",
|
||||
"integrity": "sha512-F0W0AAi8tgtTx6ApZRI2S9HbXEA9ENX1phTZgdNNWcMFm1BNbc21XEwLqwXBNjub5nlA6CE8xnjXRgdZKx4kzQ==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.21.1",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.24.51",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
|
||||
@@ -14306,16 +14256,6 @@
|
||||
"@maxmind/geoip2-node": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"@sentry/core": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.21.1.tgz",
|
||||
"integrity": "sha512-Og5wEEsy24fNvT/T7IKjcV4EvVK5ryY2kxbJzKY6GU2eX+i+aBl+n/vp7U0Es351C/AlTkS+0NOUsp2TQQFxZA==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.21.1",
|
||||
"@sentry/utils": "7.21.1",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -14370,6 +14310,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"@sentry/core": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.21.1.tgz",
|
||||
"integrity": "sha512-Og5wEEsy24fNvT/T7IKjcV4EvVK5ryY2kxbJzKY6GU2eX+i+aBl+n/vp7U0Es351C/AlTkS+0NOUsp2TQQFxZA==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.21.1",
|
||||
"@sentry/utils": "7.21.1",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/node": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.21.1.tgz",
|
||||
@@ -14409,20 +14359,6 @@
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.21.1.tgz",
|
||||
"integrity": "sha512-3/IKnd52Ol21amQvI+kz+WB76s8/LR5YvFJzMgIoI2S8d82smIr253zGijRXxHPEif8kMLX4Yt+36VzrLxg6+A=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.21.1.tgz",
|
||||
"integrity": "sha512-F0W0AAi8tgtTx6ApZRI2S9HbXEA9ENX1phTZgdNNWcMFm1BNbc21XEwLqwXBNjub5nlA6CE8xnjXRgdZKx4kzQ==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.21.1",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sinclair/typebox": {
|
||||
"version": "0.24.51",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
serviceTokenData as v2ServiceTokenDataRouter,
|
||||
apiKeyData as v2APIKeyDataRouter,
|
||||
environment as v2EnvironmentRouter,
|
||||
tags as v2TagsRouter,
|
||||
} from './routes/v2';
|
||||
|
||||
import { healthCheck } from './routes/status';
|
||||
@@ -112,6 +113,7 @@ app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||
app.use('/api/v2/users', v2UsersRouter);
|
||||
app.use('/api/v2/organizations', v2OrganizationsRouter);
|
||||
app.use('/api/v2/workspace', v2EnvironmentRouter);
|
||||
app.use('/api/v2/workspace', v2TagsRouter);
|
||||
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
||||
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
||||
app.use('/api/v2/secrets', v2SecretsRouter);
|
||||
|
||||
@@ -13,10 +13,13 @@ const MONGO_URL = process.env.MONGO_URL!;
|
||||
const NODE_ENV = process.env.NODE_ENV! || 'production';
|
||||
const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true;
|
||||
const LOKI_HOST = process.env.LOKI_HOST || undefined;
|
||||
const CLIENT_ID_AZURE = process.env.CLIENT_ID_AZURE!;
|
||||
const TENANT_ID_AZURE = process.env.TENANT_ID_AZURE!;
|
||||
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
|
||||
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
|
||||
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
|
||||
const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!;
|
||||
const CLIENT_SECRET_AZURE = process.env.CLIENT_SECRET_AZURE!;
|
||||
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
|
||||
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
|
||||
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
|
||||
@@ -60,10 +63,13 @@ export {
|
||||
NODE_ENV,
|
||||
VERBOSE_ERROR_OUTPUT,
|
||||
LOKI_HOST,
|
||||
CLIENT_ID_AZURE,
|
||||
TENANT_ID_AZURE,
|
||||
CLIENT_ID_HEROKU,
|
||||
CLIENT_ID_VERCEL,
|
||||
CLIENT_ID_NETLIFY,
|
||||
CLIENT_ID_GITHUB,
|
||||
CLIENT_SECRET_AZURE,
|
||||
CLIENT_SECRET_HEROKU,
|
||||
CLIENT_SECRET_VERCEL,
|
||||
CLIENT_SECRET_NETLIFY,
|
||||
|
||||
@@ -4,16 +4,21 @@ import jwt from 'jsonwebtoken';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import * as bigintConversion from 'bigint-conversion';
|
||||
const jsrp = require('jsrp');
|
||||
import { User } from '../../models';
|
||||
import { User, LoginSRPDetail } from '../../models';
|
||||
import { createToken, issueTokens, clearTokens } from '../../helpers/auth';
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT
|
||||
} from '../../variables';
|
||||
import {
|
||||
NODE_ENV,
|
||||
JWT_AUTH_LIFETIME,
|
||||
JWT_AUTH_SECRET,
|
||||
JWT_REFRESH_SECRET
|
||||
} from '../../config';
|
||||
import LoginSRPDetail from '../../models/LoginSRPDetail';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
import { EELogService } from '../../ee/services';
|
||||
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
|
||||
|
||||
declare module 'jsonwebtoken' {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
@@ -116,6 +121,18 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
secure: NODE_ENV === 'production' ? true : false
|
||||
});
|
||||
|
||||
const loginAction = await EELogService.createAction({
|
||||
name: ACTION_LOGIN,
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
loginAction && await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
// return (access) token in response
|
||||
return res.status(200).send({
|
||||
token: tokens.token,
|
||||
@@ -159,6 +176,19 @@ export const logout = async (req: Request, res: Response) => {
|
||||
sameSite: 'strict',
|
||||
secure: NODE_ENV === 'production' ? true : false
|
||||
});
|
||||
|
||||
const logoutAction = await EELogService.createAction({
|
||||
name: ACTION_LOGOUT,
|
||||
userId: req.user._id
|
||||
});
|
||||
|
||||
logoutAction && await EELogService.createLog({
|
||||
userId: req.user._id,
|
||||
actions: [logoutAction],
|
||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
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 { 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';
|
||||
|
||||
/***
|
||||
* 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) => {
|
||||
return res.status(200).send({
|
||||
@@ -18,35 +47,37 @@ export const getIntegrationOptions = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const oAuthExchange = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { workspaceId, code, integration } = req.body;
|
||||
|
||||
if (!INTEGRATION_SET.has(integration))
|
||||
throw new Error("Failed to validate integration");
|
||||
|
||||
const environments = req.membership.workspace?.environments || [];
|
||||
if (environments.length === 0) {
|
||||
throw new Error("Failed to get environments");
|
||||
}
|
||||
|
||||
await IntegrationService.handleOAuthExchange({
|
||||
workspaceId,
|
||||
integration,
|
||||
code,
|
||||
environment: environments[0].slug,
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: "Failed to get OAuth2 code-token exchange",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully enabled integration authorization",
|
||||
});
|
||||
export const oAuthExchange = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
try {
|
||||
const { workspaceId, code, integration } = req.body;
|
||||
if (!INTEGRATION_SET.has(integration))
|
||||
throw new Error('Failed to validate integration');
|
||||
|
||||
const environments = req.membership.workspace?.environments || [];
|
||||
if(environments.length === 0){
|
||||
throw new Error("Failed to get environments")
|
||||
}
|
||||
|
||||
const integrationAuth = await IntegrationService.handleOAuthExchange({
|
||||
workspaceId,
|
||||
integration,
|
||||
code,
|
||||
environment: environments[0].slug,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
integrationAuth
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: 'Failed to get OAuth2 code-token exchange'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -71,50 +102,44 @@ export const saveIntegrationAccessToken = async (
|
||||
integration: string;
|
||||
} = req.body;
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||
{
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration,
|
||||
},
|
||||
{
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration,
|
||||
},
|
||||
{
|
||||
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');
|
||||
|
||||
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({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessToken,
|
||||
accessExpiresAt: undefined,
|
||||
});
|
||||
|
||||
if (!integrationAuth)
|
||||
throw new Error("Failed to save integration access token");
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: "Failed to save access token for integration",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
integrationAuth,
|
||||
});
|
||||
};
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration
|
||||
}, {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration
|
||||
}, {
|
||||
new: true,
|
||||
upsert: true
|
||||
});
|
||||
|
||||
// encrypt and save integration access token
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
|
||||
if (!integrationAuth) throw new Error('Failed to save integration access token');
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: 'Failed to save access token for integration'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
integrationAuth
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of applications allowed for integration with integration authorization id [integrationAuthId]
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Request, Response } from "express";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { Integration, Workspace, Bot, BotKey } from "../../models";
|
||||
import { EventService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { Request, Response } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
Integration,
|
||||
Workspace,
|
||||
Bot,
|
||||
BotKey
|
||||
} from '../../models';
|
||||
import { EventService } from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
|
||||
/**
|
||||
* Create/initialize an (empty) integration for integration authorization
|
||||
@@ -11,24 +17,49 @@ import { eventPushSecrets } from "../../events";
|
||||
* @returns
|
||||
*/
|
||||
export const createIntegration = async (req: Request, res: Response) => {
|
||||
let integration;
|
||||
try {
|
||||
// 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,
|
||||
integration: req.integrationAuth.integration,
|
||||
integrationAuth: req.integrationAuth._id,
|
||||
}).save();
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: "Failed to create integration",
|
||||
});
|
||||
}
|
||||
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,
|
||||
environment: sourceEnvironment,
|
||||
isActive,
|
||||
app,
|
||||
appId,
|
||||
targetEnvironment,
|
||||
owner,
|
||||
integration: req.integrationAuth.integration,
|
||||
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);
|
||||
return res.status(400).send({
|
||||
message: 'Failed to create integration'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
integration,
|
||||
|
||||
@@ -77,8 +77,6 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
|
||||
// change role for (target) organization membership with id
|
||||
// [membershipOrgId]
|
||||
|
||||
// TODO
|
||||
|
||||
let membershipToChangeRole;
|
||||
// try {
|
||||
// } catch (err) {
|
||||
|
||||
@@ -4,12 +4,11 @@ import crypto from 'crypto';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const jsrp = require('jsrp');
|
||||
import * as bigintConversion from 'bigint-conversion';
|
||||
import { User, Token, BackupPrivateKey } from '../../models';
|
||||
import { User, Token, BackupPrivateKey, LoginSRPDetail } from '../../models';
|
||||
import { checkEmailVerification } from '../../helpers/signup';
|
||||
import { createToken } from '../../helpers/auth';
|
||||
import { sendMail } from '../../helpers/nodemailer';
|
||||
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config';
|
||||
import LoginSRPDetail from '../../models/LoginSRPDetail';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as apiKeyDataController from './apiKeyDataController';
|
||||
import * as secretController from './secretController';
|
||||
import * as secretsController from './secretsController';
|
||||
import * as environmentController from './environmentController';
|
||||
import * as tagController from './tagController';
|
||||
|
||||
export {
|
||||
usersController,
|
||||
@@ -15,5 +16,6 @@ export {
|
||||
apiKeyDataController,
|
||||
secretController,
|
||||
secretsController,
|
||||
environmentController
|
||||
environmentController,
|
||||
tagController
|
||||
}
|
||||
|
||||
@@ -79,24 +79,38 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
*/
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||
const { workspaceId, environment } = req.body;
|
||||
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
||||
|
||||
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_WRITE)
|
||||
if (!hasAccess) {
|
||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||
}
|
||||
|
||||
let toAdd;
|
||||
let listOfSecretsToCreate;
|
||||
if (Array.isArray(req.body.secrets)) {
|
||||
// case: create multiple secrets
|
||||
toAdd = req.body.secrets;
|
||||
listOfSecretsToCreate = req.body.secrets;
|
||||
} else if (typeof req.body.secrets === 'object') {
|
||||
// case: create 1 secret
|
||||
toAdd = [req.body.secrets];
|
||||
listOfSecretsToCreate = [req.body.secrets];
|
||||
}
|
||||
|
||||
const newSecrets = await Secret.insertMany(
|
||||
toAdd.map(({
|
||||
type secretsToCreateType = {
|
||||
type: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
secretCommentCiphertext: string;
|
||||
secretCommentIV: string;
|
||||
secretCommentTag: string;
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
const newlyCreatedSecrets = await Secret.insertMany(
|
||||
listOfSecretsToCreate.map(({
|
||||
type,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
@@ -104,15 +118,11 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
}: {
|
||||
type: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
}) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
}: secretsToCreateType) => {
|
||||
return ({
|
||||
version: 1,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
@@ -124,7 +134,11 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
});
|
||||
})
|
||||
);
|
||||
@@ -140,7 +154,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// (EE) add secret versions for new secrets
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions: newSecrets.map(({
|
||||
secretVersions: newlyCreatedSecrets.map(({
|
||||
_id,
|
||||
version,
|
||||
workspace,
|
||||
@@ -154,7 +168,11 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash
|
||||
secretValueHash,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
}) => ({
|
||||
_id: new Types.ObjectId(),
|
||||
secret: _id,
|
||||
@@ -171,21 +189,25 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash
|
||||
secretValueHash,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
}))
|
||||
});
|
||||
|
||||
const addAction = await EELogService.createActionSecret({
|
||||
const addAction = await EELogService.createAction({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
secretIds: newSecrets.map((n) => n._id)
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
addAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions: [addAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
@@ -201,7 +223,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
event: 'secrets added',
|
||||
distinctId: req.user.email,
|
||||
properties: {
|
||||
numberOfSecrets: toAdd.length,
|
||||
numberOfSecrets: listOfSecretsToCreate.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel,
|
||||
@@ -211,7 +233,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: newSecrets
|
||||
secrets: newlyCreatedSecrets
|
||||
});
|
||||
}
|
||||
|
||||
@@ -294,22 +316,22 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
],
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
}
|
||||
).then())
|
||||
).populate("tags").then())
|
||||
|
||||
if (err) throw ValidationError({ message: 'Failed to get secrets', stack: err.stack });
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||
|
||||
const readAction = await EELogService.createActionSecret({
|
||||
const readAction = await EELogService.createAction({
|
||||
name: ACTION_READ_SECRETS,
|
||||
userId: userId,
|
||||
workspaceId: workspaceId as string,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
secretIds: secrets.map((n: any) => n._id)
|
||||
});
|
||||
|
||||
readAction && await EELogService.createLog({
|
||||
userId: userId,
|
||||
workspaceId: workspaceId as string,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
actions: [readAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
@@ -398,6 +420,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext: string;
|
||||
secretCommentIV: string;
|
||||
secretCommentTag: string;
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
|
||||
@@ -410,7 +433,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
secretCommentTag,
|
||||
tags
|
||||
} = secret;
|
||||
|
||||
return ({
|
||||
@@ -426,6 +450,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
tags,
|
||||
...((
|
||||
secretCommentCiphertext &&
|
||||
secretCommentIV &&
|
||||
@@ -460,6 +485,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
} = secretModificationsBySecretId[secret._id.toString()]
|
||||
|
||||
return ({
|
||||
@@ -477,6 +503,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext: secretCommentCiphertext ? secretCommentCiphertext : secret.secretCommentCiphertext,
|
||||
secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
|
||||
secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
|
||||
tags: tags ? tags : secret.tags
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -505,17 +532,17 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
const updateAction = await EELogService.createActionSecret({
|
||||
const updateAction = await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
updateAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [updateAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
@@ -631,17 +658,17 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: key
|
||||
})
|
||||
});
|
||||
const deleteAction = await EELogService.createActionSecret({
|
||||
const deleteAction = await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
deleteAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: key,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [deleteAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Membership,
|
||||
} from '../../models';
|
||||
import Tag, { ITag } from '../../models/tag';
|
||||
import { Builder } from "builder-pattern"
|
||||
import to from 'await-to-js';
|
||||
import { BadRequestError, UnauthorizedRequestError } from '../../utils/errors';
|
||||
import { MongoError } from 'mongodb';
|
||||
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
|
||||
|
||||
export const createWorkspaceTag = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params
|
||||
const { name, slug } = req.body
|
||||
const sanitizedTagToCreate = Builder<ITag>()
|
||||
.name(name)
|
||||
.workspace(new Types.ObjectId(workspaceId))
|
||||
.slug(slug)
|
||||
.user(new Types.ObjectId(req.user._id))
|
||||
.build();
|
||||
|
||||
const [err, createdTag] = await to(Tag.create(sanitizedTagToCreate))
|
||||
|
||||
if (err) {
|
||||
if ((err as MongoError).code === 11000) {
|
||||
throw BadRequestError({ message: "Tags must be unique in a workspace" })
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
res.json(createdTag)
|
||||
}
|
||||
|
||||
export const deleteWorkspaceTag = async (req: Request, res: Response) => {
|
||||
const { tagId } = req.params
|
||||
|
||||
const tagFromDB = await Tag.findById(tagId)
|
||||
if (!tagFromDB) {
|
||||
throw BadRequestError()
|
||||
}
|
||||
|
||||
// can only delete if the request user is one that belongs to the same workspace as the tag
|
||||
const membership = await Membership.findOne({
|
||||
user: req.user,
|
||||
workspace: tagFromDB.workspace
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
UnauthorizedRequestError({ message: 'Failed to validate membership' });
|
||||
}
|
||||
|
||||
const result = await Tag.findByIdAndDelete(tagId);
|
||||
|
||||
res.json(result);
|
||||
}
|
||||
|
||||
export const getWorkspaceTags = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params
|
||||
const workspaceTags = await Tag.find({ workspace: workspaceId })
|
||||
return res.json({
|
||||
workspaceTags
|
||||
})
|
||||
}
|
||||
@@ -467,4 +467,42 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
|
||||
return res.status(200).send({
|
||||
membership
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change autoCapitilzation Rule of workspace
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
||||
let workspace;
|
||||
try {
|
||||
const { workspaceId } = req.params;
|
||||
const { autoCapitalization } = req.body;
|
||||
|
||||
workspace = await Workspace.findOneAndUpdate(
|
||||
{
|
||||
_id: workspaceId
|
||||
},
|
||||
{
|
||||
autoCapitalization
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: 'Failed to change autoCapitalization setting'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully changed autoCapitalization setting',
|
||||
workspace
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,13 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
|
||||
|
||||
secretSnapshot = await SecretSnapshot
|
||||
.findById(secretSnapshotId)
|
||||
.populate('secretVersions');
|
||||
.populate({
|
||||
path: 'secretVersions',
|
||||
populate: {
|
||||
path: 'tags',
|
||||
model: 'Tag',
|
||||
}
|
||||
});
|
||||
|
||||
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
|
||||
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import { SecretVersion, Action } from '../models';
|
||||
import { Action } from '../models';
|
||||
import {
|
||||
getLatestSecretVersionIds,
|
||||
getLatestNSecretSecretVersionIds
|
||||
} from '../helpers/secretVersion';
|
||||
import { ACTION_UPDATE_SECRETS } from '../../variables';
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
} from '../../variables';
|
||||
|
||||
/**
|
||||
* Create an (audit) action for secrets including
|
||||
* add, delete, update, and read actions.
|
||||
* Create an (audit) action for updating secrets
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.name - name of action
|
||||
* @param {ObjectId[]} obj.secretIds - ids of relevant secrets
|
||||
* @param {Types.ObjectId} obj.secretIds - ids of relevant secrets
|
||||
* @returns {Action} action - new action
|
||||
*/
|
||||
const createActionSecretHelper = async ({
|
||||
const createActionUpdateSecret = async ({
|
||||
name,
|
||||
userId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
}: {
|
||||
name: string;
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
userId: Types.ObjectId;
|
||||
workspaceId: Types.ObjectId;
|
||||
secretIds: Types.ObjectId[];
|
||||
}) => {
|
||||
|
||||
let action;
|
||||
let latestSecretVersions;
|
||||
try {
|
||||
if (name === ACTION_UPDATE_SECRETS) {
|
||||
// case: action is updating secrets
|
||||
// -> add old and new secret versions
|
||||
latestSecretVersions = (await getLatestNSecretSecretVersionIds({
|
||||
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
|
||||
secretIds,
|
||||
n: 2
|
||||
}))
|
||||
@@ -41,17 +42,7 @@ const createActionSecretHelper = async ({
|
||||
oldSecretVersion: s.versions[0]._id,
|
||||
newSecretVersion: s.versions[1]._id
|
||||
}));
|
||||
} else {
|
||||
// case: action is adding, deleting, or reading secrets
|
||||
// -> add new secret versions
|
||||
latestSecretVersions = (await getLatestSecretVersionIds({
|
||||
secretIds
|
||||
}))
|
||||
.map((s) => ({
|
||||
newSecretVersion: s.versionId
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
action = await new Action({
|
||||
name,
|
||||
user: userId,
|
||||
@@ -64,10 +55,148 @@ const createActionSecretHelper = async ({
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to create update secret action');
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an (audit) action for creating, reading, and deleting
|
||||
* secrets
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.name - name of action
|
||||
* @param {Types.ObjectId} obj.secretIds - ids of relevant secrets
|
||||
* @returns {Action} action - new action
|
||||
*/
|
||||
const createActionSecret = async ({
|
||||
name,
|
||||
userId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
workspaceId: Types.ObjectId;
|
||||
secretIds: Types.ObjectId[];
|
||||
}) => {
|
||||
let action;
|
||||
try {
|
||||
// case: action is adding, deleting, or reading secrets
|
||||
// -> add new secret versions
|
||||
const latestSecretVersions = (await getLatestSecretVersionIds({
|
||||
secretIds
|
||||
}))
|
||||
.map((s) => ({
|
||||
newSecretVersion: s.versionId
|
||||
}));
|
||||
|
||||
action = await new Action({
|
||||
name,
|
||||
user: userId,
|
||||
workspace: workspaceId,
|
||||
payload: {
|
||||
secretVersions: latestSecretVersions
|
||||
}
|
||||
}).save();
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to create action create/read/delete secret action');
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an (audit) action for user with id [userId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.name - name of action
|
||||
* @param {String} obj.userId - id of user associated with action
|
||||
* @returns
|
||||
*/
|
||||
const createActionUser = ({
|
||||
name,
|
||||
userId
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
}) => {
|
||||
let action;
|
||||
try {
|
||||
action = new Action({
|
||||
name,
|
||||
user: userId
|
||||
}).save();
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to create user action');
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an (audit) action.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.name - name of action
|
||||
* @param {Types.ObjectId} obj.userId - id of user associated with action
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with action
|
||||
* @param {Types.ObjectId[]} obj.secretIds - ids of secrets associated with action
|
||||
*/
|
||||
const createActionHelper = async ({
|
||||
name,
|
||||
userId,
|
||||
workspaceId,
|
||||
secretIds,
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
secretIds?: Types.ObjectId[];
|
||||
}) => {
|
||||
let action;
|
||||
try {
|
||||
switch (name) {
|
||||
case ACTION_LOGIN:
|
||||
case ACTION_LOGOUT:
|
||||
action = await createActionUser({
|
||||
name,
|
||||
userId
|
||||
});
|
||||
break;
|
||||
case ACTION_ADD_SECRETS:
|
||||
case ACTION_READ_SECRETS:
|
||||
case ACTION_DELETE_SECRETS:
|
||||
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
|
||||
action = await createActionSecret({
|
||||
name,
|
||||
userId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
});
|
||||
break;
|
||||
case ACTION_UPDATE_SECRETS:
|
||||
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
|
||||
action = await createActionUpdateSecret({
|
||||
name,
|
||||
userId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to create action');
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export { createActionSecretHelper };
|
||||
export {
|
||||
createActionHelper
|
||||
};
|
||||
@@ -1,9 +1,19 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Log,
|
||||
IAction
|
||||
} from '../models';
|
||||
|
||||
/**
|
||||
* Create an (audit) log
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.userId - id of user associated with the log
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the log
|
||||
* @param {IAction[]} obj.actions - actions to include in log
|
||||
* @param {String} obj.channel - channel (web/cli/auto) associated with the log
|
||||
* @param {String} obj.ipAddress - ip address associated with the log
|
||||
* @returns {Log} log - new audit log
|
||||
*/
|
||||
const createLogHelper = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
@@ -11,8 +21,8 @@ const createLogHelper = async ({
|
||||
channel,
|
||||
ipAddress
|
||||
}: {
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
userId: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
actions: IAction[];
|
||||
channel: string;
|
||||
ipAddress: string;
|
||||
@@ -21,7 +31,7 @@ const createLogHelper = async ({
|
||||
try {
|
||||
log = await new Log({
|
||||
user: userId,
|
||||
workspace: workspaceId,
|
||||
workspace: workspaceId ?? undefined,
|
||||
actionNames: actions.map((a) => a.name),
|
||||
actions,
|
||||
channel,
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_DELETE_SECRETS
|
||||
} from '../../variables';
|
||||
|
||||
export interface IAction {
|
||||
name: string;
|
||||
user?: Types.ObjectId,
|
||||
workspace?: Types.ObjectId,
|
||||
payload: {
|
||||
payload?: {
|
||||
secretVersions?: Types.ObjectId[]
|
||||
}
|
||||
}
|
||||
@@ -13,7 +21,15 @@ const actionSchema = new Schema<IAction>(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
enum: [
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_DELETE_SECRETS
|
||||
]
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
@@ -29,6 +31,8 @@ const logSchema = new Schema<ILog>(
|
||||
actionNames: {
|
||||
type: [String],
|
||||
enum: [
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface ISecretVersion {
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
secretValueHash: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
@@ -88,7 +89,12 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
},
|
||||
secretValueHash: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
ref: 'Tag',
|
||||
type: [Schema.Types.ObjectId],
|
||||
default: []
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Log,
|
||||
Action,
|
||||
IAction
|
||||
} from '../models';
|
||||
import {
|
||||
createLogHelper
|
||||
} from '../helpers/log';
|
||||
import {
|
||||
createActionSecretHelper
|
||||
createActionHelper
|
||||
} from '../helpers/action';
|
||||
import EELicenseService from './EELicenseService';
|
||||
|
||||
@@ -33,8 +31,8 @@ class EELogService {
|
||||
channel,
|
||||
ipAddress
|
||||
}: {
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
userId: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
actions: IAction[];
|
||||
channel: string;
|
||||
ipAddress: string;
|
||||
@@ -50,26 +48,26 @@ class EELogService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an (audit) action for secrets including
|
||||
* add, delete, update, and read actions.
|
||||
* Create an (audit) action
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.name - name of action
|
||||
* @param {ObjectId[]} obj.secretIds - secret ids
|
||||
* @param {Types.ObjectId} obj.userId - id of user associated with the action
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the action
|
||||
* @param {ObjectId[]} obj.secretIds - ids of secrets associated with the action
|
||||
* @returns {Action} action - new action
|
||||
*/
|
||||
static async createActionSecret({
|
||||
static async createAction({
|
||||
name,
|
||||
userId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
}: {
|
||||
name: string;
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
secretIds: Types.ObjectId[];
|
||||
userId: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
secretIds?: Types.ObjectId[];
|
||||
}) {
|
||||
if (!EELicenseService.isLicenseValid) return null;
|
||||
return await createActionSecretHelper({
|
||||
return await createActionHelper({
|
||||
name,
|
||||
userId,
|
||||
workspaceId,
|
||||
|
||||
@@ -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,
|
||||
@@ -42,7 +43,6 @@ const handleOAuthExchangeHelper = async ({
|
||||
code: string;
|
||||
environment: string;
|
||||
}) => {
|
||||
let action;
|
||||
let integrationAuth;
|
||||
try {
|
||||
const bot = await Bot.findOne({
|
||||
@@ -98,21 +98,13 @@ const handleOAuthExchangeHelper = async ({
|
||||
accessExpiresAt: res.accessExpiresAt
|
||||
});
|
||||
}
|
||||
|
||||
// initialize new integration after exchange
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Sync/push environment variables in workspace with id [workspaceId] to
|
||||
|
||||
@@ -406,10 +406,10 @@ const v2PushSecrets = async ({
|
||||
secretIds: toDelete
|
||||
});
|
||||
|
||||
const deleteAction = await EELogService.createActionSecret({
|
||||
const deleteAction = await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId,
|
||||
workspaceId,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(userId),
|
||||
secretIds: toDelete
|
||||
});
|
||||
|
||||
@@ -499,10 +499,10 @@ const v2PushSecrets = async ({
|
||||
})
|
||||
});
|
||||
|
||||
const updateAction = await EELogService.createActionSecret({
|
||||
const updateAction = await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
userId,
|
||||
workspaceId,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: toUpdate.map((u) => u._id)
|
||||
});
|
||||
|
||||
@@ -536,10 +536,10 @@ const v2PushSecrets = async ({
|
||||
})
|
||||
});
|
||||
|
||||
const addAction = await EELogService.createActionSecret({
|
||||
const addAction = await EELogService.createAction({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
userId,
|
||||
workspaceId,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: newSecrets.map((n) => n._id)
|
||||
});
|
||||
addAction && actions.push(addAction);
|
||||
@@ -553,8 +553,8 @@ const v2PushSecrets = async ({
|
||||
// (EE) create (audit) log
|
||||
if (actions.length > 0) {
|
||||
await EELogService.createLog({
|
||||
userId,
|
||||
workspaceId,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions,
|
||||
channel,
|
||||
ipAddress
|
||||
@@ -645,16 +645,16 @@ const pullSecrets = async ({
|
||||
environment
|
||||
})
|
||||
|
||||
const readAction = await EELogService.createActionSecret({
|
||||
const readAction = await EELogService.createAction({
|
||||
name: ACTION_READ_SECRETS,
|
||||
userId,
|
||||
workspaceId,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: secrets.map((n: any) => n._id)
|
||||
});
|
||||
|
||||
readAction && await EELogService.createLog({
|
||||
userId,
|
||||
workspaceId,
|
||||
userId: new Types.ObjectId(userId),
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions: [readAction],
|
||||
channel,
|
||||
ipAddress
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as Sentry from "@sentry/node";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -42,6 +43,11 @@ const getApps = async ({
|
||||
let apps: App[];
|
||||
try {
|
||||
switch (integrationAuth.integration) {
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
apps = await getAppsAzureKeyVault({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_HEROKU:
|
||||
apps = await getAppsHeroku({
|
||||
accessToken,
|
||||
@@ -88,6 +94,15 @@ const getApps = async ({
|
||||
return apps;
|
||||
};
|
||||
|
||||
const getAppsAzureKeyVault = async ({
|
||||
accessToken
|
||||
}: {
|
||||
accessToken: string;
|
||||
}) => {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of apps for Heroku integration
|
||||
* @param {Object} obj
|
||||
@@ -246,14 +261,18 @@ const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
|
||||
await axios.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Accept-Encoding': 'application/json',
|
||||
},
|
||||
})
|
||||
).data;
|
||||
|
||||
apps = res.map((a: any) => ({
|
||||
name: a.service.name,
|
||||
appId: a.service.id,
|
||||
}));
|
||||
|
||||
apps = res
|
||||
.map((a: any) => ({
|
||||
name: a.service.name,
|
||||
appId: a.service.id
|
||||
}));
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
@@ -291,6 +310,8 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
||||
method: "post",
|
||||
headers: {
|
||||
Authorization: "Bearer " + accessToken,
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'application/json',
|
||||
},
|
||||
data: {
|
||||
query,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import axios from 'axios';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
@@ -12,15 +14,27 @@ import {
|
||||
} from '../variables';
|
||||
import {
|
||||
SITE_URL,
|
||||
CLIENT_ID_AZURE,
|
||||
CLIENT_ID_VERCEL,
|
||||
CLIENT_ID_NETLIFY,
|
||||
CLIENT_ID_GITHUB,
|
||||
CLIENT_SECRET_AZURE,
|
||||
CLIENT_SECRET_HEROKU,
|
||||
CLIENT_SECRET_VERCEL,
|
||||
CLIENT_SECRET_NETLIFY,
|
||||
CLIENT_SECRET_GITHUB
|
||||
} from '../config';
|
||||
|
||||
interface ExchangeCodeAzureResponse {
|
||||
token_type: string;
|
||||
scope: string;
|
||||
expires_in: number;
|
||||
ext_expires_in: number;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
id_token: string;
|
||||
}
|
||||
|
||||
interface ExchangeCodeHerokuResponse {
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
@@ -75,6 +89,11 @@ const exchangeCode = async ({
|
||||
|
||||
try {
|
||||
switch (integration) {
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
obj = await exchangeCodeAzure({
|
||||
code
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_HEROKU:
|
||||
obj = await exchangeCodeHeroku({
|
||||
code
|
||||
@@ -105,6 +124,46 @@ const exchangeCode = async ({
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return [accessToken] for Azure OAuth2 code-token exchange
|
||||
* @param param0
|
||||
*/
|
||||
const exchangeCodeAzure = async ({
|
||||
code
|
||||
}: {
|
||||
code: string;
|
||||
}) => {
|
||||
const accessExpiresAt = new Date();
|
||||
let res: ExchangeCodeAzureResponse;
|
||||
try {
|
||||
res = (await axios.post(
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
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}/integrations/azure-key-vault/oauth2/callback`
|
||||
} as any)
|
||||
)).data;
|
||||
|
||||
accessExpiresAt.setSeconds(
|
||||
accessExpiresAt.getSeconds() + res.expires_in
|
||||
);
|
||||
} catch (err: any) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed OAuth2 code-token exchange with Azure');
|
||||
}
|
||||
|
||||
return ({
|
||||
accessToken: res.access_token,
|
||||
refreshToken: res.refresh_token,
|
||||
accessExpiresAt
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
|
||||
* OAuth2 code-token exchange
|
||||
@@ -168,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;
|
||||
@@ -208,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;
|
||||
@@ -260,10 +319,11 @@ 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'
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'application/json'
|
||||
}
|
||||
})
|
||||
).data;
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import axios from 'axios';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { INTEGRATION_HEROKU } from '../variables';
|
||||
import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU } from '../variables';
|
||||
import {
|
||||
CLIENT_SECRET_HEROKU
|
||||
SITE_URL,
|
||||
CLIENT_ID_AZURE,
|
||||
CLIENT_SECRET_AZURE,
|
||||
CLIENT_SECRET_HEROKU
|
||||
} from '../config';
|
||||
import {
|
||||
INTEGRATION_HEROKU_TOKEN_URL
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL
|
||||
} from '../variables';
|
||||
|
||||
interface RefreshTokenAzureResponse {
|
||||
token_type: string;
|
||||
scope: string;
|
||||
expires_in: number;
|
||||
ext_expires_in: 4871;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||
* named [integration]
|
||||
@@ -25,6 +38,11 @@ const exchangeRefresh = async ({
|
||||
let accessToken;
|
||||
try {
|
||||
switch (integration) {
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
accessToken = await exchangeRefreshAzure({
|
||||
refreshToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_HEROKU:
|
||||
accessToken = await exchangeRefreshHeroku({
|
||||
refreshToken
|
||||
@@ -40,6 +58,38 @@ const exchangeRefresh = async ({
|
||||
return accessToken;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||
* Azure integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for Azure
|
||||
* @returns
|
||||
*/
|
||||
const exchangeRefreshAzure = async ({
|
||||
refreshToken
|
||||
}: {
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
try {
|
||||
const res: RefreshTokenAzureResponse = (await axios.post(
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
client_id: CLIENT_ID_AZURE,
|
||||
scope: 'openid offline_access',
|
||||
refresh_token: refreshToken,
|
||||
grant_type: 'refresh_token',
|
||||
client_secret: CLIENT_SECRET_AZURE
|
||||
} as any)
|
||||
)).data;
|
||||
|
||||
return res.access_token;
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to get refresh OAuth2 access token for Azure');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||
* Heroku integration
|
||||
@@ -52,23 +102,23 @@ const exchangeRefreshHeroku = async ({
|
||||
}: {
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
let accessToken;
|
||||
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
|
||||
try {
|
||||
const res = await axios.post(
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_secret: CLIENT_SECRET_HEROKU
|
||||
} as any)
|
||||
);
|
||||
|
||||
let accessToken;
|
||||
try {
|
||||
const res = await axios.post(
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_secret: CLIENT_SECRET_HEROKU
|
||||
} as any)
|
||||
);
|
||||
|
||||
accessToken = res.data.access_token;
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to get new OAuth2 access token for Heroku');
|
||||
throw new Error('Failed to refresh OAuth2 access token for Heroku');
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -43,6 +42,13 @@ const syncSecrets = async ({
|
||||
}) => {
|
||||
try {
|
||||
switch (integration.integration) {
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
await syncSecretsAzureKeyVault({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_HEROKU:
|
||||
await syncSecretsHeroku({
|
||||
integration,
|
||||
@@ -97,7 +103,152 @@ const syncSecrets = async ({
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error("Failed to sync secrets to integration");
|
||||
throw new Error('Failed to sync secrets to integration');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Azure Key Vault with vault URI [integration.app]
|
||||
* @param {Object} obj
|
||||
* @param {IIntegration} obj.integration - integration details
|
||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||
* @param {String} obj.accessToken - access token for Azure Key Vault integration
|
||||
*/
|
||||
const syncSecretsAzureKeyVault = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: any;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
try {
|
||||
|
||||
interface GetAzureKeyVaultSecret {
|
||||
id: string; // secret URI
|
||||
attributes: {
|
||||
enabled: true,
|
||||
created: number;
|
||||
updated: number;
|
||||
recoveryLevel: string;
|
||||
recoverableDays: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface AzureKeyVaultSecret extends GetAzureKeyVaultSecret {
|
||||
key: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all secrets from Azure Key Vault by paginating through URL [url]
|
||||
* @param {String} url - pagination URL to get next set of secrets from Azure Key Vault
|
||||
* @returns
|
||||
*/
|
||||
const paginateAzureKeyVaultSecrets = async (url: string) => {
|
||||
let result: GetAzureKeyVaultSecret[] = [];
|
||||
|
||||
while (url) {
|
||||
const res = await axios.get(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
result = result.concat(res.data.value);
|
||||
url = res.data.nextLink;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const getAzureKeyVaultSecrets = await paginateAzureKeyVaultSecrets(`${integration.app}/secrets?api-version=7.3`);
|
||||
|
||||
let lastSlashIndex: number;
|
||||
const res = (await Promise.all(getAzureKeyVaultSecrets.map(async (getAzureKeyVaultSecret) => {
|
||||
if (!lastSlashIndex) {
|
||||
lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf('/');
|
||||
}
|
||||
|
||||
const azureKeyVaultSecret = await axios.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
return ({
|
||||
...azureKeyVaultSecret.data,
|
||||
key: getAzureKeyVaultSecret.id.substring(lastSlashIndex + 1),
|
||||
});
|
||||
})))
|
||||
.reduce((obj: any, secret: any) => ({
|
||||
...obj,
|
||||
[secret.key]: secret
|
||||
}), {});
|
||||
|
||||
const setSecrets: {
|
||||
key: string;
|
||||
value: string;
|
||||
}[] = [];
|
||||
|
||||
Object.keys(secrets).forEach((key) => {
|
||||
const hyphenatedKey = key.replace(/_/g, '-');
|
||||
if (!(hyphenatedKey in res)) {
|
||||
// case: secret has been created
|
||||
setSecrets.push({
|
||||
key: hyphenatedKey,
|
||||
value: secrets[key]
|
||||
});
|
||||
} else {
|
||||
if (secrets[key] !== res[hyphenatedKey].value) {
|
||||
// case: secret has been updated
|
||||
setSecrets.push({
|
||||
key: hyphenatedKey,
|
||||
value: secrets[key]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const deleteSecrets: AzureKeyVaultSecret[] = [];
|
||||
|
||||
Object.keys(res).forEach((key) => {
|
||||
const underscoredKey = key.replace(/-/g, '_');
|
||||
if (!(underscoredKey in secrets)) {
|
||||
deleteSecrets.push(res[key]);
|
||||
}
|
||||
});
|
||||
|
||||
// Sync/push set secrets
|
||||
if (setSecrets.length > 0) {
|
||||
setSecrets.forEach(async ({ key, value }) => {
|
||||
await axios.put(
|
||||
`${integration.app}/secrets/${key}?api-version=7.3`,
|
||||
{
|
||||
value
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (deleteSecrets.length > 0) {
|
||||
deleteSecrets.forEach(async (secret) => {
|
||||
await axios.delete(`${integration.app}/secrets/${secret.key}?api-version=7.3`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to sync secrets to Azure Key Vault');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -772,8 +923,9 @@ const syncSecretsFlyio = async ({
|
||||
method: "post",
|
||||
url: INTEGRATION_FLYIO_API_URL,
|
||||
headers: {
|
||||
Authorization: "Bearer " + accessToken,
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer ' + accessToken,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept-Encoding': 'application/json'
|
||||
},
|
||||
data: {
|
||||
query: GetSecrets,
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import mongoose, { Schema, model } from 'mongoose';
|
||||
|
||||
const LoginSRPDetailSchema = new Schema(
|
||||
{
|
||||
clientPublicKey: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
serverBInt: { type: mongoose.Schema.Types.Buffer },
|
||||
expireAt: { type: Date }
|
||||
}
|
||||
);
|
||||
|
||||
const LoginSRPDetail = model('LoginSRPDetail', LoginSRPDetailSchema);
|
||||
|
||||
// LoginSRPDetailSchema.index({ "expireAt": 1 }, { expireAfterSeconds: 0 });
|
||||
|
||||
export default LoginSRPDetail;
|
||||
@@ -16,6 +16,7 @@ import UserAction, { IUserAction } from './userAction';
|
||||
import Workspace, { IWorkspace } from './workspace';
|
||||
import ServiceTokenData, { IServiceTokenData } from './serviceTokenData';
|
||||
import APIKeyData, { IAPIKeyData } from './apiKeyData';
|
||||
import LoginSRPDetail, { ILoginSRPDetail } from './loginSRPDetail';
|
||||
|
||||
export {
|
||||
BackupPrivateKey,
|
||||
@@ -53,5 +54,7 @@ export {
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
APIKeyData,
|
||||
IAPIKeyData
|
||||
IAPIKeyData,
|
||||
LoginSRPDetail,
|
||||
ILoginSRPDetail
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Schema, model, Types } from "mongoose";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -25,6 +26,7 @@ export interface IIntegration {
|
||||
| "github"
|
||||
| "render"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "circleci";
|
||||
integrationAuth: Types.ObjectId;
|
||||
}
|
||||
@@ -69,6 +71,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Schema, model, Types } from "mongoose";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -16,7 +17,8 @@ export interface IIntegrationAuth {
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "render"
|
||||
| "flyio"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "circleci";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
@@ -39,6 +41,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import mongoose, { Schema, model, Types } from 'mongoose';
|
||||
|
||||
export interface ILoginSRPDetail {
|
||||
_id: Types.ObjectId;
|
||||
clientPublicKey: string;
|
||||
email: string;
|
||||
serverBInt: mongoose.Schema.Types.Buffer;
|
||||
expireAt: Date;
|
||||
}
|
||||
|
||||
const loginSRPDetailSchema = new Schema<ILoginSRPDetail>(
|
||||
{
|
||||
clientPublicKey: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
serverBInt: { type: mongoose.Schema.Types.Buffer },
|
||||
expireAt: { type: Date }
|
||||
}
|
||||
);
|
||||
|
||||
const LoginSRPDetail = model('LoginSRPDetail', loginSRPDetailSchema);
|
||||
|
||||
export default LoginSRPDetail;
|
||||
@@ -23,6 +23,7 @@ export interface ISecret {
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
secretCommentHash?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
const secretSchema = new Schema<ISecret>(
|
||||
@@ -47,6 +48,11 @@ const secretSchema = new Schema<ISecret>(
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
tags: {
|
||||
ref: 'Tag',
|
||||
type: [Schema.Types.ObjectId],
|
||||
default: []
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
|
||||
export interface ITag {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
slug: string;
|
||||
user: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
}
|
||||
|
||||
const tagSchema = new Schema<ITag>(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
slug: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
lowercase: true,
|
||||
validate: [
|
||||
function (value: any) {
|
||||
return value.indexOf(' ') === -1;
|
||||
},
|
||||
'slug cannot contain spaces'
|
||||
]
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Workspace'
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
tagSchema.index({ slug: 1, workspace: 1 }, { unique: true })
|
||||
tagSchema.index({ workspace: 1 })
|
||||
|
||||
const Tag = model<ITag>('Tag', tagSchema);
|
||||
|
||||
export default Tag;
|
||||
@@ -12,6 +12,7 @@ export interface IUser {
|
||||
salt?: string;
|
||||
verifier?: string;
|
||||
refreshVersion?: number;
|
||||
seenIps: [string];
|
||||
}
|
||||
|
||||
const userSchema = new Schema<IUser>(
|
||||
@@ -54,7 +55,8 @@ const userSchema = new Schema<IUser>(
|
||||
type: Number,
|
||||
default: 0,
|
||||
select: false
|
||||
}
|
||||
},
|
||||
seenIps: [String]
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface IWorkspace {
|
||||
name: string;
|
||||
slug: string;
|
||||
}>;
|
||||
autoCapitalization: boolean;
|
||||
}
|
||||
|
||||
const workspaceSchema = new Schema<IWorkspace>({
|
||||
@@ -15,6 +16,10 @@ const workspaceSchema = new Schema<IWorkspace>({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
autoCapitalization: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Organization',
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ADMIN, MEMBER } from '../../variables';
|
||||
import { body, param } from 'express-validator';
|
||||
import { integrationController } from '../../controllers/v1';
|
||||
|
||||
router.post( // new: add new integration
|
||||
router.post( // new: add new integration for integration auth
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey']
|
||||
@@ -19,7 +19,13 @@ router.post( // new: add new integration
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
location: 'body'
|
||||
}),
|
||||
body('integrationAuthId').exists().trim(),
|
||||
body('integrationAuthId').exists().isString().trim(),
|
||||
body('app').isString().trim(),
|
||||
body('isActive').exists().isBoolean(),
|
||||
body('appId').trim(),
|
||||
body('sourceEnvironment').trim(),
|
||||
body('targetEnvironment').trim(),
|
||||
body('owner').trim(),
|
||||
validateRequest,
|
||||
integrationController.createIntegration
|
||||
);
|
||||
|
||||
@@ -18,6 +18,19 @@ router.get(
|
||||
integrationAuthController.getIntegrationOptions
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:integrationAuthId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
}),
|
||||
param('integrationAuthId'),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuth
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/oauth-token',
|
||||
requireAuth({
|
||||
|
||||
@@ -6,6 +6,7 @@ import secrets from './secrets';
|
||||
import serviceTokenData from './serviceTokenData';
|
||||
import apiKeyData from './apiKeyData';
|
||||
import environment from "./environment"
|
||||
import tags from "./tags"
|
||||
|
||||
export {
|
||||
users,
|
||||
@@ -15,5 +16,6 @@ export {
|
||||
secrets,
|
||||
serviceTokenData,
|
||||
apiKeyData,
|
||||
environment
|
||||
environment,
|
||||
tags
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import express, { Response, Request } from 'express';
|
||||
const router = express.Router();
|
||||
import { body, param } from 'express-validator';
|
||||
import { tagController } from '../../controllers/v2';
|
||||
import {
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest,
|
||||
} from '../../middleware';
|
||||
import { ADMIN, MEMBER } from '../../variables';
|
||||
|
||||
router.get(
|
||||
'/:workspaceId/tags',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt'],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [MEMBER, ADMIN],
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
validateRequest,
|
||||
tagController.getWorkspaceTags
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/tags/:tagId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt'],
|
||||
}),
|
||||
param('tagId').exists().trim(),
|
||||
validateRequest,
|
||||
tagController.deleteWorkspaceTag
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:workspaceId/tags',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt'],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [MEMBER, ADMIN],
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body('name').exists().trim(),
|
||||
body('slug').exists().trim(),
|
||||
validateRequest,
|
||||
tagController.createWorkspaceTag
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -118,4 +118,19 @@ router.delete( // TODO - rewire dashboard to this route
|
||||
workspaceController.deleteWorkspaceMembership
|
||||
);
|
||||
|
||||
|
||||
router.patch(
|
||||
'/:workspaceId/auto-capitalization',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body('autoCapitalization').exists().trim().notEmpty(),
|
||||
validateRequest,
|
||||
workspaceController.toggleAutoCapitalization
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
Integration
|
||||
} from '../models';
|
||||
import {
|
||||
handleOAuthExchangeHelper,
|
||||
syncIntegrationsHelper,
|
||||
@@ -10,7 +6,6 @@ import {
|
||||
setIntegrationAuthRefreshHelper,
|
||||
setIntegrationAuthAccessHelper,
|
||||
} from '../helpers/integration';
|
||||
import { exchangeCode } from '../integrations';
|
||||
|
||||
// should sync stuff be here too? Probably.
|
||||
// TODO: move bot functions to IntegrationService.
|
||||
@@ -26,11 +21,12 @@ class IntegrationService {
|
||||
* - Store integration access and refresh tokens returned from the OAuth2 code-token exchange
|
||||
* - Add placeholder inactive integration
|
||||
* - Create bot sequence for integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.environment - workspace environment
|
||||
* @param {String} obj.integration - name of integration
|
||||
* @param {String} obj.code - code
|
||||
* @param {Object} obj1
|
||||
* @param {String} obj1.workspaceId - id of workspace
|
||||
* @param {String} obj1.environment - workspace environment
|
||||
* @param {String} obj1.integration - name of integration
|
||||
* @param {String} obj1.code - code
|
||||
* @returns {IntegrationAuth} integrationAuth - integration authorization after OAuth2 code-token exchange
|
||||
*/
|
||||
static async handleOAuthExchange({
|
||||
workspaceId,
|
||||
@@ -43,7 +39,7 @@ class IntegrationService {
|
||||
code: string;
|
||||
environment: string;
|
||||
}) {
|
||||
await handleOAuthExchangeHelper({
|
||||
return await handleOAuthExchangeHelper({
|
||||
workspaceId,
|
||||
integration,
|
||||
code,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
const ACTION_LOGIN = 'login';
|
||||
const ACTION_LOGOUT = 'logout';
|
||||
const ACTION_ADD_SECRETS = 'addSecrets';
|
||||
const ACTION_DELETE_SECRETS = 'deleteSecrets';
|
||||
const ACTION_UPDATE_SECRETS = 'updateSecrets';
|
||||
const ACTION_READ_SECRETS = 'readSecrets';
|
||||
|
||||
export {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ENV_SET,
|
||||
} from "./environment";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_OAUTH2,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
@@ -31,6 +33,8 @@ import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from "./organization";
|
||||
import { SECRET_SHARED, SECRET_PERSONAL } from "./secret";
|
||||
import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from "./event";
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
@@ -52,6 +56,7 @@ export {
|
||||
ENV_STAGING,
|
||||
ENV_PROD,
|
||||
ENV_SET,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -61,6 +66,7 @@ export {
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_OAUTH2,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
@@ -73,6 +79,8 @@ export {
|
||||
INTEGRATION_CIRCLECI_API_URL,
|
||||
EVENT_PUSH_SECRETS,
|
||||
EVENT_PULL_SECRETS,
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
CLIENT_ID_AZURE,
|
||||
TENANT_ID_AZURE
|
||||
} from '../config';
|
||||
import {
|
||||
CLIENT_ID_HEROKU,
|
||||
CLIENT_ID_NETLIFY,
|
||||
@@ -6,6 +10,7 @@ import {
|
||||
} from "../config";
|
||||
|
||||
// integrations
|
||||
const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault';
|
||||
const INTEGRATION_HEROKU = "heroku";
|
||||
const INTEGRATION_VERCEL = "vercel";
|
||||
const INTEGRATION_NETLIFY = "netlify";
|
||||
@@ -14,6 +19,7 @@ const INTEGRATION_RENDER = "render";
|
||||
const INTEGRATION_FLYIO = "flyio";
|
||||
const INTEGRATION_CIRCLECI = "circleci";
|
||||
const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -27,7 +33,8 @@ const INTEGRATION_SET = new Set([
|
||||
const INTEGRATION_OAUTH2 = "oauth2";
|
||||
|
||||
// integration oauth endpoints
|
||||
const INTEGRATION_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
|
||||
const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/${TENANT_ID_AZURE}/oauth2/v2.0/token`;
|
||||
const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
|
||||
const INTEGRATION_VERCEL_TOKEN_URL =
|
||||
"https://api.vercel.com/v2/oauth/access_token";
|
||||
const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
|
||||
@@ -43,6 +50,16 @@ const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
|
||||
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
||||
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: 'Azure Key Vault',
|
||||
slug: 'azure-key-vault',
|
||||
image: 'Microsoft Azure.png',
|
||||
isAvailable: false,
|
||||
type: 'oauth',
|
||||
clientId: CLIENT_ID_AZURE,
|
||||
tenantId: TENANT_ID_AZURE,
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: "Heroku",
|
||||
slug: "heroku",
|
||||
@@ -146,6 +163,7 @@ const INTEGRATION_OPTIONS = [
|
||||
];
|
||||
|
||||
export {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -155,6 +173,7 @@ export {
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_OAUTH2,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
|
||||
Reference in New Issue
Block a user