Fix merge conflicts

This commit is contained in:
Tuan Dang
2023-07-23 22:55:18 +07:00
40 changed files with 795 additions and 52 deletions
@@ -1,6 +1,7 @@
import { Types } from "mongoose";
import { Request, Response } from "express";
import { MembershipOrg, Organization, User } from "../../models";
import { SSOConfig } from "../../ee/models";
import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
import { createToken } from "../../helpers/auth";
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
@@ -110,6 +111,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
}
const plan = await EELicenseService.getPlan(organizationId);
const ssoConfig = await SSOConfig.findOne({
organization: new Types.ObjectId(organizationId)
});
if (ssoConfig && ssoConfig.isActive) {
// case: SAML SSO is enabled for the organization
return res.status(400).send({
message:
"Failed to invite member due to SAML SSO configured for organization"
});
}
if (plan.memberLimit !== null) {
// case: limit imposed on number of members allowed
@@ -4,6 +4,7 @@ import crypto from "crypto";
import bcrypt from "bcrypt";
import {
APIKeyData,
AuthProvider,
MembershipOrg,
TokenVersion,
User
@@ -121,6 +122,10 @@ export const updateAuthProvider = async (req: Request, res: Response) => {
const {
authProvider
} = req.body;
if (req.user?.authProvider === AuthProvider.OKTA_SAML) return res.status(400).send({
message: "Failed to update user authentication method because SAML SSO is enforced"
});
const user = await User.findByIdAndUpdate(
req.user._id.toString(),
@@ -10,6 +10,7 @@ import { getSSOConfigHelper } from "../../helpers/organizations";
import { client } from "../../../config";
import { ResourceNotFoundError } from "../../../utils/errors";
import { getSiteURL } from "../../../config";
import { EELicenseService } from "../../services";
/**
* Redirect user to appropriate SSO endpoint after successful authentication
@@ -58,6 +59,12 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
cert,
audience
} = req.body;
const plan = await EELicenseService.getPlan(organizationId);
if (!plan.samlSSO) return res.status(400).send({
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
});
interface PatchUpdate {
authProvider?: string;
@@ -203,6 +210,12 @@ export const createSSOConfig = async (req: Request, res: Response) => {
cert,
audience
} = req.body;
const plan = await EELicenseService.getPlan(organizationId);
if (!plan.samlSSO) return res.status(400).send({
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
});
const key = await BotOrgService.getSymmetricKey(
new Types.ObjectId(organizationId)
+89 -1
View File
@@ -3,12 +3,100 @@ import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { BotOrg } from "../models";
import { decryptSymmetric128BitHexKeyUTF8 } from "../utils/crypto";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8
} from "../variables";
import { InternalServerError } from "../utils/errors";
import { encryptSymmetric128BitHexKeyUTF8, generateKeyPair } from "../utils/crypto";
// TODO: DOCstrings
/**
* Create a bot with name [name] for organization with id [organizationId]
* @param {Object} obj
* @param {String} obj.name - name of bot
* @param {String} obj.organizationId - id of organization that bot belongs to
*/
export const createBotOrg = async ({
name,
organizationId,
}: {
name: string;
organizationId: Types.ObjectId;
}) => {
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey();
const { publicKey, privateKey } = generateKeyPair();
const key = client.createSymmetricKey();
if (rootEncryptionKey) {
const {
ciphertext: encryptedPrivateKey,
iv: privateKeyIV,
tag: privateKeyTag
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
const {
ciphertext: encryptedSymmetricKey,
iv: symmetricKeyIV,
tag: symmetricKeyTag
} = client.encryptSymmetric(key, rootEncryptionKey);
return await new BotOrg({
name,
organization: organizationId,
publicKey,
encryptedSymmetricKey,
symmetricKeyIV,
symmetricKeyTag,
symmetricKeyAlgorithm: ALGORITHM_AES_256_GCM,
symmetricKeyKeyEncoding: ENCODING_SCHEME_BASE64,
encryptedPrivateKey,
privateKeyIV,
privateKeyTag,
privateKeyAlgorithm: ALGORITHM_AES_256_GCM,
privateKeyKeyEncoding: ENCODING_SCHEME_BASE64
}).save();
} else if (encryptionKey) {
const {
ciphertext: encryptedPrivateKey,
iv: privateKeyIV,
tag: privateKeyTag
} = encryptSymmetric128BitHexKeyUTF8({
plaintext: privateKey,
key: encryptionKey
});
const {
ciphertext: encryptedSymmetricKey,
iv: symmetricKeyIV,
tag: symmetricKeyTag
} = encryptSymmetric128BitHexKeyUTF8({
plaintext: key,
key: encryptionKey
});
return await new BotOrg({
name,
organization: organizationId,
publicKey,
encryptedSymmetricKey,
symmetricKeyIV,
symmetricKeyTag,
symmetricKeyAlgorithm: ALGORITHM_AES_256_GCM,
symmetricKeyKeyEncoding: ENCODING_SCHEME_UTF8,
encryptedPrivateKey,
privateKeyIV,
privateKeyTag,
privateKeyAlgorithm: ALGORITHM_AES_256_GCM,
privateKeyKeyEncoding: ENCODING_SCHEME_UTF8
}).save();
}
throw InternalServerError({
message: "Failed to create new organization bot due to missing encryption key",
});
};
export const getSymmetricKeyHelper = async (organizationId: Types.ObjectId) => {
const rootEncryptionKey = await getRootEncryptionKey();
+10
View File
@@ -14,6 +14,9 @@ import {
licenseKeyRequest,
licenseServerKeyRequest,
} from "../config/request";
import {
createBotOrg
} from "./botOrg";
/**
* Create an organization with name [name]
@@ -29,6 +32,7 @@ export const createOrganization = async ({
name: string;
email: string;
}) => {
const licenseServerKey = await getLicenseServerKey();
let organization;
@@ -52,6 +56,12 @@ export const createOrganization = async ({
}).save();
}
// initialize bot for organization
await createBotOrg({
name,
organizationId: organization._id
});
return organization;
};
+79 -9
View File
@@ -1,6 +1,3 @@
import { Octokit } from "@octokit/rest";
import { IIntegrationAuth } from "../models";
import { standardRequest } from "../config/request";
import {
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
@@ -13,6 +10,8 @@ import {
INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
INTEGRATION_CLOUD_66,
INTEGRATION_CLOUD_66_API_URL,
INTEGRATION_CODEFRESH,
INTEGRATION_CODEFRESH_API_URL,
INTEGRATION_DIGITAL_OCEAN_API_URL,
@@ -39,6 +38,9 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_VERCEL_API_URL
} from "../variables";
import { IIntegrationAuth } from "../models";
import { Octokit } from "@octokit/rest";
import { standardRequest } from "../config/request";
interface App {
name: string;
@@ -165,7 +167,14 @@ const getApps = async ({
});
break;
case INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM:
apps = await getAppsDigitalOceanAppPlatform({ accessToken });
apps = await getAppsDigitalOceanAppPlatform({
accessToken
});
break;
case INTEGRATION_CLOUD_66:
apps = await getAppsCloud66({
accessToken,
});
break;
}
@@ -824,7 +833,6 @@ const getAppsBitBucket = async ({
* @returns {Object[]} apps - names of Supabase apps
* @returns {String} apps.name - name of Supabase app
*/
const getAppsCodefresh = async ({
accessToken,
}: {
@@ -849,9 +857,13 @@ const getAppsCodefresh = async ({
};
/**
* Return list of projects for Digital Ocean App Platform integration
* Return list of applications for DigitalOcean App Platform integration
* @param {Object} obj
* @param {String} obj.accessToken - personal access token for DigitalOcean
* @returns {Object[]} apps - names of DigitalOcean apps
* @returns {String} apps.name - name of DigitalOcean app
* @returns {String} apps.appId - id of DigitalOcean app
*/
const getAppsDigitalOceanAppPlatform = async ({ accessToken }: { accessToken: string }) => {
interface DigitalOceanApp {
id: string;
@@ -879,12 +891,70 @@ const getAppsDigitalOceanAppPlatform = async ({ accessToken }: { accessToken: st
}
})
).data;
return (res.apps ?? []).map((a: DigitalOceanApp) => ({
name: a.spec.name,
appId: a.id
}));
}
/**
* Return list of applications for Cloud66 integration
* @param {Object} obj
* @param {String} obj.accessToken - personal access token for Cloud66 API
* @returns {Object[]} apps - Cloud66 apps
* @returns {String} apps.name - name of Cloud66 app
* @returns {String} apps.appId - uid of Cloud66 app
*/
const getAppsCloud66 = async ({ accessToken }: { accessToken: string }) => {
interface Cloud66Apps {
uid: string;
name: string;
account_id: number;
git: string;
git_branch: string;
environment: string;
cloud: string;
fqdn: string;
language: string;
framework: string;
status: number;
health: number;
last_activity: string;
last_activity_iso: string;
maintenance_mode: boolean;
has_loadbalancer: boolean;
created_at: string;
updated_at: string;
deploy_directory: string;
cloud_status: string;
backend: string;
version: string;
revision: string;
is_busy: boolean;
account_name: string;
is_cluster: boolean;
is_inside_cluster: boolean;
cluster_name: any;
application_address: string;
configstore_namespace: string;
}
const stacks = (
await standardRequest.get(`${INTEGRATION_CLOUD_66_API_URL}/3/stacks`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
})
).data.response as Cloud66Apps[]
const apps = stacks.map((app) => ({
name: app.name,
appId: app.uid
}));
return apps;
};
export { getApps };
+128 -7
View File
@@ -1,14 +1,10 @@
import _ from "lodash";
import AWS from "aws-sdk";
import {
CreateSecretCommand,
GetSecretValueCommand,
ResourceNotFoundException,
SecretsManagerClient,
UpdateSecretCommand,
UpdateSecretCommand
} from "@aws-sdk/client-secrets-manager";
import { Octokit } from "@octokit/rest";
import sodium from "libsodium-wrappers";
import { IIntegration, IIntegrationAuth } from "../models";
import {
INTEGRATION_AWS_PARAMETER_STORE,
@@ -22,6 +18,8 @@ import {
INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
INTEGRATION_CLOUD_66,
INTEGRATION_CLOUD_66_API_URL,
INTEGRATION_CODEFRESH,
INTEGRATION_CODEFRESH_API_URL,
INTEGRATION_DIGITAL_OCEAN_API_URL,
@@ -49,6 +47,10 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_VERCEL_API_URL
} from "../variables";
import AWS from "aws-sdk";
import { Octokit } from "@octokit/rest";
import _ from "lodash";
import sodium from "libsodium-wrappers";
import { standardRequest } from "../config/request";
/**
@@ -229,6 +231,13 @@ const syncSecrets = async ({
accessToken,
});
break;
case INTEGRATION_CLOUD_66:
await syncSecretsCloud66({
integration,
secrets,
accessToken
});
break;
}
};
@@ -2077,10 +2086,11 @@ const syncSecretsBitBucket = async ({
}
}
/*
* Sync/push [secrets] to Codefresh with name [integration.app]
/**
* Sync/push [secrets] to Codefresh project with name [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth 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 Codefresh integration
*/
@@ -2110,6 +2120,14 @@ const syncSecretsCodefresh = async ({
);
};
/**
* Sync/push [secrets] to DigitalOcean App Platform application with name [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth 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 - personal access token for DigitalOcean
*/
const syncSecretsDigitalOceanAppPlatform = async ({
integration,
secrets,
@@ -2134,6 +2152,109 @@ const syncSecretsDigitalOceanAppPlatform = async ({
}
}
);
}
/**
* Sync/push [secrets] to Cloud66 application with name [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth 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 Cloud66 integration
*/
const syncSecretsCloud66 = async ({
integration,
secrets,
accessToken
}: {
integration: IIntegration;
secrets: any;
accessToken: string;
}) => {
interface Cloud66Secret {
id: number;
key: string;
value: string;
readonly: boolean;
created_at: string;
updated_at: string;
is_password: boolean;
is_generated: boolean;
history: any[];
}
// get all current secrets
const res = (
await standardRequest.get(
`${INTEGRATION_CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
)
)
.data
.response
.filter((secret: Cloud66Secret) => !secret.readonly || !secret.is_generated)
.reduce(
(obj: any, secret: any) => ({
...obj,
[secret.key]: secret
}),
{}
);
for await (const key of Object.keys(secrets)) {
if (key in res) {
// update existing secret
await standardRequest.put(
`${INTEGRATION_CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments/${key}`,
{
key,
value: secrets[key]
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
} else {
// create new secret
await standardRequest.post(
`${INTEGRATION_CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments`,
{
key,
value: secrets[key]
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
}
}
for await (const key of Object.keys(res)) {
if (!(key in secrets)) {
// delete secret
await standardRequest.delete(
`${INTEGRATION_CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments/${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
}
}
};
export { syncSecrets };
+5 -2
View File
@@ -1,4 +1,3 @@
import { Schema, Types, model } from "mongoose";
import {
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
@@ -7,6 +6,7 @@ import {
INTEGRATION_CHECKLY,
INTEGRATION_CIRCLECI,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUD_66,
INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
@@ -22,6 +22,7 @@ import {
INTEGRATION_TRAVISCI,
INTEGRATION_VERCEL
} from "../variables";
import { Schema, Types, model } from "mongoose";
export interface IIntegration {
_id: Types.ObjectId;
@@ -61,6 +62,7 @@ export interface IIntegration {
| "bitbucket"
| "codefresh"
| "digital-ocean-app-platform"
| "cloud-66"
integrationAuth: Types.ObjectId;
}
@@ -152,7 +154,8 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CODEFRESH
INTEGRATION_CODEFRESH,
INTEGRATION_CLOUD_66,
],
required: true,
},
+6 -3
View File
@@ -1,4 +1,3 @@
import { Document, Schema, Types, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
@@ -9,6 +8,7 @@ import {
INTEGRATION_BITBUCKET,
INTEGRATION_CIRCLECI,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUD_66,
INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
@@ -24,6 +24,7 @@ import {
INTEGRATION_TRAVISCI,
INTEGRATION_VERCEL
} from "../variables";
import { Document, Schema, Types, model } from "mongoose";
export interface IIntegrationAuth extends Document {
_id: Types.ObjectId;
@@ -48,7 +49,8 @@ export interface IIntegrationAuth extends Document {
| "cloudflare-pages"
| "codefresh"
| "digital-ocean-app-platform"
| "bitbucket";
| "bitbucket"
| "cloud-66";
teamId: string;
accountId: string;
url: string;
@@ -96,7 +98,8 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CODEFRESH
INTEGRATION_CODEFRESH,
INTEGRATION_CLOUD_66,
],
required: true,
},
-2
View File
@@ -177,7 +177,6 @@ export const backfillBotOrgs = async () => {
return new BotOrg({
name: "Infisical Bot",
organization: organizationToAddBot,
isActive: false,
publicKey,
encryptedSymmetricKey,
symmetricKeyIV,
@@ -212,7 +211,6 @@ export const backfillBotOrgs = async () => {
return new BotOrg({
name: "Infisical Bot",
organization: organizationToAddBot,
isActive: false,
publicKey,
encryptedSymmetricKey,
symmetricKeyIV,
+13 -1
View File
@@ -30,6 +30,7 @@ export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
export const INTEGRATION_BITBUCKET = "bitbucket";
export const INTEGRATION_CODEFRESH = "codefresh";
export const INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platform";
export const INTEGRATION_CLOUD_66 = "cloud-66";
export const INTEGRATION_SET = new Set([
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
@@ -48,7 +49,8 @@ export const INTEGRATION_SET = new Set([
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CODEFRESH
INTEGRATION_CODEFRESH,
INTEGRATION_CLOUD_66
]);
// integration types
@@ -82,6 +84,7 @@ export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com"
export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org";
export const INTEGRATION_CODEFRESH_API_URL = "https://g.codefresh.io/api";
export const INTEGRATION_DIGITAL_OCEAN_API_URL = "https://api.digitalocean.com";
export const INTEGRATION_CLOUD_66_API_URL = "https://app.cloud66.com/api";
export const getIntegrationOptions = async () => {
const INTEGRATION_OPTIONS = [
@@ -284,6 +287,15 @@ export const getIntegrationOptions = async () => {
clientId: "",
docsLink: "",
},
{
name: "Cloud 66",
slug: "cloud-66",
image: "Cloud 66.png",
isAvailable: true,
type: "pat",
clientId: "",
docsLink: "",
},
]
return INTEGRATION_OPTIONS;