mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 18:27:36 +00:00
Begin refactoring backfilling and preparation operations into setup and start adding encryption metadata to models
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import InfisicalClient from 'infisical-node';
|
import InfisicalClient from 'infisical-node';
|
||||||
|
import { validateEncryptionKey } from '../validation';
|
||||||
|
|
||||||
const client = new InfisicalClient({
|
const client = new InfisicalClient({
|
||||||
token: process.env.INFISICAL_TOKEN!
|
token: process.env.INFISICAL_TOKEN!
|
||||||
@@ -6,7 +7,18 @@ const client = new InfisicalClient({
|
|||||||
|
|
||||||
export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000;
|
export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000;
|
||||||
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue;
|
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue;
|
||||||
export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue;
|
export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue; // TODO: deprecate in favor of INFISICAL_ENCRYPTION_KEY
|
||||||
|
export const getRootEncryptionKey = async (): Promise<string | undefined> => {
|
||||||
|
const encryptionKey = (await client.getSecret('ROOT_ENCRYPTION_KEY')).secretValue;
|
||||||
|
|
||||||
|
if (encryptionKey) {
|
||||||
|
// validate [encryptionKey] to make sure it is in base64 format and 256-bit
|
||||||
|
validateEncryptionKey(encryptionKey);
|
||||||
|
return encryptionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptionKey;
|
||||||
|
}
|
||||||
export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10;
|
export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10;
|
||||||
export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d';
|
export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d';
|
||||||
export const getJwtAuthSecret = async () => (await client.getSecret('JWT_AUTH_SECRET')).secretValue;
|
export const getJwtAuthSecret = async () => (await client.getSecret('JWT_AUTH_SECRET')).secretValue;
|
||||||
|
|||||||
@@ -93,52 +93,8 @@ const markDeletedSecretVersionsHelper = async ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize secret versioning by setting previously unversioned
|
|
||||||
* secrets to version 1 and begin populating secret versions.
|
|
||||||
*/
|
|
||||||
const initSecretVersioningHelper = async () => {
|
|
||||||
await Secret.updateMany(
|
|
||||||
{ version: { $exists: false } },
|
|
||||||
{ $set: { version: 1 } }
|
|
||||||
);
|
|
||||||
|
|
||||||
const unversionedSecrets: ISecret[] = await Secret.aggregate([
|
|
||||||
{
|
|
||||||
$lookup: {
|
|
||||||
from: "secretversions",
|
|
||||||
localField: "_id",
|
|
||||||
foreignField: "secret",
|
|
||||||
as: "versions",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
versions: { $size: 0 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (unversionedSecrets.length > 0) {
|
|
||||||
await addSecretVersionsHelper({
|
|
||||||
secretVersions: unversionedSecrets.map(
|
|
||||||
(s, idx) =>
|
|
||||||
new SecretVersion({
|
|
||||||
...s,
|
|
||||||
secret: s._id,
|
|
||||||
version: s.version ? s.version : 1,
|
|
||||||
isDeleted: false,
|
|
||||||
workspace: s.workspace,
|
|
||||||
environment: s.environment,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
takeSecretSnapshotHelper,
|
takeSecretSnapshotHelper,
|
||||||
addSecretVersionsHelper,
|
addSecretVersionsHelper,
|
||||||
markDeletedSecretVersionsHelper,
|
markDeletedSecretVersionsHelper
|
||||||
initSecretVersioningHelper,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { ISecretVersion } from '../models';
|
|||||||
import {
|
import {
|
||||||
takeSecretSnapshotHelper,
|
takeSecretSnapshotHelper,
|
||||||
addSecretVersionsHelper,
|
addSecretVersionsHelper,
|
||||||
markDeletedSecretVersionsHelper,
|
markDeletedSecretVersionsHelper
|
||||||
initSecretVersioningHelper
|
|
||||||
} from '../helpers/secret';
|
} from '../helpers/secret';
|
||||||
import EELicenseService from './EELicenseService';
|
import EELicenseService from './EELicenseService';
|
||||||
|
|
||||||
@@ -64,15 +63,6 @@ class EESecretService {
|
|||||||
secretIds
|
secretIds
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize secret versioning by setting previously unversioned
|
|
||||||
* secrets to version 1 and begin populating secret versions.
|
|
||||||
*/
|
|
||||||
static async initSecretVersioning() {
|
|
||||||
if (!EELicenseService.isLicenseValid) return;
|
|
||||||
await initSecretVersioningHelper();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EESecretService;
|
export default EESecretService;
|
||||||
+11
-12
@@ -13,10 +13,10 @@ import {
|
|||||||
} from "../models";
|
} from "../models";
|
||||||
import {
|
import {
|
||||||
generateKeyPair,
|
generateKeyPair,
|
||||||
encryptSymmetric,
|
encryptSymmetric128BitHexKeyUTF8,
|
||||||
decryptSymmetric,
|
decryptSymmetric128BitHexKeyUTF8,
|
||||||
decryptAsymmetric,
|
decryptAsymmetric
|
||||||
} from "../utils/crypto";
|
} from '../utils/crypto';
|
||||||
import {
|
import {
|
||||||
SECRET_SHARED,
|
SECRET_SHARED,
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { getEncryptionKey } from "../config";
|
import { getEncryptionKey } from "../config";
|
||||||
import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
import { validateMembership } from "../helpers/membership";
|
|
||||||
import { validateUserClientForWorkspace } from "../helpers/user";
|
import { validateUserClientForWorkspace } from "../helpers/user";
|
||||||
import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount";
|
import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount";
|
||||||
|
|
||||||
@@ -120,7 +119,7 @@ const createBot = async ({
|
|||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
const { publicKey, privateKey } = generateKeyPair();
|
const { publicKey, privateKey } = generateKeyPair();
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: privateKey,
|
plaintext: privateKey,
|
||||||
key: await getEncryptionKey(),
|
key: await getEncryptionKey(),
|
||||||
});
|
});
|
||||||
@@ -161,14 +160,14 @@ const getSecretsHelper = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
secrets.forEach((secret: ISecret) => {
|
secrets.forEach((secret: ISecret) => {
|
||||||
const secretKey = decryptSymmetric({
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: secret.secretKeyIV,
|
iv: secret.secretKeyIV,
|
||||||
tag: secret.secretKeyTag,
|
tag: secret.secretKeyTag,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValue = decryptSymmetric({
|
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretValueCiphertext,
|
ciphertext: secret.secretValueCiphertext,
|
||||||
iv: secret.secretValueIV,
|
iv: secret.secretValueIV,
|
||||||
tag: secret.secretValueTag,
|
tag: secret.secretValueTag,
|
||||||
@@ -202,7 +201,7 @@ const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
|||||||
if (!bot) throw new Error("Failed to find bot");
|
if (!bot) throw new Error("Failed to find bot");
|
||||||
if (!bot.isActive) throw new Error("Bot is not active");
|
if (!bot.isActive) throw new Error("Bot is not active");
|
||||||
|
|
||||||
const privateKeyBot = decryptSymmetric({
|
const privateKeyBot = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: bot.encryptedPrivateKey,
|
ciphertext: bot.encryptedPrivateKey,
|
||||||
iv: bot.iv,
|
iv: bot.iv,
|
||||||
tag: bot.tag,
|
tag: bot.tag,
|
||||||
@@ -234,7 +233,7 @@ const encryptSymmetricHelper = async ({
|
|||||||
plaintext: string;
|
plaintext: string;
|
||||||
}) => {
|
}) => {
|
||||||
const key = await getKey({ workspaceId: workspaceId.toString() });
|
const key = await getKey({ workspaceId: workspaceId.toString() });
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext,
|
plaintext,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
@@ -266,7 +265,7 @@ const decryptSymmetricHelper = async ({
|
|||||||
tag: string;
|
tag: string;
|
||||||
}) => {
|
}) => {
|
||||||
const key = await getKey({ workspaceId: workspaceId.toString() });
|
const key = await getKey({ workspaceId: workspaceId.toString() });
|
||||||
const plaintext = decryptSymmetric({
|
const plaintext = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
@@ -281,5 +280,5 @@ export {
|
|||||||
createBot,
|
createBot,
|
||||||
getSecretsHelper,
|
getSecretsHelper,
|
||||||
encryptSymmetricHelper,
|
encryptSymmetricHelper,
|
||||||
decryptSymmetricHelper,
|
decryptSymmetricHelper
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { EESecretService } from '../ee/services';
|
|
||||||
import { SecretService } from '../services';
|
|
||||||
import { getLogger } from '../utils/logger';
|
import { getLogger } from '../utils/logger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,8 +20,6 @@ const initDatabaseHelper = async ({
|
|||||||
|
|
||||||
(await getLogger("database")).info("Database connection established");
|
(await getLogger("database")).info("Database connection established");
|
||||||
|
|
||||||
await EESecretService.initSecretVersioning();
|
|
||||||
await SecretService.initSecretBlindIndexDataHelper();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
(await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`);
|
(await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ import {
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import {
|
import {
|
||||||
encryptSymmetric,
|
encryptSymmetric128BitHexKeyUTF8,
|
||||||
decryptSymmetric
|
decryptSymmetric128BitHexKeyUTF8
|
||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import { getEncryptionKey } from '../config';
|
import { getEncryptionKey } from '../config';
|
||||||
import { TelemetryService } from '../services';
|
import { TelemetryService } from '../services';
|
||||||
@@ -220,48 +220,6 @@ const validateClientForSecrets = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize secret blind index data by setting previously
|
|
||||||
* un-initialized projects to have secret blind index data
|
|
||||||
* (Ensures that all projects have associated blind index data)
|
|
||||||
*/
|
|
||||||
const initSecretBlindIndexDataHelper = async () => {
|
|
||||||
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace');
|
|
||||||
const workspaceIdsToBlindIndex = await Workspace.distinct('_id', {
|
|
||||||
_id: {
|
|
||||||
$nin: workspaceIdsBlindIndexed
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretBlindIndexDataToInsert = await Promise.all(
|
|
||||||
workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
|
|
||||||
const salt = crypto.randomBytes(16).toString('base64');
|
|
||||||
|
|
||||||
const {
|
|
||||||
ciphertext: encryptedSaltCiphertext,
|
|
||||||
iv: saltIV,
|
|
||||||
tag: saltTag
|
|
||||||
} = encryptSymmetric({
|
|
||||||
plaintext: salt,
|
|
||||||
key: await getEncryptionKey()
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretBlindIndexData = new SecretBlindIndexData({
|
|
||||||
workspace: workspaceToBlindIndex,
|
|
||||||
encryptedSaltCiphertext,
|
|
||||||
saltIV,
|
|
||||||
saltTag
|
|
||||||
})
|
|
||||||
|
|
||||||
return secretBlindIndexData;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (secretBlindIndexDataToInsert.length > 0) {
|
|
||||||
await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create secret blind index data containing encrypted blind index [salt]
|
* Create secret blind index data containing encrypted blind index [salt]
|
||||||
* for workspace with id [workspaceId]
|
* for workspace with id [workspaceId]
|
||||||
@@ -280,7 +238,7 @@ const createSecretBlindIndexDataHelper = async ({
|
|||||||
ciphertext: encryptedSaltCiphertext,
|
ciphertext: encryptedSaltCiphertext,
|
||||||
iv: saltIV,
|
iv: saltIV,
|
||||||
tag: saltTag
|
tag: saltTag
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: salt,
|
plaintext: salt,
|
||||||
key: await getEncryptionKey()
|
key: await getEncryptionKey()
|
||||||
});
|
});
|
||||||
@@ -314,7 +272,7 @@ const getSecretBlindIndexSaltHelper = async ({
|
|||||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||||
|
|
||||||
// decrypt workspace salt
|
// decrypt workspace salt
|
||||||
const salt = decryptSymmetric({
|
const salt = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
iv: secretBlindIndexData.saltIV,
|
iv: secretBlindIndexData.saltIV,
|
||||||
tag: secretBlindIndexData.saltTag,
|
tag: secretBlindIndexData.saltTag,
|
||||||
@@ -376,7 +334,7 @@ const generateSecretBlindIndexHelper = async ({
|
|||||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||||
|
|
||||||
// decrypt workspace salt
|
// decrypt workspace salt
|
||||||
const salt = decryptSymmetric({
|
const salt = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
iv: secretBlindIndexData.saltIV,
|
iv: secretBlindIndexData.saltIV,
|
||||||
tag: secretBlindIndexData.saltTag,
|
tag: secretBlindIndexData.saltTag,
|
||||||
@@ -934,7 +892,6 @@ const deleteSecretHelper = async ({
|
|||||||
export {
|
export {
|
||||||
validateClientForSecret,
|
validateClientForSecret,
|
||||||
validateClientForSecrets,
|
validateClientForSecrets,
|
||||||
initSecretBlindIndexDataHelper,
|
|
||||||
createSecretBlindIndexDataHelper,
|
createSecretBlindIndexDataHelper,
|
||||||
getSecretBlindIndexSaltHelper,
|
getSecretBlindIndexSaltHelper,
|
||||||
generateSecretBlindIndexWithSaltHelper,
|
generateSecretBlindIndexWithSaltHelper,
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_API_KEY
|
AUTH_MODE_API_KEY
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import { encryptSymmetric } from '../utils/crypto';
|
|
||||||
import { SecretService } from '../services';
|
import { SecretService } from '../services';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+3
-18
@@ -4,15 +4,9 @@ dotenv.config();
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { DatabaseService } from './services';
|
import { DatabaseService } from './services';
|
||||||
import { setUpHealthEndpoint } from './services/health';
|
import { setUpHealthEndpoint } from './services/health';
|
||||||
import { initSmtp } from './services/smtp';
|
|
||||||
import { TelemetryService } from './services';
|
import { TelemetryService } from './services';
|
||||||
import { setTransporter } from './helpers/nodemailer';
|
|
||||||
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const { patchRouterParam } = require('./utils/patchAsyncRoutes');
|
|
||||||
|
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import swaggerUi = require('swagger-ui-express');
|
import swaggerUi = require('swagger-ui-express');
|
||||||
@@ -77,22 +71,13 @@ import {
|
|||||||
getSiteURL,
|
getSiteURL,
|
||||||
getSmtpHost
|
getSmtpHost
|
||||||
} from './config';
|
} from './config';
|
||||||
|
import { setup } from './utils/setup';
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
TelemetryService.logTelemetryMessage();
|
TelemetryService.logTelemetryMessage();
|
||||||
setTransporter(await initSmtp());
|
|
||||||
|
|
||||||
await DatabaseService.initDatabase(await getMongoURL());
|
await setup();
|
||||||
if ((await getNodeEnv()) !== 'test') {
|
|
||||||
Sentry.init({
|
|
||||||
dsn: await getSentryDSN(),
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
debug: await getNodeEnv() === 'production' ? false : true,
|
|
||||||
environment: await getNodeEnv()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
patchRouterParam();
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.enable('trust proxy');
|
app.enable('trust proxy');
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -176,7 +161,7 @@ const main = async () => {
|
|||||||
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`)
|
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
await createTestUserForDevelopment();
|
// await createTestUserForDevelopment();
|
||||||
setUpHealthEndpoint(server);
|
setUpHealthEndpoint(server);
|
||||||
|
|
||||||
server.on('close', async () => {
|
server.on('close', async () => {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
export interface IGenerateKeyPairOutput {
|
||||||
|
publicKey: string;
|
||||||
|
privateKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEncryptAsymmetricInput {
|
||||||
|
plaintext: string;
|
||||||
|
publicKey: string;
|
||||||
|
privateKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEncryptAsymmetricOutput {
|
||||||
|
ciphertext: string;
|
||||||
|
nonce: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDecryptAsymmetricInput {
|
||||||
|
ciphertext: string;
|
||||||
|
nonce: string;
|
||||||
|
publicKey: string;
|
||||||
|
privateKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEncryptSymmetricInput {
|
||||||
|
plaintext: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEncryptSymmetricOutput {
|
||||||
|
ciphertext: string;
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDecryptSymmetricInput {
|
||||||
|
ciphertext: string;
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './crypto';
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
export interface IBackupPrivateKey {
|
export interface IBackupPrivateKey {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
@@ -7,6 +12,9 @@ export interface IBackupPrivateKey {
|
|||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
salt: string;
|
salt: string;
|
||||||
|
algorithm: string;
|
||||||
|
keySize: number;
|
||||||
|
keyEncoding: 'base64' | 'utf8';
|
||||||
verifier: string;
|
verifier: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +40,24 @@ const backupPrivateKeySchema = new Schema<IBackupPrivateKey>(
|
|||||||
select: false,
|
select: false,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
algorithm: { // the encryption algorithm used
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keySize: { // the size of the key used in the algorithm
|
||||||
|
type: Number,
|
||||||
|
enum: [256],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
salt: {
|
salt: {
|
||||||
type: String,
|
type: String,
|
||||||
select: false,
|
select: false,
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_HEX,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
export interface IBot {
|
export interface IBot {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
@@ -9,6 +15,9 @@ export interface IBot {
|
|||||||
encryptedPrivateKey: string;
|
encryptedPrivateKey: string;
|
||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
|
algorithm: 'aes-256-gcm';
|
||||||
|
keySize: 256;
|
||||||
|
keyEncoding: 'base64' | 'utf8';
|
||||||
}
|
}
|
||||||
|
|
||||||
const botSchema = new Schema<IBot>(
|
const botSchema = new Schema<IBot>(
|
||||||
@@ -45,6 +54,24 @@ const botSchema = new Schema<IBot>(
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
select: false
|
select: false
|
||||||
|
},
|
||||||
|
algorithm: { // the encryption algorithm used
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keySize: { // the size of the key used in the algorithm
|
||||||
|
type: Number,
|
||||||
|
enum: [256],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
],
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import {
|
|||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
export interface IIntegrationAuth extends Document {
|
export interface IIntegrationAuth extends Document {
|
||||||
@@ -31,6 +34,9 @@ export interface IIntegrationAuth extends Document {
|
|||||||
accessCiphertext?: string;
|
accessCiphertext?: string;
|
||||||
accessIV?: string;
|
accessIV?: string;
|
||||||
accessTag?: string;
|
accessTag?: string;
|
||||||
|
algorithm?: 'aes-256-gcm';
|
||||||
|
keySize?: 256;
|
||||||
|
keyEncoding: 'utf8' | 'base64';
|
||||||
accessExpiresAt?: Date;
|
accessExpiresAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +115,24 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
|||||||
type: Date,
|
type: Date,
|
||||||
select: false,
|
select: false,
|
||||||
},
|
},
|
||||||
|
algorithm: { // the encryption algorithm used
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keySize: { // the size of the key used in the algorithm
|
||||||
|
type: Number,
|
||||||
|
enum: [256],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { Schema, model, Types, Document } from 'mongoose';
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
export interface ISecretBlindIndexData extends Document {
|
export interface ISecretBlindIndexData extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
@@ -6,6 +11,9 @@ export interface ISecretBlindIndexData extends Document {
|
|||||||
encryptedSaltCiphertext: string;
|
encryptedSaltCiphertext: string;
|
||||||
saltIV: string;
|
saltIV: string;
|
||||||
saltTag: string;
|
saltTag: string;
|
||||||
|
algorithm: 'aes-256-gcm';
|
||||||
|
keySize: 256;
|
||||||
|
keyEncoding: 'base64' | 'utf8'
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
||||||
@@ -26,7 +34,26 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
|||||||
saltTag: {
|
saltTag: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
algorithm: {
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keySize: {
|
||||||
|
type: Number,
|
||||||
|
enum: [256],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
],
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// WIP
|
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
ISecret
|
ISecret
|
||||||
@@ -11,7 +10,6 @@ import {
|
|||||||
DeleteSecretParams
|
DeleteSecretParams
|
||||||
} from '../interfaces/services/SecretService';
|
} from '../interfaces/services/SecretService';
|
||||||
import {
|
import {
|
||||||
initSecretBlindIndexDataHelper,
|
|
||||||
createSecretBlindIndexDataHelper,
|
createSecretBlindIndexDataHelper,
|
||||||
getSecretBlindIndexSaltHelper,
|
getSecretBlindIndexSaltHelper,
|
||||||
generateSecretBlindIndexWithSaltHelper,
|
generateSecretBlindIndexWithSaltHelper,
|
||||||
@@ -25,16 +23,6 @@ import {
|
|||||||
|
|
||||||
class SecretService {
|
class SecretService {
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param param0 h
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
|
|
||||||
static async initSecretBlindIndexDataHelper() {
|
|
||||||
return await initSecretBlindIndexDataHelper();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create secret blind index data containing encrypted blind index salt
|
* Create secret blind index data containing encrypted blind index salt
|
||||||
* for workspace with id [workspaceId]
|
* for workspace with id [workspaceId]
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
import nacl from 'tweetnacl';
|
|
||||||
import util from 'tweetnacl-util';
|
|
||||||
import AesGCM from './aes-gcm';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return new base64, NaCl, public-private key pair.
|
|
||||||
* @returns {Object} obj
|
|
||||||
* @returns {String} obj.publicKey - base64, NaCl, public key
|
|
||||||
* @returns {String} obj.privateKey - base64, NaCl, private key
|
|
||||||
*/
|
|
||||||
const generateKeyPair = () => {
|
|
||||||
const pair = nacl.box.keyPair();
|
|
||||||
|
|
||||||
return ({
|
|
||||||
publicKey: util.encodeBase64(pair.publicKey),
|
|
||||||
privateKey: util.encodeBase64(pair.secretKey)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return assymmetrically encrypted [plaintext] using [publicKey] where
|
|
||||||
* [publicKey] likely belongs to the recipient.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.plaintext - plaintext to encrypt
|
|
||||||
* @param {String} obj.publicKey - public key of the recipient
|
|
||||||
* @param {String} obj.privateKey - private key of the sender (current user)
|
|
||||||
* @returns {Object} obj
|
|
||||||
* @returns {String} ciphertext - base64-encoded ciphertext
|
|
||||||
* @returns {String} nonce - base64-encoded nonce
|
|
||||||
*/
|
|
||||||
const encryptAsymmetric = ({
|
|
||||||
plaintext,
|
|
||||||
publicKey,
|
|
||||||
privateKey
|
|
||||||
}: {
|
|
||||||
plaintext: string;
|
|
||||||
publicKey: string;
|
|
||||||
privateKey: string;
|
|
||||||
}) => {
|
|
||||||
const nonce = nacl.randomBytes(24);
|
|
||||||
const ciphertext = nacl.box(
|
|
||||||
util.decodeUTF8(plaintext),
|
|
||||||
nonce,
|
|
||||||
util.decodeBase64(publicKey),
|
|
||||||
util.decodeBase64(privateKey)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ciphertext: util.encodeBase64(ciphertext),
|
|
||||||
nonce: util.encodeBase64(nonce)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return assymmetrically decrypted [ciphertext] using [privateKey] where
|
|
||||||
* [privateKey] likely belongs to the recipient.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
|
||||||
* @param {String} obj.nonce - nonce
|
|
||||||
* @param {String} obj.publicKey - public key of the sender
|
|
||||||
* @param {String} obj.privateKey - private key of the receiver (current user)
|
|
||||||
* @param {String} plaintext - UTF8 plaintext
|
|
||||||
*/
|
|
||||||
const decryptAsymmetric = ({
|
|
||||||
ciphertext,
|
|
||||||
nonce,
|
|
||||||
publicKey,
|
|
||||||
privateKey
|
|
||||||
}: {
|
|
||||||
ciphertext: string;
|
|
||||||
nonce: string;
|
|
||||||
publicKey: string;
|
|
||||||
privateKey: string;
|
|
||||||
}): string => {
|
|
||||||
const plaintext: any = nacl.box.open(
|
|
||||||
util.decodeBase64(ciphertext),
|
|
||||||
util.decodeBase64(nonce),
|
|
||||||
util.decodeBase64(publicKey),
|
|
||||||
util.decodeBase64(privateKey)
|
|
||||||
);
|
|
||||||
|
|
||||||
return util.encodeUTF8(plaintext);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return symmetrically encrypted [plaintext] using [key].
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.plaintext - plaintext to encrypt
|
|
||||||
* @param {String} obj.key - hex key
|
|
||||||
*/
|
|
||||||
const encryptSymmetric = ({
|
|
||||||
plaintext,
|
|
||||||
key
|
|
||||||
}: {
|
|
||||||
plaintext: string;
|
|
||||||
key: string;
|
|
||||||
}) => {
|
|
||||||
const obj = AesGCM.encrypt(plaintext, key);
|
|
||||||
const { ciphertext, iv, tag } = obj;
|
|
||||||
|
|
||||||
return {
|
|
||||||
ciphertext,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return symmetrically decypted [ciphertext] using [iv], [tag],
|
|
||||||
* and [key].
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
|
||||||
* @param {String} obj.iv - iv
|
|
||||||
* @param {String} obj.tag - tag
|
|
||||||
* @param {String} obj.key - hex key
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const decryptSymmetric = ({
|
|
||||||
ciphertext,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
key
|
|
||||||
}: {
|
|
||||||
ciphertext: string;
|
|
||||||
iv: string;
|
|
||||||
tag: string;
|
|
||||||
key: string;
|
|
||||||
}): string => {
|
|
||||||
const plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
|
|
||||||
return plaintext;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
generateKeyPair,
|
|
||||||
encryptAsymmetric,
|
|
||||||
decryptAsymmetric,
|
|
||||||
encryptSymmetric,
|
|
||||||
decryptSymmetric
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
import nacl from 'tweetnacl';
|
||||||
|
import util from 'tweetnacl-util';
|
||||||
|
import {
|
||||||
|
IGenerateKeyPairOutput,
|
||||||
|
IEncryptAsymmetricInput,
|
||||||
|
IEncryptAsymmetricOutput,
|
||||||
|
IDecryptAsymmetricInput,
|
||||||
|
IEncryptSymmetricInput,
|
||||||
|
IEncryptSymmetricOutput,
|
||||||
|
IDecryptSymmetricInput
|
||||||
|
} from '../../interfaces/utils';
|
||||||
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
InternalServerError
|
||||||
|
} from '../errors';
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
BLOCK_SIZE_BYTES_32,
|
||||||
|
BLOCK_SIZE_BYTES_16
|
||||||
|
} from '../../variables';
|
||||||
|
import { validateEncryptionKey } from '../../validation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return new base64, NaCl, public-private key pair.
|
||||||
|
* @returns {Object} obj
|
||||||
|
* @returns {String} obj.publicKey - (base64) NaCl, public key
|
||||||
|
* @returns {String} obj.privateKey - (base64), NaCl, private key
|
||||||
|
*/
|
||||||
|
const generateKeyPair = (): IGenerateKeyPairOutput => {
|
||||||
|
const pair = nacl.box.keyPair();
|
||||||
|
|
||||||
|
return ({
|
||||||
|
publicKey: util.encodeBase64(pair.publicKey),
|
||||||
|
privateKey: util.encodeBase64(pair.secretKey)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return assymmetrically encrypted [plaintext] using [publicKey] where
|
||||||
|
* [publicKey] likely belongs to the recipient.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.plaintext - plaintext to encrypt
|
||||||
|
* @param {String} obj.publicKey - (base64) Nacl public key of the recipient
|
||||||
|
* @param {String} obj.privateKey - (base64) Nacl private key of the sender (current user)
|
||||||
|
* @returns {Object} obj
|
||||||
|
* @returns {String} obj.ciphertext - (base64) ciphertext
|
||||||
|
* @returns {String} obj.nonce - (base64) nonce
|
||||||
|
*/
|
||||||
|
const encryptAsymmetric = ({
|
||||||
|
plaintext,
|
||||||
|
publicKey,
|
||||||
|
privateKey
|
||||||
|
}: IEncryptAsymmetricInput): IEncryptAsymmetricOutput => {
|
||||||
|
const nonce = nacl.randomBytes(24);
|
||||||
|
const ciphertext = nacl.box(
|
||||||
|
util.decodeUTF8(plaintext),
|
||||||
|
nonce,
|
||||||
|
util.decodeBase64(publicKey),
|
||||||
|
util.decodeBase64(privateKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ciphertext: util.encodeBase64(ciphertext),
|
||||||
|
nonce: util.encodeBase64(nonce)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return assymmetrically decrypted [ciphertext] using [privateKey] where
|
||||||
|
* [privateKey] likely belongs to the recipient.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||||
|
* @param {String} obj.nonce - (base64) nonce
|
||||||
|
* @param {String} obj.publicKey - (base64) public key of the sender
|
||||||
|
* @param {String} obj.privateKey - (base64) private key of the receiver (current user)
|
||||||
|
* @returns {String} plaintext - (utf8) plaintext
|
||||||
|
*/
|
||||||
|
const decryptAsymmetric = ({
|
||||||
|
ciphertext,
|
||||||
|
nonce,
|
||||||
|
publicKey,
|
||||||
|
privateKey
|
||||||
|
}: IDecryptAsymmetricInput): string => {
|
||||||
|
const plaintext: Uint8Array | null = nacl.box.open(
|
||||||
|
util.decodeBase64(ciphertext),
|
||||||
|
util.decodeBase64(nonce),
|
||||||
|
util.decodeBase64(publicKey),
|
||||||
|
util.decodeBase64(privateKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (plaintext == null) throw BadRequestError({
|
||||||
|
message: 'Invalid ciphertext or keys'
|
||||||
|
});
|
||||||
|
|
||||||
|
return util.encodeUTF8(plaintext);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return symmetrically encrypted [plaintext] using [key].
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.plaintext - (utf8) plaintext to encrypt
|
||||||
|
* @param {String} obj.key - (base64) 256-bit key
|
||||||
|
* @returns {Object} obj
|
||||||
|
* @returns {String} obj.ciphertext (base64) ciphertext
|
||||||
|
* @returns {String} obj.iv (base64) iv
|
||||||
|
* @returns {String} obj.tag (base64) tag
|
||||||
|
*/
|
||||||
|
const encryptSymmetric = ({
|
||||||
|
plaintext,
|
||||||
|
key
|
||||||
|
}: IEncryptSymmetricInput): IEncryptSymmetricOutput => {
|
||||||
|
validateEncryptionKey(key);
|
||||||
|
|
||||||
|
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_32);
|
||||||
|
const secretKey = crypto.createSecretKey(key, 'base64');
|
||||||
|
const cipher = crypto.createCipheriv(ALGORITHM_AES_256_GCM, secretKey, iv);
|
||||||
|
|
||||||
|
let ciphertext = cipher.update(plaintext, 'utf8', 'base64');
|
||||||
|
ciphertext += cipher.final('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
ciphertext,
|
||||||
|
iv: iv.toString('base64'),
|
||||||
|
tag: cipher.getAuthTag().toString('base64')
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return symmetrically decrypted [ciphertext] using [iv], [tag],
|
||||||
|
* and [key].
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||||
|
* @param {String} obj.iv - (base64) 256-bit iv
|
||||||
|
* @param {String} obj.tag - (base64) tag
|
||||||
|
* @param {String} obj.key - (base64) 256-bit key
|
||||||
|
* @returns {String} cleartext - the deciphered ciphertext
|
||||||
|
*/
|
||||||
|
const decryptSymmetric = ({
|
||||||
|
ciphertext,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
key
|
||||||
|
}: IDecryptSymmetricInput): string => {
|
||||||
|
validateEncryptionKey(key);
|
||||||
|
|
||||||
|
const secretKey = crypto.createSecretKey(key, 'base64');
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv(
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
secretKey,
|
||||||
|
Buffer.from(iv, 'base64')
|
||||||
|
);
|
||||||
|
|
||||||
|
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||||
|
|
||||||
|
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
||||||
|
cleartext += decipher.final('utf8');
|
||||||
|
|
||||||
|
return cleartext;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return symmetrically encrypted [plaintext] using [key].
|
||||||
|
*
|
||||||
|
* NOTE: THIS FUNCTION SHOULD NOT BE USED FOR ALL FUTURE
|
||||||
|
* ENCRYPTION OPERATIONS UNLESS IT TOUCHES OLD FUNCTIONALITY
|
||||||
|
* THAT USES IT. USE encryptSymmetric() instead
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.plaintext - (utf8) plaintext to encrypt
|
||||||
|
* @param {String} obj.key - (base64) 256-bit key
|
||||||
|
* @returns {Object} obj
|
||||||
|
* @returns {String} obj.ciphertext (base64) ciphertext
|
||||||
|
* @returns {String} obj.iv (base64) iv
|
||||||
|
* @returns {String} obj.tag (base64) tag
|
||||||
|
*/
|
||||||
|
const encryptSymmetric128BitHexKeyUTF8 = ({
|
||||||
|
plaintext,
|
||||||
|
key
|
||||||
|
}: IEncryptSymmetricInput) => {
|
||||||
|
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16);
|
||||||
|
const cipher = crypto.createCipheriv(ALGORITHM_AES_256_GCM, key, iv);
|
||||||
|
|
||||||
|
let ciphertext = cipher.update(plaintext, 'utf8', 'base64');
|
||||||
|
ciphertext += cipher.final('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
ciphertext,
|
||||||
|
iv: iv.toString('base64'),
|
||||||
|
tag: cipher.getAuthTag().toString('base64')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return symmetrically decrypted [ciphertext] using [iv], [tag],
|
||||||
|
* and [key].
|
||||||
|
*
|
||||||
|
* NOTE: THIS FUNCTION SHOULD NOT BE USED FOR ALL FUTURE
|
||||||
|
* DECRYPTION OPERATIONS UNLESS IT TOUCHES OLD FUNCTIONALITY
|
||||||
|
* THAT USES IT. USE decryptSymmetric() instead
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||||
|
* @param {String} obj.iv - (base64) 256-bit iv
|
||||||
|
* @param {String} obj.tag - (base64) tag
|
||||||
|
* @param {String} obj.key - (hex) 128-bit key
|
||||||
|
* @returns {String} cleartext - the deciphered ciphertext
|
||||||
|
*/
|
||||||
|
const decryptSymmetric128BitHexKeyUTF8 = ({
|
||||||
|
ciphertext,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
key
|
||||||
|
}: IDecryptSymmetricInput) => {
|
||||||
|
const decipher = crypto.createDecipheriv(
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
key,
|
||||||
|
Buffer.from(iv, 'base64')
|
||||||
|
);
|
||||||
|
|
||||||
|
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||||
|
|
||||||
|
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
||||||
|
cleartext += decipher.final('utf8');
|
||||||
|
|
||||||
|
return cleartext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
generateKeyPair,
|
||||||
|
encryptAsymmetric,
|
||||||
|
decryptAsymmetric,
|
||||||
|
encryptSymmetric,
|
||||||
|
decryptSymmetric,
|
||||||
|
encryptSymmetric128BitHexKeyUTF8,
|
||||||
|
decryptSymmetric128BitHexKeyUTF8
|
||||||
|
};
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
import { encryptSymmetric128BitHexKeyUTF8 } from '../crypto';
|
||||||
|
import { EESecretService } from '../../ee/services';
|
||||||
|
import { SecretVersion } from '../../ee/models';
|
||||||
|
import {
|
||||||
|
Secret,
|
||||||
|
ISecret,
|
||||||
|
SecretBlindIndexData,
|
||||||
|
Workspace,
|
||||||
|
Bot,
|
||||||
|
BackupPrivateKey,
|
||||||
|
IntegrationAuth
|
||||||
|
} from '../../models';
|
||||||
|
import { getEncryptionKey, getRootEncryptionKey } from '../../config';
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const backfillSecretVersions = async () => {
|
||||||
|
await Secret.updateMany(
|
||||||
|
{ version: { $exists: false } },
|
||||||
|
{ $set: { version: 1 } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const unversionedSecrets: ISecret[] = await Secret.aggregate([
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "secretversions",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "secret",
|
||||||
|
as: "versions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
versions: { $size: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (unversionedSecrets.length > 0) {
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: unversionedSecrets.map(
|
||||||
|
(s, idx) =>
|
||||||
|
new SecretVersion({
|
||||||
|
...s,
|
||||||
|
secret: s._id,
|
||||||
|
version: s.version ? s.version : 1,
|
||||||
|
isDeleted: false,
|
||||||
|
workspace: s.workspace,
|
||||||
|
environment: s.environment,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const backfillSecretBlindIndexData = async () => {
|
||||||
|
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace');
|
||||||
|
const workspaceIdsToBlindIndex = await Workspace.distinct('_id', {
|
||||||
|
_id: {
|
||||||
|
$nin: workspaceIdsBlindIndexed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretBlindIndexDataToInsert = await Promise.all(
|
||||||
|
workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
|
||||||
|
const salt = crypto.randomBytes(16).toString('base64');
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSaltCiphertext,
|
||||||
|
iv: saltIV,
|
||||||
|
tag: saltTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: salt,
|
||||||
|
key: await getEncryptionKey()
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretBlindIndexData = new SecretBlindIndexData({
|
||||||
|
workspace: workspaceToBlindIndex,
|
||||||
|
encryptedSaltCiphertext,
|
||||||
|
saltIV,
|
||||||
|
saltTag
|
||||||
|
})
|
||||||
|
|
||||||
|
return secretBlindIndexData;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (secretBlindIndexDataToInsert.length > 0) {
|
||||||
|
await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const backfillEncryptionMetadata = async () => {
|
||||||
|
|
||||||
|
// backfill bot encryption metadata
|
||||||
|
await Bot.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keySize: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keySize: 256,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill secret blind index encryption metadata
|
||||||
|
await SecretBlindIndexData.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keySize: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keySize: 256,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill backup private key encryption metadata
|
||||||
|
await BackupPrivateKey.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keySize: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keySize: 256,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill integration auth encryption metadata
|
||||||
|
await IntegrationAuth.updateMany(
|
||||||
|
{
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: blind indices
|
||||||
|
// TODO: secret versions and snapshots etc.
|
||||||
|
|
||||||
|
// TODO: re-encrypt keys logic
|
||||||
|
// TODO: how do you handle different parts of the software
|
||||||
|
// encrypting under different schemes?
|
||||||
|
|
||||||
|
// const encryptionKey = await getEncryptionKey();
|
||||||
|
// const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
// console.log('rootEncryptionKey: ', rootEncryptionKey);
|
||||||
|
|
||||||
|
// if (encryptionKey && rootEncryptionKey) {
|
||||||
|
// // case: both the old encryption key and new encryption key are present
|
||||||
|
// // -> perform migration if needed
|
||||||
|
// console.log('rootEncryptionKey is defined');
|
||||||
|
|
||||||
|
// const bots = await Bot.find({
|
||||||
|
// algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
// keySize: 256,
|
||||||
|
// keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
// }, 'encryptedPrivateKey iv tag');
|
||||||
|
|
||||||
|
// if (bots.length > 0) {
|
||||||
|
// // TODO: unencrypt and re-encrypt
|
||||||
|
// // TODO: unencrypt and re-encrypt blind-indices
|
||||||
|
// // probably then need to move this function
|
||||||
|
|
||||||
|
// console.log('bots: ', bots);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { DatabaseService } from '../../services';
|
||||||
|
import { setTransporter } from '../../helpers/nodemailer';
|
||||||
|
import { initSmtp } from '../../services/smtp';
|
||||||
|
import { createTestUserForDevelopment } from '../addDevelopmentUser'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { patchRouterParam } = require('../patchAsyncRoutes');
|
||||||
|
import {
|
||||||
|
backfillSecretVersions,
|
||||||
|
backfillSecretBlindIndexData,
|
||||||
|
backfillEncryptionMetadata
|
||||||
|
} from './backfill';
|
||||||
|
import {
|
||||||
|
getNodeEnv,
|
||||||
|
getMongoURL,
|
||||||
|
getSentryDSN
|
||||||
|
} from '../../config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare Infisical upon startup. This includes tasks like:
|
||||||
|
* - Initializing SMTP configuration
|
||||||
|
* - Initializing the database connection
|
||||||
|
* - Initializing Sentry
|
||||||
|
* - Backfilling data
|
||||||
|
*/
|
||||||
|
export const setup = async () => {
|
||||||
|
// initializing SMTP configuration
|
||||||
|
setTransporter(await initSmtp());
|
||||||
|
|
||||||
|
// initializing the database connection
|
||||||
|
await DatabaseService.initDatabase(await getMongoURL());
|
||||||
|
|
||||||
|
// backfilling data
|
||||||
|
await backfillSecretVersions();
|
||||||
|
await backfillSecretBlindIndexData();
|
||||||
|
await backfillEncryptionMetadata();
|
||||||
|
|
||||||
|
// initializing Sentry
|
||||||
|
if ((await getNodeEnv()) !== 'development') {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: await getSentryDSN(),
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
debug: await getNodeEnv() === 'production' ? false : true,
|
||||||
|
environment: await getNodeEnv()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
patchRouterParam();
|
||||||
|
await createTestUserForDevelopment();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { InternalServerError } from "../utils/errors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the encryption key [encryptionKey] is in base64 format and 256-bit
|
||||||
|
* @param {String} encryptionKey - the encryption key to validate
|
||||||
|
*/
|
||||||
|
export const validateEncryptionKey = (encryptionKey: string): Buffer => {
|
||||||
|
|
||||||
|
const keyBuffer = Buffer.from(encryptionKey, 'base64')
|
||||||
|
const decoded = keyBuffer.toString('base64');
|
||||||
|
|
||||||
|
if (decoded !== encryptionKey) throw InternalServerError({
|
||||||
|
message: 'Failed to validate the format of the encryption key. Please check that it is in base64 format.'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (keyBuffer.length !== 32) throw InternalServerError({
|
||||||
|
message: 'Failed to validate that the encryption key is 256-bit. Please check that it is 256-bit.'
|
||||||
|
});
|
||||||
|
|
||||||
|
return keyBuffer;
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './config';
|
||||||
@@ -1,15 +1,6 @@
|
|||||||
const ACTION_LOGIN = 'login';
|
export const ACTION_LOGIN = 'login';
|
||||||
const ACTION_LOGOUT = 'logout';
|
export const ACTION_LOGOUT = 'logout';
|
||||||
const ACTION_ADD_SECRETS = 'addSecrets';
|
export const ACTION_ADD_SECRETS = 'addSecrets';
|
||||||
const ACTION_DELETE_SECRETS = 'deleteSecrets';
|
export const ACTION_DELETE_SECRETS = 'deleteSecrets';
|
||||||
const ACTION_UPDATE_SECRETS = 'updateSecrets';
|
export const ACTION_UPDATE_SECRETS = 'updateSecrets';
|
||||||
const ACTION_READ_SECRETS = 'readSecrets';
|
export const ACTION_READ_SECRETS = 'readSecrets';
|
||||||
|
|
||||||
export {
|
|
||||||
ACTION_LOGIN,
|
|
||||||
ACTION_LOGOUT,
|
|
||||||
ACTION_ADD_SECRETS,
|
|
||||||
ACTION_DELETE_SECRETS,
|
|
||||||
ACTION_UPDATE_SECRETS,
|
|
||||||
ACTION_READ_SECRETS
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
const AUTH_MODE_JWT = 'jwt';
|
export const AUTH_MODE_JWT = 'jwt';
|
||||||
const AUTH_MODE_SERVICE_ACCOUNT = 'serviceAccount';
|
export const AUTH_MODE_SERVICE_ACCOUNT = 'serviceAccount';
|
||||||
const AUTH_MODE_SERVICE_TOKEN = 'serviceToken';
|
export const AUTH_MODE_SERVICE_TOKEN = 'serviceToken';
|
||||||
const AUTH_MODE_API_KEY = 'apiKey'; // TODO: deprecate
|
export const AUTH_MODE_API_KEY = 'apiKey'; // TODO: deprecate
|
||||||
|
|
||||||
export {
|
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const ALGORITHM_AES_256_GCM = 'aes-256-gcm';
|
||||||
|
export const BLOCK_SIZE_BYTES_32 = 32;
|
||||||
|
export const BLOCK_SIZE_BYTES_16 = 16;
|
||||||
|
|
||||||
|
export const ENCODING_SCHEME_UTF8 = 'utf8';
|
||||||
|
export const ENCODING_SCHEME_HEX = 'hex';
|
||||||
|
export const ENCODING_SCHEME_BASE64 = 'base64';
|
||||||
@@ -1,14 +1,6 @@
|
|||||||
// environments
|
// environments
|
||||||
const ENV_DEV = 'dev';
|
export const ENV_DEV = 'dev';
|
||||||
const ENV_TESTING = 'test';
|
export const ENV_TESTING = 'test';
|
||||||
const ENV_STAGING = 'staging';
|
export const ENV_STAGING = 'staging';
|
||||||
const ENV_PROD = 'prod';
|
export const ENV_PROD = 'prod';
|
||||||
const ENV_SET = new Set([ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]);
|
export const ENV_SET = new Set([ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]);
|
||||||
|
|
||||||
export {
|
|
||||||
ENV_DEV,
|
|
||||||
ENV_TESTING,
|
|
||||||
ENV_STAGING,
|
|
||||||
ENV_PROD,
|
|
||||||
ENV_SET
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,2 @@
|
|||||||
const EVENT_PUSH_SECRETS = 'pushSecrets';
|
export const EVENT_PUSH_SECRETS = 'pushSecrets';
|
||||||
const EVENT_PULL_SECRETS = 'pullSecrets';
|
export const EVENT_PULL_SECRETS = 'pullSecrets';
|
||||||
|
|
||||||
export {
|
|
||||||
EVENT_PUSH_SECRETS,
|
|
||||||
EVENT_PULL_SECRETS
|
|
||||||
}
|
|
||||||
+13
-152
@@ -1,152 +1,13 @@
|
|||||||
import {
|
export * from './action';
|
||||||
ENV_DEV,
|
export * from './authentication';
|
||||||
ENV_TESTING,
|
export * from './crypto';
|
||||||
ENV_STAGING,
|
export * from './environment';
|
||||||
ENV_PROD,
|
export * from './event';
|
||||||
ENV_SET,
|
export * from './integration';
|
||||||
} from "./environment";
|
export * from './organization';
|
||||||
import {
|
export * from './permission';
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
export * from './secret';
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
export * from './smtp';
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
export * from './stripe';
|
||||||
INTEGRATION_HEROKU,
|
export * from './token';
|
||||||
INTEGRATION_VERCEL,
|
export * from './user';
|
||||||
INTEGRATION_NETLIFY,
|
|
||||||
INTEGRATION_GITHUB,
|
|
||||||
INTEGRATION_GITLAB,
|
|
||||||
INTEGRATION_RENDER,
|
|
||||||
INTEGRATION_RAILWAY,
|
|
||||||
INTEGRATION_FLYIO,
|
|
||||||
INTEGRATION_CIRCLECI,
|
|
||||||
INTEGRATION_TRAVISCI,
|
|
||||||
INTEGRATION_SUPABASE,
|
|
||||||
INTEGRATION_SET,
|
|
||||||
INTEGRATION_OAUTH2,
|
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
|
||||||
INTEGRATION_GITHUB_TOKEN_URL,
|
|
||||||
INTEGRATION_GITLAB_TOKEN_URL,
|
|
||||||
INTEGRATION_HEROKU_API_URL,
|
|
||||||
INTEGRATION_GITLAB_API_URL,
|
|
||||||
INTEGRATION_VERCEL_API_URL,
|
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
|
||||||
INTEGRATION_RENDER_API_URL,
|
|
||||||
INTEGRATION_RAILWAY_API_URL,
|
|
||||||
INTEGRATION_FLYIO_API_URL,
|
|
||||||
INTEGRATION_CIRCLECI_API_URL,
|
|
||||||
INTEGRATION_TRAVISCI_API_URL,
|
|
||||||
INTEGRATION_SUPABASE_API_URL,
|
|
||||||
getIntegrationOptions
|
|
||||||
} from "./integration";
|
|
||||||
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,
|
|
||||||
ACTION_READ_SECRETS
|
|
||||||
} from './action';
|
|
||||||
import {
|
|
||||||
SMTP_HOST_SENDGRID,
|
|
||||||
SMTP_HOST_MAILGUN,
|
|
||||||
SMTP_HOST_SOCKETLABS,
|
|
||||||
SMTP_HOST_ZOHOMAIL
|
|
||||||
} from './smtp';
|
|
||||||
import { PLAN_STARTER, PLAN_PRO } from './stripe';
|
|
||||||
import {
|
|
||||||
MFA_METHOD_EMAIL
|
|
||||||
} from './user';
|
|
||||||
import {
|
|
||||||
TOKEN_EMAIL_CONFIRMATION,
|
|
||||||
TOKEN_EMAIL_MFA,
|
|
||||||
TOKEN_EMAIL_ORG_INVITATION,
|
|
||||||
TOKEN_EMAIL_PASSWORD_RESET
|
|
||||||
} from './token';
|
|
||||||
import {
|
|
||||||
PERMISSION_READ_SECRETS,
|
|
||||||
PERMISSION_WRITE_SECRETS
|
|
||||||
} from './permission';
|
|
||||||
import {
|
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY
|
|
||||||
} from './authentication';
|
|
||||||
|
|
||||||
export {
|
|
||||||
OWNER,
|
|
||||||
ADMIN,
|
|
||||||
MEMBER,
|
|
||||||
INVITED,
|
|
||||||
ACCEPTED,
|
|
||||||
SECRET_SHARED,
|
|
||||||
SECRET_PERSONAL,
|
|
||||||
ENV_DEV,
|
|
||||||
ENV_TESTING,
|
|
||||||
ENV_STAGING,
|
|
||||||
ENV_PROD,
|
|
||||||
ENV_SET,
|
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
|
||||||
INTEGRATION_HEROKU,
|
|
||||||
INTEGRATION_VERCEL,
|
|
||||||
INTEGRATION_NETLIFY,
|
|
||||||
INTEGRATION_GITHUB,
|
|
||||||
INTEGRATION_GITLAB,
|
|
||||||
INTEGRATION_RENDER,
|
|
||||||
INTEGRATION_RAILWAY,
|
|
||||||
INTEGRATION_FLYIO,
|
|
||||||
INTEGRATION_CIRCLECI,
|
|
||||||
INTEGRATION_TRAVISCI,
|
|
||||||
INTEGRATION_SUPABASE,
|
|
||||||
INTEGRATION_SET,
|
|
||||||
INTEGRATION_OAUTH2,
|
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
|
||||||
INTEGRATION_GITHUB_TOKEN_URL,
|
|
||||||
INTEGRATION_GITLAB_TOKEN_URL,
|
|
||||||
INTEGRATION_HEROKU_API_URL,
|
|
||||||
INTEGRATION_GITLAB_API_URL,
|
|
||||||
INTEGRATION_VERCEL_API_URL,
|
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
|
||||||
INTEGRATION_RENDER_API_URL,
|
|
||||||
INTEGRATION_RAILWAY_API_URL,
|
|
||||||
INTEGRATION_FLYIO_API_URL,
|
|
||||||
INTEGRATION_CIRCLECI_API_URL,
|
|
||||||
INTEGRATION_TRAVISCI_API_URL,
|
|
||||||
INTEGRATION_SUPABASE_API_URL,
|
|
||||||
EVENT_PUSH_SECRETS,
|
|
||||||
EVENT_PULL_SECRETS,
|
|
||||||
ACTION_LOGIN,
|
|
||||||
ACTION_LOGOUT,
|
|
||||||
ACTION_ADD_SECRETS,
|
|
||||||
ACTION_UPDATE_SECRETS,
|
|
||||||
ACTION_DELETE_SECRETS,
|
|
||||||
ACTION_READ_SECRETS,
|
|
||||||
PERMISSION_READ_SECRETS,
|
|
||||||
PERMISSION_WRITE_SECRETS,
|
|
||||||
getIntegrationOptions,
|
|
||||||
SMTP_HOST_SENDGRID,
|
|
||||||
SMTP_HOST_MAILGUN,
|
|
||||||
SMTP_HOST_SOCKETLABS,
|
|
||||||
SMTP_HOST_ZOHOMAIL,
|
|
||||||
PLAN_STARTER,
|
|
||||||
PLAN_PRO,
|
|
||||||
MFA_METHOD_EMAIL,
|
|
||||||
TOKEN_EMAIL_CONFIRMATION,
|
|
||||||
TOKEN_EMAIL_MFA,
|
|
||||||
TOKEN_EMAIL_ORG_INVITATION,
|
|
||||||
TOKEN_EMAIL_PASSWORD_RESET,
|
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY
|
|
||||||
};
|
|
||||||
@@ -8,21 +8,21 @@ import {
|
|||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
// integrations
|
// integrations
|
||||||
const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault';
|
export const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault';
|
||||||
const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store';
|
export const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store';
|
||||||
const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager';
|
export const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager';
|
||||||
const INTEGRATION_HEROKU = "heroku";
|
export const INTEGRATION_HEROKU = "heroku";
|
||||||
const INTEGRATION_VERCEL = "vercel";
|
export const INTEGRATION_VERCEL = "vercel";
|
||||||
const INTEGRATION_NETLIFY = "netlify";
|
export const INTEGRATION_NETLIFY = "netlify";
|
||||||
const INTEGRATION_GITHUB = "github";
|
export const INTEGRATION_GITHUB = "github";
|
||||||
const INTEGRATION_GITLAB = "gitlab";
|
export const INTEGRATION_GITLAB = "gitlab";
|
||||||
const INTEGRATION_RENDER = "render";
|
export const INTEGRATION_RENDER = "render";
|
||||||
const INTEGRATION_RAILWAY = "railway";
|
export const INTEGRATION_RAILWAY = "railway";
|
||||||
const INTEGRATION_FLYIO = "flyio";
|
export const INTEGRATION_FLYIO = "flyio";
|
||||||
const INTEGRATION_CIRCLECI = "circleci";
|
export const INTEGRATION_CIRCLECI = "circleci";
|
||||||
const INTEGRATION_TRAVISCI = "travisci";
|
export const INTEGRATION_TRAVISCI = "travisci";
|
||||||
const INTEGRATION_SUPABASE = 'supabase';
|
export const INTEGRATION_SUPABASE = 'supabase';
|
||||||
const INTEGRATION_SET = new Set([
|
export const INTEGRATION_SET = new Set([
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
@@ -37,31 +37,31 @@ const INTEGRATION_SET = new Set([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// integration types
|
// integration types
|
||||||
const INTEGRATION_OAUTH2 = "oauth2";
|
export const INTEGRATION_OAUTH2 = "oauth2";
|
||||||
|
|
||||||
// integration oauth endpoints
|
// integration oauth endpoints
|
||||||
const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/common/oauth2/v2.0/token`;
|
export const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/common/oauth2/v2.0/token`;
|
||||||
const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
|
export const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
|
||||||
const INTEGRATION_VERCEL_TOKEN_URL =
|
export const INTEGRATION_VERCEL_TOKEN_URL =
|
||||||
"https://api.vercel.com/v2/oauth/access_token";
|
"https://api.vercel.com/v2/oauth/access_token";
|
||||||
const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
|
export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
|
||||||
const INTEGRATION_GITHUB_TOKEN_URL =
|
export const INTEGRATION_GITHUB_TOKEN_URL =
|
||||||
"https://github.com/login/oauth/access_token";
|
"https://github.com/login/oauth/access_token";
|
||||||
const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
|
export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
|
||||||
|
|
||||||
// integration apps endpoints
|
// integration apps endpoints
|
||||||
const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
||||||
const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
|
export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
|
||||||
const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
||||||
const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
||||||
const INTEGRATION_RENDER_API_URL = "https://api.render.com";
|
export const INTEGRATION_RENDER_API_URL = "https://api.render.com";
|
||||||
const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2";
|
export const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2";
|
||||||
const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
|
export const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
|
||||||
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
export const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
||||||
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||||
const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com';
|
export const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com';
|
||||||
|
|
||||||
const getIntegrationOptions = async () => {
|
export const getIntegrationOptions = async () => {
|
||||||
const INTEGRATION_OPTIONS = [
|
const INTEGRATION_OPTIONS = [
|
||||||
{
|
{
|
||||||
name: 'Heroku',
|
name: 'Heroku',
|
||||||
@@ -203,40 +203,3 @@ const getIntegrationOptions = async () => {
|
|||||||
|
|
||||||
return INTEGRATION_OPTIONS;
|
return INTEGRATION_OPTIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export {
|
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
|
||||||
INTEGRATION_HEROKU,
|
|
||||||
INTEGRATION_VERCEL,
|
|
||||||
INTEGRATION_NETLIFY,
|
|
||||||
INTEGRATION_GITHUB,
|
|
||||||
INTEGRATION_GITLAB,
|
|
||||||
INTEGRATION_RENDER,
|
|
||||||
INTEGRATION_RAILWAY,
|
|
||||||
INTEGRATION_FLYIO,
|
|
||||||
INTEGRATION_CIRCLECI,
|
|
||||||
INTEGRATION_TRAVISCI,
|
|
||||||
INTEGRATION_SUPABASE,
|
|
||||||
INTEGRATION_SET,
|
|
||||||
INTEGRATION_OAUTH2,
|
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
|
||||||
INTEGRATION_GITHUB_TOKEN_URL,
|
|
||||||
INTEGRATION_GITLAB_API_URL,
|
|
||||||
INTEGRATION_HEROKU_API_URL,
|
|
||||||
INTEGRATION_GITLAB_TOKEN_URL,
|
|
||||||
INTEGRATION_VERCEL_API_URL,
|
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
|
||||||
INTEGRATION_RENDER_API_URL,
|
|
||||||
INTEGRATION_RAILWAY_API_URL,
|
|
||||||
INTEGRATION_FLYIO_API_URL,
|
|
||||||
INTEGRATION_CIRCLECI_API_URL,
|
|
||||||
INTEGRATION_TRAVISCI_API_URL,
|
|
||||||
INTEGRATION_SUPABASE_API_URL,
|
|
||||||
getIntegrationOptions
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
// membership roles
|
// membership roles
|
||||||
const OWNER = "owner";
|
export const OWNER = "owner";
|
||||||
const ADMIN = "admin";
|
export const ADMIN = "admin";
|
||||||
const MEMBER = "member";
|
export const MEMBER = "member";
|
||||||
|
|
||||||
// membership statuses
|
// membership statuses
|
||||||
const INVITED = "invited";
|
export const INVITED = "invited";
|
||||||
|
|
||||||
// -- organization
|
// -- organization
|
||||||
const ACCEPTED = "accepted";
|
export const ACCEPTED = "accepted";
|
||||||
|
|
||||||
export { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED };
|
|
||||||
@@ -1,7 +1,2 @@
|
|||||||
const PERMISSION_READ_SECRETS = 'read';
|
export const PERMISSION_READ_SECRETS = 'read';
|
||||||
const PERMISSION_WRITE_SECRETS = 'write';
|
export const PERMISSION_WRITE_SECRETS = 'write';
|
||||||
|
|
||||||
export {
|
|
||||||
PERMISSION_READ_SECRETS,
|
|
||||||
PERMISSION_WRITE_SECRETS
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
// secrets
|
// secrets
|
||||||
const SECRET_SHARED = 'shared';
|
export const SECRET_SHARED = 'shared';
|
||||||
const SECRET_PERSONAL = 'personal';
|
export const SECRET_PERSONAL = 'personal';
|
||||||
|
|
||||||
export {
|
|
||||||
SECRET_SHARED,
|
|
||||||
SECRET_PERSONAL
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net';
|
export const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net';
|
||||||
const SMTP_HOST_MAILGUN = 'smtp.mailgun.org';
|
export const SMTP_HOST_MAILGUN = 'smtp.mailgun.org';
|
||||||
const SMTP_HOST_SOCKETLABS = 'smtp.socketlabs.com';
|
export const SMTP_HOST_SOCKETLABS = 'smtp.socketlabs.com';
|
||||||
const SMTP_HOST_ZOHOMAIL = 'smtp.zoho.com';
|
export const SMTP_HOST_ZOHOMAIL = 'smtp.zoho.com';
|
||||||
|
|
||||||
export {
|
|
||||||
SMTP_HOST_SENDGRID,
|
|
||||||
SMTP_HOST_MAILGUN,
|
|
||||||
SMTP_HOST_SOCKETLABS,
|
|
||||||
SMTP_HOST_ZOHOMAIL
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,2 @@
|
|||||||
const PLAN_STARTER = 'starter';
|
export const PLAN_STARTER = 'starter';
|
||||||
const PLAN_PRO = 'pro';
|
export const PLAN_PRO = 'pro';
|
||||||
|
|
||||||
export {
|
|
||||||
PLAN_STARTER,
|
|
||||||
PLAN_PRO
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
const TOKEN_EMAIL_CONFIRMATION = 'emailConfirmation';
|
export const TOKEN_EMAIL_CONFIRMATION = 'emailConfirmation';
|
||||||
const TOKEN_EMAIL_MFA = 'emailMfa';
|
export const TOKEN_EMAIL_MFA = 'emailMfa';
|
||||||
const TOKEN_EMAIL_ORG_INVITATION = 'organizationInvitation';
|
export const TOKEN_EMAIL_ORG_INVITATION = 'organizationInvitation';
|
||||||
const TOKEN_EMAIL_PASSWORD_RESET = 'passwordReset';
|
export const TOKEN_EMAIL_PASSWORD_RESET = 'passwordReset';
|
||||||
|
|
||||||
export {
|
|
||||||
TOKEN_EMAIL_CONFIRMATION,
|
|
||||||
TOKEN_EMAIL_MFA,
|
|
||||||
TOKEN_EMAIL_ORG_INVITATION,
|
|
||||||
TOKEN_EMAIL_PASSWORD_RESET
|
|
||||||
}
|
|
||||||
@@ -1,5 +1 @@
|
|||||||
const MFA_METHOD_EMAIL = 'email';
|
export const MFA_METHOD_EMAIL = 'email';
|
||||||
|
|
||||||
export {
|
|
||||||
MFA_METHOD_EMAIL
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,9 @@ const jsrp = require('jsrp');
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
import { plainTextWorkspaceKey, testWorkspaceId } from "../../src/utils/addDevelopmentUser";
|
import { plainTextWorkspaceKey, testWorkspaceId } from "../../src/utils/addDevelopmentUser";
|
||||||
import { encryptSymmetric } from "../../src/utils/crypto";
|
import {
|
||||||
|
encryptSymmetric128BitHexKeyUTF8
|
||||||
|
} from '../../src/utils/crypto';
|
||||||
|
|
||||||
interface TokenData {
|
interface TokenData {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -64,7 +66,7 @@ export const getJWTFromTestUser = (): Promise<TokenData> => {
|
|||||||
export const getServiceTokenFromTestUser = async () => {
|
export const getServiceTokenFromTestUser = async () => {
|
||||||
const loggedInUserDetails = await getJWTFromTestUser()
|
const loggedInUserDetails = await getJWTFromTestUser()
|
||||||
const randomBytes = crypto.randomBytes(16).toString('hex');
|
const randomBytes = crypto.randomBytes(16).toString('hex');
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: plainTextWorkspaceKey,
|
plaintext: plainTextWorkspaceKey,
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user