mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 18:29:07 +00:00
Checkpoint finish preliminary support for ROOT_ENCRYPTION_KEY
This commit is contained in:
Generated
+7
-41
@@ -33,7 +33,7 @@
|
|||||||
"express-validator": "^6.14.2",
|
"express-validator": "^6.14.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"helmet": "^5.1.1",
|
"helmet": "^5.1.1",
|
||||||
"infisical-node": "^1.1.3",
|
"infisical-node": "^1.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
@@ -5331,14 +5331,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/clone": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/co": {
|
"node_modules/co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@@ -6904,13 +6896,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/infisical-node": {
|
"node_modules/infisical-node": {
|
||||||
"version": "1.1.3",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.2.1.tgz",
|
||||||
"integrity": "sha512-MLcZQ/zdpCYFRbj50Tn4Qm58wSKPQfKc3xX4I0c3NnFZvMGd50wnoG1jkkNKjKiYU5h7QDpOg0XZSvlU7yuG6g==",
|
"integrity": "sha512-zEB0w5+1O0mv9qc68bq4f9jDjrtwdbqjJebnwodgy8U1XZElDXeMDQgSMCtgYan7JRmVlH6s/LM8X7kUF+67ZA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.3.3",
|
"axios": "^1.3.3",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"node-cache": "^5.1.2",
|
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
"tweetnacl-util": "^0.15.1"
|
"tweetnacl-util": "^0.15.1"
|
||||||
}
|
}
|
||||||
@@ -8404,17 +8395,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
||||||
},
|
},
|
||||||
"node_modules/node-cache": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
|
|
||||||
"dependencies": {
|
|
||||||
"clone": "2.x"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
||||||
@@ -17266,11 +17246,6 @@
|
|||||||
"wrap-ansi": "^7.0.0"
|
"wrap-ansi": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clone": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
|
|
||||||
},
|
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@@ -18461,13 +18436,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"infisical-node": {
|
"infisical-node": {
|
||||||
"version": "1.1.3",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.2.1.tgz",
|
||||||
"integrity": "sha512-MLcZQ/zdpCYFRbj50Tn4Qm58wSKPQfKc3xX4I0c3NnFZvMGd50wnoG1jkkNKjKiYU5h7QDpOg0XZSvlU7yuG6g==",
|
"integrity": "sha512-zEB0w5+1O0mv9qc68bq4f9jDjrtwdbqjJebnwodgy8U1XZElDXeMDQgSMCtgYan7JRmVlH6s/LM8X7kUF+67ZA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"axios": "^1.3.3",
|
"axios": "^1.3.3",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"node-cache": "^5.1.2",
|
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
"tweetnacl-util": "^0.15.1"
|
"tweetnacl-util": "^0.15.1"
|
||||||
}
|
}
|
||||||
@@ -19615,14 +19589,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
||||||
},
|
},
|
||||||
"node-cache": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
|
|
||||||
"requires": {
|
|
||||||
"clone": "2.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node-fetch": {
|
"node-fetch": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"express-validator": "^6.14.2",
|
"express-validator": "^6.14.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"helmet": "^5.1.1",
|
"helmet": "^5.1.1",
|
||||||
"infisical-node": "^1.1.3",
|
"infisical-node": "^1.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
|
|||||||
+10
-12
@@ -1,24 +1,22 @@
|
|||||||
import InfisicalClient from 'infisical-node';
|
import InfisicalClient from 'infisical-node';
|
||||||
import { validateEncryptionKey } from '../validation';
|
|
||||||
|
|
||||||
const client = new InfisicalClient({
|
export const client = new InfisicalClient({
|
||||||
token: process.env.INFISICAL_TOKEN!
|
token: process.env.INFISICAL_TOKEN!
|
||||||
});
|
});
|
||||||
|
|
||||||
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; // 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) {
|
export const getEncryptionKey = async () => {
|
||||||
// validate [encryptionKey] to make sure it is in base64 format and 256-bit
|
const secretValue = (await client.getSecret('ENCRYPTION_KEY')).secretValue;
|
||||||
validateEncryptionKey(encryptionKey);
|
return secretValue === '' ? undefined : secretValue;
|
||||||
return encryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptionKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getRootEncryptionKey = async () => {
|
||||||
|
const secretValue = (await client.getSecret('ROOT_ENCRYPTION_KEY')).secretValue;
|
||||||
|
return secretValue === '' ? undefined : secretValue;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
Bot
|
Bot
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { INTEGRATION_SET, getIntegrationOptions as getIntegrationOptionsFunc } from '../../variables';
|
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, INTEGRATION_SET, getIntegrationOptions as getIntegrationOptionsFunc } from '../../variables';
|
||||||
import { IntegrationService } from '../../services';
|
import { IntegrationService } from '../../services';
|
||||||
import {
|
import {
|
||||||
getApps,
|
getApps,
|
||||||
@@ -129,7 +129,9 @@ export const saveIntegrationAccessToken = async (
|
|||||||
integration
|
integration
|
||||||
}, {
|
}, {
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
integration
|
integration,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}, {
|
}, {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
ACTION_ADD_SECRETS,
|
ACTION_ADD_SECRETS,
|
||||||
ACTION_READ_SECRETS,
|
ACTION_READ_SECRETS,
|
||||||
ACTION_UPDATE_SECRETS,
|
ACTION_UPDATE_SECRETS,
|
||||||
ACTION_DELETE_SECRETS
|
ACTION_DELETE_SECRETS,
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
@@ -81,7 +83,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
path: fullFolderPath,
|
path: fullFolderPath,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
secretBlindIndex
|
secretBlindIndex,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'PATCH':
|
case 'PATCH':
|
||||||
@@ -96,6 +100,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
path: fullFolderPath,
|
path: fullFolderPath,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'DELETE':
|
case 'DELETE':
|
||||||
@@ -196,6 +202,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext: u.secretCommentCiphertext,
|
secretCommentCiphertext: u.secretCommentCiphertext,
|
||||||
secretCommentIV: u.secretCommentIV,
|
secretCommentIV: u.secretCommentIV,
|
||||||
secretCommentTag: u.secretCommentTag,
|
secretCommentTag: u.secretCommentTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
tags: u.tags
|
tags: u.tags
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -444,6 +452,8 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
tags
|
tags
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -490,7 +500,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag
|
secretValueTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -831,6 +843,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
tags,
|
tags,
|
||||||
...((
|
...((
|
||||||
secretCommentCiphertext !== undefined &&
|
secretCommentCiphertext !== undefined &&
|
||||||
@@ -884,6 +898,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext: secretCommentCiphertext ? secretCommentCiphertext : secret.secretCommentCiphertext,
|
secretCommentCiphertext: secretCommentCiphertext ? secretCommentCiphertext : secret.secretCommentCiphertext,
|
||||||
secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
|
secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
|
||||||
secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
|
secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
tags: tags ? tags : secret.tags
|
tags: tags ? tags : secret.tags
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding
|
||||||
} = oldSecretVersion;
|
} = oldSecretVersion;
|
||||||
|
|
||||||
// update secret
|
// update secret
|
||||||
@@ -182,6 +184,8 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
@@ -205,7 +209,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag
|
secretValueTag,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
// take secret snapshot
|
// take secret snapshot
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { Schema, model, Types } from 'mongoose';
|
|||||||
import {
|
import {
|
||||||
SECRET_SHARED,
|
SECRET_SHARED,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
|
|
||||||
export interface ISecretVersion {
|
export interface ISecretVersion {
|
||||||
@@ -20,6 +23,8 @@ export interface ISecretVersion {
|
|||||||
secretValueCiphertext: string;
|
secretValueCiphertext: string;
|
||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
|
algorithm: 'aes-256-gcm';
|
||||||
|
keyEncoding: 'utf8' | 'base64';
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretVersionSchema = new Schema<ISecretVersion>(
|
const secretVersionSchema = new Schema<ISecretVersion>(
|
||||||
@@ -85,7 +90,20 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
|||||||
secretValueTag: {
|
secretValueTag: {
|
||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
|
algorithm: { // the encryption algorithm used
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true
|
timestamps: true
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ const validateAuthMode = ({
|
|||||||
headers: { [key: string]: string | string[] | undefined },
|
headers: { [key: string]: string | string[] | undefined },
|
||||||
acceptedAuthModes: string[]
|
acceptedAuthModes: string[]
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: refactor middleware
|
|
||||||
const apiKey = headers['x-api-key'];
|
const apiKey = headers['x-api-key'];
|
||||||
const authHeader = headers['authorization'];
|
const authHeader = headers['authorization'];
|
||||||
|
|
||||||
|
|||||||
+89
-121
@@ -4,12 +4,7 @@ import {
|
|||||||
BotKey,
|
BotKey,
|
||||||
Secret,
|
Secret,
|
||||||
ISecret,
|
ISecret,
|
||||||
IUser,
|
IUser
|
||||||
User,
|
|
||||||
IServiceAccount,
|
|
||||||
ServiceAccount,
|
|
||||||
IServiceTokenData,
|
|
||||||
ServiceTokenData,
|
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import {
|
import {
|
||||||
generateKeyPair,
|
generateKeyPair,
|
||||||
@@ -19,91 +14,16 @@ import {
|
|||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import {
|
import {
|
||||||
SECRET_SHARED,
|
SECRET_SHARED,
|
||||||
AUTH_MODE_JWT,
|
ALGORITHM_AES_256_GCM,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
ENCODING_SCHEME_UTF8,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
ENCODING_SCHEME_BASE64
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { getEncryptionKey } from "../config";
|
import {
|
||||||
import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
getEncryptionKey,
|
||||||
import { validateUserClientForWorkspace } from "../helpers/user";
|
getRootEncryptionKey,
|
||||||
import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount";
|
client
|
||||||
|
} from "../config";
|
||||||
/**
|
import { InternalServerError } from "../utils/errors";
|
||||||
* Validate authenticated clients for bot with id [botId] based
|
|
||||||
* on any known permissions.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId} obj.botId - id of bot to validate against
|
|
||||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
|
||||||
*/
|
|
||||||
const validateClientForBot = async ({
|
|
||||||
authData,
|
|
||||||
botId,
|
|
||||||
acceptedRoles,
|
|
||||||
}: {
|
|
||||||
authData: {
|
|
||||||
authMode: string;
|
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
|
||||||
};
|
|
||||||
botId: Types.ObjectId;
|
|
||||||
acceptedRoles: Array<"admin" | "member">;
|
|
||||||
}) => {
|
|
||||||
const bot = await Bot.findById(botId);
|
|
||||||
|
|
||||||
if (!bot) throw BotNotFoundError();
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_JWT &&
|
|
||||||
authData.authPayload instanceof User
|
|
||||||
) {
|
|
||||||
await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId: bot.workspace,
|
|
||||||
acceptedRoles,
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
|
||||||
authData.authPayload instanceof ServiceAccount
|
|
||||||
) {
|
|
||||||
await validateServiceAccountClientForWorkspace({
|
|
||||||
serviceAccount: authData.authPayload,
|
|
||||||
workspaceId: bot.workspace,
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
|
||||||
authData.authPayload instanceof ServiceTokenData
|
|
||||||
) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed service token authorization for bot",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_API_KEY &&
|
|
||||||
authData.authPayload instanceof User
|
|
||||||
) {
|
|
||||||
await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId: bot.workspace,
|
|
||||||
acceptedRoles,
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw BotNotFoundError({
|
|
||||||
message: "Failed client authorization for bot",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
||||||
@@ -118,23 +38,52 @@ const createBot = async ({
|
|||||||
name: string;
|
name: string;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
const { publicKey, privateKey } = generateKeyPair();
|
const { publicKey, privateKey } = generateKeyPair();
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
|
||||||
plaintext: privateKey,
|
if (rootEncryptionKey) {
|
||||||
key: await getEncryptionKey(),
|
const {
|
||||||
|
ciphertext,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||||
|
|
||||||
|
return await new Bot({
|
||||||
|
name,
|
||||||
|
workspace: workspaceId,
|
||||||
|
isActive: false,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey: ciphertext,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
} else if (encryptionKey) {
|
||||||
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: privateKey,
|
||||||
|
key: await getEncryptionKey(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return await new Bot({
|
||||||
|
name,
|
||||||
|
workspace: workspaceId,
|
||||||
|
isActive: false,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey: ciphertext,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}).save();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw InternalServerError({
|
||||||
|
message: 'Failed to create new bot due to missing encryption key'
|
||||||
});
|
});
|
||||||
|
|
||||||
const bot = await new Bot({
|
|
||||||
name,
|
|
||||||
workspace: workspaceId,
|
|
||||||
isActive: false,
|
|
||||||
publicKey,
|
|
||||||
encryptedPrivateKey: ciphertext,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,34 +137,54 @@ const getSecretsHelper = async ({
|
|||||||
* @returns {String} key - decrypted workspace key
|
* @returns {String} key - decrypted workspace key
|
||||||
*/
|
*/
|
||||||
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
const botKey = await BotKey.findOne({
|
const botKey = await BotKey.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
}).populate<{ sender: IUser }>("sender", "publicKey");
|
})
|
||||||
|
.populate<{ sender: IUser }>("sender", "publicKey");
|
||||||
|
|
||||||
if (!botKey) throw new Error("Failed to find bot key");
|
if (!botKey) throw new Error("Failed to find bot key");
|
||||||
|
|
||||||
const bot = await Bot.findOne({
|
const bot = await Bot.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
}).select("+encryptedPrivateKey +iv +tag");
|
}).select("+encryptedPrivateKey +iv +tag +algorithm +keyEncoding");
|
||||||
|
|
||||||
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 = decryptSymmetric128BitHexKeyUTF8({
|
if (rootEncryptionKey && bot.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||||
ciphertext: bot.encryptedPrivateKey,
|
// case: encoding scheme is base64
|
||||||
iv: bot.iv,
|
const privateKeyBot = client.decryptSymmetric(bot.encryptedPrivateKey, rootEncryptionKey, bot.iv, bot.tag);
|
||||||
tag: bot.tag,
|
|
||||||
key: await getEncryptionKey(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const key = decryptAsymmetric({
|
return decryptAsymmetric({
|
||||||
ciphertext: botKey.encryptedKey,
|
ciphertext: botKey.encryptedKey,
|
||||||
nonce: botKey.nonce,
|
nonce: botKey.nonce,
|
||||||
publicKey: botKey.sender.publicKey as string,
|
publicKey: botKey.sender.publicKey as string,
|
||||||
privateKey: privateKeyBot,
|
privateKey: privateKeyBot,
|
||||||
});
|
});
|
||||||
|
} else if (encryptionKey && bot.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||||
|
|
||||||
return key;
|
// case: encoding scheme is utf8
|
||||||
|
const privateKeyBot = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: bot.encryptedPrivateKey,
|
||||||
|
iv: bot.iv,
|
||||||
|
tag: bot.tag,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
return decryptAsymmetric({
|
||||||
|
ciphertext: botKey.encryptedKey,
|
||||||
|
nonce: botKey.nonce,
|
||||||
|
publicKey: botKey.sender.publicKey as string,
|
||||||
|
privateKey: privateKeyBot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw InternalServerError({
|
||||||
|
message: "Failed to obtain bot's copy of workspace key needed for bot operations"
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,7 +245,6 @@ const decryptSymmetricHelper = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForBot,
|
|
||||||
createBot,
|
createBot,
|
||||||
getSecretsHelper,
|
getSecretsHelper,
|
||||||
encryptSymmetricHelper,
|
encryptSymmetricHelper,
|
||||||
|
|||||||
@@ -3,40 +3,20 @@ import { Types } from 'mongoose';
|
|||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
Integration,
|
Integration,
|
||||||
IntegrationAuth,
|
IntegrationAuth
|
||||||
IUser,
|
|
||||||
User,
|
|
||||||
IServiceAccount,
|
|
||||||
ServiceAccount,
|
|
||||||
IServiceTokenData,
|
|
||||||
ServiceTokenData
|
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||||
import { BotService } from '../services';
|
import { BotService } from '../services';
|
||||||
import {
|
import {
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
UnauthorizedRequestError,
|
UnauthorizedRequestError,
|
||||||
IntegrationAuthNotFoundError,
|
|
||||||
IntegrationNotFoundError
|
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
import RequestError from '../utils/requestError';
|
import RequestError from '../utils/requestError';
|
||||||
import {
|
|
||||||
validateClientForIntegrationAuth
|
|
||||||
} from '../helpers/integrationAuth';
|
|
||||||
import {
|
|
||||||
validateUserClientForWorkspace
|
|
||||||
} from '../helpers/user';
|
|
||||||
import {
|
|
||||||
validateServiceAccountClientForWorkspace
|
|
||||||
} from '../helpers/serviceAccount';
|
|
||||||
import { IntegrationService } from '../services';
|
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
@@ -45,84 +25,6 @@ interface Update {
|
|||||||
accountId?: string;
|
accountId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate authenticated clients for integration with id [integrationId] based
|
|
||||||
* on any known permissions.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId} obj.integrationId - id of integration to validate against
|
|
||||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
|
||||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
|
||||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateClientForIntegration = async ({
|
|
||||||
authData,
|
|
||||||
integrationId,
|
|
||||||
acceptedRoles
|
|
||||||
}: {
|
|
||||||
authData: {
|
|
||||||
authMode: string;
|
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
|
||||||
};
|
|
||||||
integrationId: Types.ObjectId;
|
|
||||||
acceptedRoles: Array<'admin' | 'member'>;
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const integration = await Integration.findById(integrationId);
|
|
||||||
if (!integration) throw IntegrationNotFoundError();
|
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth
|
|
||||||
.findById(integration.integrationAuth)
|
|
||||||
.select(
|
|
||||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
|
||||||
|
|
||||||
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
|
||||||
integrationAuthId: integrationAuth._id
|
|
||||||
})).accessToken;
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId: integration.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
return ({ integration, accessToken });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
|
||||||
await validateServiceAccountClientForWorkspace({
|
|
||||||
serviceAccount: authData.authPayload,
|
|
||||||
workspaceId: integration.workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
return ({ integration, accessToken });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed service token authorization for integration'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId: integration.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
return ({ integration, accessToken });
|
|
||||||
}
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed client authorization for integration'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform OAuth2 code-token exchange for workspace with id [workspaceId] and integration
|
* Perform OAuth2 code-token exchange for workspace with id [workspaceId] and integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -400,7 +302,9 @@ const setIntegrationAuthRefreshHelper = async ({
|
|||||||
}, {
|
}, {
|
||||||
refreshCiphertext: obj.ciphertext,
|
refreshCiphertext: obj.ciphertext,
|
||||||
refreshIV: obj.iv,
|
refreshIV: obj.iv,
|
||||||
refreshTag: obj.tag
|
refreshTag: obj.tag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}, {
|
}, {
|
||||||
new: true
|
new: true
|
||||||
});
|
});
|
||||||
@@ -461,7 +365,9 @@ const setIntegrationAuthAccessHelper = async ({
|
|||||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||||
accessIV: encryptedAccessTokenObj.iv,
|
accessIV: encryptedAccessTokenObj.iv,
|
||||||
accessTag: encryptedAccessTokenObj.tag,
|
accessTag: encryptedAccessTokenObj.tag,
|
||||||
accessExpiresAt
|
accessExpiresAt,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}, {
|
}, {
|
||||||
new: true
|
new: true
|
||||||
});
|
});
|
||||||
@@ -475,7 +381,6 @@ const setIntegrationAuthAccessHelper = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForIntegration,
|
|
||||||
handleOAuthExchangeHelper,
|
handleOAuthExchangeHelper,
|
||||||
syncIntegrationsHelper,
|
syncIntegrationsHelper,
|
||||||
getIntegrationAuthRefreshHelper,
|
getIntegrationAuthRefreshHelper,
|
||||||
|
|||||||
@@ -2,105 +2,12 @@ import * as Sentry from '@sentry/node';
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Membership,
|
Membership,
|
||||||
Key,
|
Key
|
||||||
IUser,
|
|
||||||
User,
|
|
||||||
IServiceAccount,
|
|
||||||
ServiceAccount,
|
|
||||||
IServiceTokenData,
|
|
||||||
ServiceTokenData
|
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
MembershipNotFoundError,
|
MembershipNotFoundError,
|
||||||
BadRequestError,
|
BadRequestError
|
||||||
UnauthorizedRequestError
|
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
import {
|
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY
|
|
||||||
} from '../variables';
|
|
||||||
import {
|
|
||||||
validateUserClientForWorkspace
|
|
||||||
} from '../helpers/user';
|
|
||||||
import {
|
|
||||||
validateServiceAccountClientForWorkspace
|
|
||||||
} from '../helpers/serviceAccount';
|
|
||||||
import {
|
|
||||||
validateServiceTokenDataClientForWorkspace
|
|
||||||
} from '../helpers/serviceTokenData';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate authenticated clients for membership with id [membershipId] based
|
|
||||||
* on any known permissions.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId} obj.membershipId - id of membership to validate against
|
|
||||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspaceRoles
|
|
||||||
* @returns {Membership} - validated membership
|
|
||||||
*/
|
|
||||||
const validateClientForMembership = async ({
|
|
||||||
authData,
|
|
||||||
membershipId,
|
|
||||||
acceptedRoles
|
|
||||||
}: {
|
|
||||||
authData: {
|
|
||||||
authMode: string;
|
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
|
||||||
};
|
|
||||||
membershipId: Types.ObjectId;
|
|
||||||
acceptedRoles: Array<'admin' | 'member'>;
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const membership = await Membership.findById(membershipId);
|
|
||||||
|
|
||||||
if (!membership) throw MembershipNotFoundError({
|
|
||||||
message: 'Failed to find membership'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId: membership.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
return membership;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
|
||||||
await validateServiceAccountClientForWorkspace({
|
|
||||||
serviceAccount: authData.authPayload,
|
|
||||||
workspaceId: membership.workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
return membership;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: authData.authPayload,
|
|
||||||
workspaceId: new Types.ObjectId(membership.workspace)
|
|
||||||
});
|
|
||||||
|
|
||||||
return membership;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode == AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId: membership.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
return membership;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed client authorization for membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
||||||
@@ -230,7 +137,6 @@ const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForMembership,
|
|
||||||
validateMembership,
|
validateMembership,
|
||||||
addMemberships,
|
addMemberships,
|
||||||
findMembership,
|
findMembership,
|
||||||
|
|||||||
@@ -3,95 +3,12 @@ import {
|
|||||||
MembershipOrg,
|
MembershipOrg,
|
||||||
Workspace,
|
Workspace,
|
||||||
Membership,
|
Membership,
|
||||||
Key,
|
Key
|
||||||
IUser,
|
|
||||||
User,
|
|
||||||
IServiceAccount,
|
|
||||||
ServiceAccount,
|
|
||||||
IServiceTokenData,
|
|
||||||
ServiceTokenData
|
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
MembershipOrgNotFoundError,
|
MembershipOrgNotFoundError,
|
||||||
BadRequestError,
|
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
import {
|
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY
|
|
||||||
} from '../variables';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
|
||||||
* on any known permissions.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId} obj.membershipOrgId - id of organization membership to validate against
|
|
||||||
* @param {Array<'owner' | 'admin' | 'member'>} obj.acceptedRoles - accepted organization roles
|
|
||||||
* @param {MembershipOrg} - validated organization membership
|
|
||||||
*/
|
|
||||||
const validateClientForMembershipOrg = async ({
|
|
||||||
authData,
|
|
||||||
membershipOrgId,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
}: {
|
|
||||||
authData: {
|
|
||||||
authMode: string;
|
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
|
||||||
};
|
|
||||||
membershipOrgId: Types.ObjectId;
|
|
||||||
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
|
||||||
acceptedStatuses: Array<'invited' | 'accepted'>;
|
|
||||||
}) => {
|
|
||||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
|
||||||
|
|
||||||
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
|
||||||
message: 'Failed to find organization membership '
|
|
||||||
});
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
|
||||||
await validateMembershipOrg({
|
|
||||||
userId: authData.authPayload._id,
|
|
||||||
organizationId: membershipOrg.organization,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
|
|
||||||
return membershipOrg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
|
||||||
if (!authData.authPayload.organization.equals(membershipOrg.organization)) throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed service account client authorization for organization membership'
|
|
||||||
});
|
|
||||||
|
|
||||||
return membershipOrg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed service account client authorization for organization membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
|
||||||
await validateMembershipOrg({
|
|
||||||
userId: authData.authPayload._id,
|
|
||||||
organizationId: membershipOrg.organization,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
|
|
||||||
return membershipOrg;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed client authorization for organization membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
||||||
@@ -234,7 +151,6 @@ const deleteMembershipOrg = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForMembershipOrg,
|
|
||||||
validateMembershipOrg,
|
validateMembershipOrg,
|
||||||
findMembershipOrg,
|
findMembershipOrg,
|
||||||
addMembershipsOrg,
|
addMembershipsOrg,
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
|
||||||
IUser,
|
|
||||||
User,
|
|
||||||
IServiceAccount,
|
|
||||||
ServiceAccount,
|
|
||||||
IServiceTokenData,
|
|
||||||
ServiceTokenData,
|
|
||||||
} from "../models";
|
|
||||||
import { Organization, MembershipOrg } from "../models";
|
import { Organization, MembershipOrg } from "../models";
|
||||||
import {
|
import {
|
||||||
ACCEPTED,
|
ACCEPTED
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
OWNER,
|
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import {
|
import {
|
||||||
getStripeSecretKey,
|
getStripeSecretKey,
|
||||||
@@ -23,94 +10,6 @@ import {
|
|||||||
getStripeProductTeam,
|
getStripeProductTeam,
|
||||||
getStripeProductStarter,
|
getStripeProductStarter,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import {
|
|
||||||
UnauthorizedRequestError,
|
|
||||||
OrganizationNotFoundError,
|
|
||||||
} from "../utils/errors";
|
|
||||||
import { validateUserClientForOrganization } from "../helpers/user";
|
|
||||||
import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate accepted clients for organization with id [organizationId]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId} obj.organizationId - id of organization to validate against
|
|
||||||
*/
|
|
||||||
const validateClientForOrganization = async ({
|
|
||||||
authData,
|
|
||||||
organizationId,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses,
|
|
||||||
}: {
|
|
||||||
authData: {
|
|
||||||
authMode: string;
|
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
|
||||||
};
|
|
||||||
organizationId: Types.ObjectId;
|
|
||||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
|
||||||
acceptedStatuses: Array<"invited" | "accepted">;
|
|
||||||
}) => {
|
|
||||||
const organization = await Organization.findById(organizationId);
|
|
||||||
|
|
||||||
if (!organization) {
|
|
||||||
throw OrganizationNotFoundError({
|
|
||||||
message: "Failed to find organization",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_JWT &&
|
|
||||||
authData.authPayload instanceof User
|
|
||||||
) {
|
|
||||||
const membershipOrg = await validateUserClientForOrganization({
|
|
||||||
user: authData.authPayload,
|
|
||||||
organization,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { organization, membershipOrg };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
|
||||||
authData.authPayload instanceof ServiceAccount
|
|
||||||
) {
|
|
||||||
await validateServiceAccountClientForOrganization({
|
|
||||||
serviceAccount: authData.authPayload,
|
|
||||||
organization,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { organization };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
|
||||||
authData.authPayload instanceof ServiceTokenData
|
|
||||||
) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed service token authorization for organization",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
authData.authMode === AUTH_MODE_API_KEY &&
|
|
||||||
authData.authPayload instanceof User
|
|
||||||
) {
|
|
||||||
const membershipOrg = await validateUserClientForOrganization({
|
|
||||||
user: authData.authPayload,
|
|
||||||
organization,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { organization, membershipOrg };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed client authorization for organization",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an organization with name [name]
|
* Create an organization with name [name]
|
||||||
@@ -258,8 +157,7 @@ const updateSubscriptionOrgQuantity = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForOrganization,
|
|
||||||
createOrganization,
|
createOrganization,
|
||||||
initSubscriptionOrg,
|
initSubscriptionOrg,
|
||||||
updateSubscriptionOrgQuantity,
|
updateSubscriptionOrgQuantity
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
ACTION_UPDATE_SECRETS,
|
ACTION_UPDATE_SECRETS,
|
||||||
ACTION_DELETE_SECRETS,
|
ACTION_DELETE_SECRETS,
|
||||||
ACTION_READ_SECRETS,
|
ACTION_READ_SECRETS,
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
@@ -194,6 +196,8 @@ const v1PushSecrets = async ({
|
|||||||
secretValueIV: newSecret.ivValue,
|
secretValueIV: newSecret.ivValue,
|
||||||
secretValueTag: newSecret.tagValue,
|
secretValueTag: newSecret.tagValue,
|
||||||
secretValueHash: newSecret.hashValue,
|
secretValueHash: newSecret.hashValue,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -225,6 +229,8 @@ const v1PushSecrets = async ({
|
|||||||
secretCommentIV: s.ivComment,
|
secretCommentIV: s.ivComment,
|
||||||
secretCommentTag: s.tagComment,
|
secretCommentTag: s.tagComment,
|
||||||
secretCommentHash: s.hashComment,
|
secretCommentHash: s.hashComment,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
};
|
};
|
||||||
|
|
||||||
if (toAdd[idx].type === "personal") {
|
if (toAdd[idx].type === "personal") {
|
||||||
@@ -254,6 +260,8 @@ const v1PushSecrets = async ({
|
|||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretValueHash,
|
secretValueHash,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding
|
||||||
}) =>
|
}) =>
|
||||||
new SecretVersion({
|
new SecretVersion({
|
||||||
secret: _id,
|
secret: _id,
|
||||||
@@ -271,6 +279,8 @@ const v1PushSecrets = async ({
|
|||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretValueHash,
|
secretValueHash,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -467,6 +477,8 @@ const v2PushSecrets = async ({
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
type: toAdd[idx].type,
|
type: toAdd[idx].type,
|
||||||
environment,
|
environment,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
...(toAdd[idx].type === "personal" ? { user: userId } : {}),
|
...(toAdd[idx].type === "personal" ? { user: userId } : {}),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -478,6 +490,8 @@ const v2PushSecrets = async ({
|
|||||||
...secretDocument,
|
...secretDocument,
|
||||||
secret: secretDocument._id,
|
secret: secretDocument._id,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
+76
-211
@@ -7,50 +7,27 @@ import {
|
|||||||
DeleteSecretParams
|
DeleteSecretParams
|
||||||
} from '../interfaces/services/SecretService';
|
} from '../interfaces/services/SecretService';
|
||||||
import {
|
import {
|
||||||
AuthData
|
|
||||||
} from '../interfaces/middleware';
|
|
||||||
import {
|
|
||||||
User,
|
|
||||||
Workspace,
|
|
||||||
ServiceAccount,
|
|
||||||
ServiceTokenData,
|
|
||||||
Secret,
|
Secret,
|
||||||
ISecret,
|
ISecret,
|
||||||
SecretBlindIndexData,
|
SecretBlindIndexData,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { SecretVersion } from '../ee/models';
|
import { SecretVersion } from '../ee/models';
|
||||||
import {
|
|
||||||
validateMembership
|
|
||||||
} from '../helpers/membership';
|
|
||||||
import {
|
|
||||||
validateUserClientForSecret,
|
|
||||||
validateUserClientForSecrets
|
|
||||||
} from '../helpers/user';
|
|
||||||
import {
|
|
||||||
validateServiceTokenDataClientForSecrets,
|
|
||||||
validateServiceTokenDataClientForWorkspace
|
|
||||||
} from '../helpers/serviceTokenData';
|
|
||||||
import {
|
|
||||||
validateServiceAccountClientForSecrets,
|
|
||||||
validateServiceAccountClientForWorkspace
|
|
||||||
} from '../helpers/serviceAccount';
|
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
UnauthorizedRequestError,
|
|
||||||
SecretNotFoundError,
|
SecretNotFoundError,
|
||||||
SecretBlindIndexDataNotFoundError
|
SecretBlindIndexDataNotFoundError,
|
||||||
|
InternalServerError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED,
|
||||||
ACTION_ADD_SECRETS,
|
ACTION_ADD_SECRETS,
|
||||||
ACTION_READ_SECRETS,
|
ACTION_READ_SECRETS,
|
||||||
ACTION_UPDATE_SECRETS,
|
ACTION_UPDATE_SECRETS,
|
||||||
ACTION_DELETE_SECRETS
|
ACTION_DELETE_SECRETS,
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
@@ -58,7 +35,7 @@ import {
|
|||||||
encryptSymmetric128BitHexKeyUTF8,
|
encryptSymmetric128BitHexKeyUTF8,
|
||||||
decryptSymmetric128BitHexKeyUTF8
|
decryptSymmetric128BitHexKeyUTF8
|
||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import { getEncryptionKey } from '../config';
|
import { getEncryptionKey, client, getRootEncryptionKey } from '../config';
|
||||||
import { TelemetryService } from '../services';
|
import { TelemetryService } from '../services';
|
||||||
import {
|
import {
|
||||||
EESecretService,
|
EESecretService,
|
||||||
@@ -69,157 +46,6 @@ import {
|
|||||||
getAuthDataPayloadUserObj
|
getAuthDataPayloadUserObj
|
||||||
} from '../utils/auth';
|
} from '../utils/auth';
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate authenticated clients for secrets with id [secretId] based
|
|
||||||
* on any known permissions.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId} obj.secretId - id of secret to validate against
|
|
||||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
|
||||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateClientForSecret = async ({
|
|
||||||
authData,
|
|
||||||
secretId,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions
|
|
||||||
}: {
|
|
||||||
authData: AuthData;
|
|
||||||
secretId: Types.ObjectId;
|
|
||||||
acceptedRoles: Array<'admin' | 'member'>;
|
|
||||||
requiredPermissions: string[];
|
|
||||||
}) => {
|
|
||||||
const secret = await Secret.findById(secretId);
|
|
||||||
|
|
||||||
if (!secret) throw SecretNotFoundError({
|
|
||||||
message: 'Failed to find secret'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForSecret({
|
|
||||||
user: authData.authPayload,
|
|
||||||
secret,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
|
||||||
await validateServiceAccountClientForWorkspace({
|
|
||||||
serviceAccount: authData.authPayload,
|
|
||||||
workspaceId: secret.workspace,
|
|
||||||
environment: secret.environment,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: authData.authPayload,
|
|
||||||
workspaceId: secret.workspace,
|
|
||||||
environment: secret.environment
|
|
||||||
});
|
|
||||||
|
|
||||||
return secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForSecret({
|
|
||||||
user: authData.authPayload,
|
|
||||||
secret,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed client authorization for secret'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate authenticated clients for secrets with ids [secretIds] based
|
|
||||||
* on any known permissions.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId[]} obj.secretIds - id of workspace to validate against
|
|
||||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
|
||||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
|
||||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateClientForSecrets = async ({
|
|
||||||
authData,
|
|
||||||
secretIds,
|
|
||||||
requiredPermissions
|
|
||||||
}: {
|
|
||||||
authData: AuthData;
|
|
||||||
secretIds: Types.ObjectId[];
|
|
||||||
requiredPermissions: string[];
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
let secrets: ISecret[] = [];
|
|
||||||
|
|
||||||
secrets = await Secret.find({
|
|
||||||
_id: {
|
|
||||||
$in: secretIds
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (secrets.length != secretIds.length) {
|
|
||||||
throw BadRequestError({ message: 'Failed to validate non-existent secrets' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForSecrets({
|
|
||||||
user: authData.authPayload,
|
|
||||||
secrets,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return secrets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
|
||||||
await validateServiceAccountClientForSecrets({
|
|
||||||
serviceAccount: authData.authPayload,
|
|
||||||
secrets,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return secrets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
await validateServiceTokenDataClientForSecrets({
|
|
||||||
serviceTokenData: authData.authPayload,
|
|
||||||
secrets,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return secrets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
|
||||||
await validateUserClientForSecrets({
|
|
||||||
user: authData.authPayload,
|
|
||||||
secrets,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return secrets;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed client authorization for secrets resource'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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]
|
||||||
@@ -231,26 +57,47 @@ const createSecretBlindIndexDataHelper = async ({
|
|||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
// initialize random blind index salt for workspace
|
// initialize random blind index salt for workspace
|
||||||
const salt = crypto.randomBytes(16).toString('base64');
|
const salt = crypto.randomBytes(16).toString('base64');
|
||||||
|
|
||||||
const {
|
const encryptionKey = await getEncryptionKey();
|
||||||
ciphertext: encryptedSaltCiphertext,
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
iv: saltIV,
|
|
||||||
tag: saltTag
|
|
||||||
} = encryptSymmetric128BitHexKeyUTF8({
|
|
||||||
plaintext: salt,
|
|
||||||
key: await getEncryptionKey()
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretBlindIndexData = await new SecretBlindIndexData({
|
if (rootEncryptionKey) {
|
||||||
workspace: workspaceId,
|
const {
|
||||||
encryptedSaltCiphertext,
|
ciphertext: encryptedSaltCiphertext,
|
||||||
saltIV,
|
iv: saltIV,
|
||||||
saltTag
|
tag: saltTag
|
||||||
}).save();
|
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
||||||
|
|
||||||
return secretBlindIndexData;
|
return await new SecretBlindIndexData({
|
||||||
|
workspace: workspaceId,
|
||||||
|
encryptedSaltCiphertext,
|
||||||
|
saltIV,
|
||||||
|
saltTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
}).save();
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSaltCiphertext,
|
||||||
|
iv: saltIV,
|
||||||
|
tag: saltTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: salt,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
return await new SecretBlindIndexData({
|
||||||
|
workspace: workspaceId,
|
||||||
|
encryptedSaltCiphertext,
|
||||||
|
saltIV,
|
||||||
|
saltTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}).save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,22 +111,36 @@ const getSecretBlindIndexSaltHelper = async ({
|
|||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
// check if workspace blind index data exists
|
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
});
|
}).select('+algorithm +keyEncoding');
|
||||||
|
|
||||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||||
|
|
||||||
// decrypt workspace salt
|
if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||||
const salt = decryptSymmetric128BitHexKeyUTF8({
|
return client.decryptSymmetric(
|
||||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
iv: secretBlindIndexData.saltIV,
|
rootEncryptionKey,
|
||||||
tag: secretBlindIndexData.saltTag,
|
secretBlindIndexData.saltIV,
|
||||||
key: await getEncryptionKey()
|
secretBlindIndexData.saltTag
|
||||||
});
|
);
|
||||||
|
} else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||||
|
// decrypt workspace salt
|
||||||
|
return decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
|
iv: secretBlindIndexData.saltIV,
|
||||||
|
tag: secretBlindIndexData.saltTag,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return salt;
|
throw InternalServerError({
|
||||||
|
message: 'Failed to obtain workspace salt needed for secret blind indexing'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -422,7 +283,9 @@ const createSecretHelper = async ({
|
|||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag
|
secretCommentTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
const secretVersion = new SecretVersion({
|
const secretVersion = new SecretVersion({
|
||||||
@@ -439,7 +302,9 @@ const createSecretHelper = async ({
|
|||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag
|
secretValueTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
|
|
||||||
// // (EE) add version for new secret
|
// // (EE) add version for new secret
|
||||||
@@ -729,7 +594,9 @@ const updateSecretHelper = async ({
|
|||||||
secretKeyTag: secret.secretKeyTag,
|
secretKeyTag: secret.secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag
|
secretValueTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) add version for new secret
|
// (EE) add version for new secret
|
||||||
@@ -890,8 +757,6 @@ const deleteSecretHelper = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForSecret,
|
|
||||||
validateClientForSecrets,
|
|
||||||
createSecretBlindIndexDataHelper,
|
createSecretBlindIndexDataHelper,
|
||||||
getSecretBlindIndexSaltHelper,
|
getSecretBlindIndexSaltHelper,
|
||||||
generateSecretBlindIndexWithSaltHelper,
|
generateSecretBlindIndexWithSaltHelper,
|
||||||
|
|||||||
+3
-221
@@ -1,24 +1,8 @@
|
|||||||
import { Types } from 'mongoose';
|
|
||||||
import {
|
import {
|
||||||
IUser,
|
IUser,
|
||||||
ISecret,
|
|
||||||
IServiceAccount,
|
|
||||||
User,
|
User,
|
||||||
Membership,
|
|
||||||
IOrganization,
|
|
||||||
Organization,
|
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { sendMail } from './nodemailer';
|
import { sendMail } from './nodemailer';
|
||||||
import { validateMembership } from './membership';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
|
||||||
import {
|
|
||||||
validateMembershipOrg
|
|
||||||
} from '../helpers/membershipOrg';
|
|
||||||
import {
|
|
||||||
PERMISSION_READ_SECRETS,
|
|
||||||
PERMISSION_WRITE_SECRETS
|
|
||||||
} from '../variables';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a user under email [email]
|
* Initialize a user under email [email]
|
||||||
@@ -26,7 +10,7 @@ import {
|
|||||||
* @param {String} obj.email - email of user to initialize
|
* @param {String} obj.email - email of user to initialize
|
||||||
* @returns {Object} user - the initialized user
|
* @returns {Object} user - the initialized user
|
||||||
*/
|
*/
|
||||||
const setupAccount = async ({ email }: { email: string }) => {
|
export const setupAccount = async ({ email }: { email: string }) => {
|
||||||
const user = await new User({
|
const user = await new User({
|
||||||
email
|
email
|
||||||
}).save();
|
}).save();
|
||||||
@@ -52,7 +36,7 @@ const setupAccount = async ({ email }: { email: string }) => {
|
|||||||
* @param {String} obj.verifier - verifier for auth SRP
|
* @param {String} obj.verifier - verifier for auth SRP
|
||||||
* @returns {Object} user - the completed user
|
* @returns {Object} user - the completed user
|
||||||
*/
|
*/
|
||||||
const completeAccount = async ({
|
export const completeAccount = async ({
|
||||||
userId,
|
userId,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@@ -113,7 +97,7 @@ const completeAccount = async ({
|
|||||||
* @param {String} obj.ip - login ip address
|
* @param {String} obj.ip - login ip address
|
||||||
* @param {String} obj.userAgent - login user-agent
|
* @param {String} obj.userAgent - login user-agent
|
||||||
*/
|
*/
|
||||||
const checkUserDevice = async ({
|
export const checkUserDevice = async ({
|
||||||
user,
|
user,
|
||||||
ip,
|
ip,
|
||||||
userAgent
|
userAgent
|
||||||
@@ -149,205 +133,3 @@ const checkUserDevice = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that user (client) can access workspace
|
|
||||||
* with id [workspaceId] and its environment [environment] with required permissions
|
|
||||||
* [requiredPermissions]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {User} obj.user - user client
|
|
||||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
|
||||||
* @param {String} environment - (optional) environment in workspace to validate against
|
|
||||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateUserClientForWorkspace = async ({
|
|
||||||
user,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions
|
|
||||||
}: {
|
|
||||||
user: IUser;
|
|
||||||
workspaceId: Types.ObjectId;
|
|
||||||
environment?: string;
|
|
||||||
acceptedRoles: Array<'admin' | 'member'>;
|
|
||||||
requiredPermissions?: string[];
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
// validate user membership in workspace
|
|
||||||
const membership = await validateMembership({
|
|
||||||
userId: user._id,
|
|
||||||
workspaceId,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
let runningIsDisallowed = false;
|
|
||||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
|
||||||
switch (requiredPermission) {
|
|
||||||
case PERMISSION_READ_SECRETS:
|
|
||||||
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
|
||||||
break;
|
|
||||||
case PERMISSION_WRITE_SECRETS:
|
|
||||||
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningIsDisallowed) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return membership;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that user (client) can access secret [secret]
|
|
||||||
* with required permissions [requiredPermissions]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {User} obj.user - user client
|
|
||||||
* @param {Secret[]} obj.secrets - secrets to validate against
|
|
||||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateUserClientForSecret = async ({
|
|
||||||
user,
|
|
||||||
secret,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions
|
|
||||||
}: {
|
|
||||||
user: IUser;
|
|
||||||
secret: ISecret;
|
|
||||||
acceptedRoles?: Array<'admin' | 'member'>;
|
|
||||||
requiredPermissions?: string[];
|
|
||||||
}) => {
|
|
||||||
const membership = await validateMembership({
|
|
||||||
userId: user._id,
|
|
||||||
workspaceId: secret.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
|
||||||
const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
|
||||||
|
|
||||||
if (isDisallowed) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'You do not have the required permissions to perform this action'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that user (client) can access secrets [secrets]
|
|
||||||
* with required permissions [requiredPermissions]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {User} obj.user - user client
|
|
||||||
* @param {Secret[]} obj.secrets - secrets to validate against
|
|
||||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateUserClientForSecrets = async ({
|
|
||||||
user,
|
|
||||||
secrets,
|
|
||||||
requiredPermissions
|
|
||||||
}: {
|
|
||||||
user: IUser;
|
|
||||||
secrets: ISecret[];
|
|
||||||
requiredPermissions?: string[];
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
// TODO: add acceptedRoles?
|
|
||||||
|
|
||||||
const userMemberships = await Membership.find({ user: user._id })
|
|
||||||
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
|
||||||
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
|
|
||||||
|
|
||||||
// for each secret check if the secret belongs to a workspace the user is a member of
|
|
||||||
secrets.forEach((secret: ISecret) => {
|
|
||||||
if (!workspaceIdsSet.has(secret.workspace.toString())) {
|
|
||||||
throw BadRequestError({
|
|
||||||
message: 'Failed authorization for the secret'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
|
||||||
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
|
||||||
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
|
||||||
|
|
||||||
if (isDisallowed) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'You do not have the required permissions to perform this action'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that user (client) can access service account [serviceAccount]
|
|
||||||
* with required permissions [requiredPermissions]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {User} obj.user - user client
|
|
||||||
* @param {ServiceAccount} obj.serviceAccount - service account to validate against
|
|
||||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateUserClientForServiceAccount = async ({
|
|
||||||
user,
|
|
||||||
serviceAccount,
|
|
||||||
requiredPermissions
|
|
||||||
}: {
|
|
||||||
user: IUser;
|
|
||||||
serviceAccount: IServiceAccount;
|
|
||||||
requiredPermissions?: string[];
|
|
||||||
}) => {
|
|
||||||
if (!serviceAccount.user.equals(user._id)) {
|
|
||||||
// case: user who created service account is not the
|
|
||||||
// same user that is on the request
|
|
||||||
await validateMembershipOrg({
|
|
||||||
userId: user._id,
|
|
||||||
organizationId: serviceAccount.organization,
|
|
||||||
acceptedRoles: [],
|
|
||||||
acceptedStatuses: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that user (client) can access organization [organization]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {User} obj.user - user client
|
|
||||||
* @param {Organization} obj.organization - organization to validate against
|
|
||||||
*/
|
|
||||||
const validateUserClientForOrganization = async ({
|
|
||||||
user,
|
|
||||||
organization,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
}: {
|
|
||||||
user: IUser;
|
|
||||||
organization: IOrganization;
|
|
||||||
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
|
||||||
acceptedStatuses: Array<'invited' | 'accepted'>;
|
|
||||||
}) => {
|
|
||||||
const membershipOrg = await validateMembershipOrg({
|
|
||||||
userId: user._id,
|
|
||||||
organizationId: organization._id,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
|
|
||||||
return membershipOrg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
setupAccount,
|
|
||||||
completeAccount,
|
|
||||||
checkUserDevice,
|
|
||||||
validateUserClientForWorkspace,
|
|
||||||
validateUserClientForSecrets,
|
|
||||||
validateUserClientForServiceAccount,
|
|
||||||
validateUserClientForOrganization,
|
|
||||||
validateUserClientForSecret
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,135 +1,14 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import crypto from 'crypto';
|
|
||||||
import { Types } from 'mongoose';
|
|
||||||
import {
|
import {
|
||||||
Workspace,
|
Workspace,
|
||||||
Bot,
|
Bot,
|
||||||
Membership,
|
Membership,
|
||||||
Key,
|
Key,
|
||||||
Secret,
|
Secret
|
||||||
User,
|
|
||||||
IUser,
|
|
||||||
ServiceAccountWorkspacePermission,
|
|
||||||
ServiceAccount,
|
|
||||||
IServiceAccount,
|
|
||||||
ServiceTokenData,
|
|
||||||
IServiceTokenData,
|
|
||||||
SecretBlindIndexData
|
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { createBot } from '../helpers/bot';
|
import { createBot } from '../helpers/bot';
|
||||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
|
||||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
|
||||||
import { validateServiceTokenDataClientForWorkspace } from '../helpers/serviceTokenData';
|
|
||||||
import { validateMembership } from '../helpers/membership';
|
|
||||||
import { UnauthorizedRequestError, WorkspaceNotFoundError } from '../utils/errors';
|
|
||||||
import {
|
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
AUTH_MODE_API_KEY
|
|
||||||
} from '../variables';
|
|
||||||
import { SecretService } from '../services';
|
import { SecretService } from '../services';
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
|
||||||
* on any known permissions.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.authData - authenticated client details
|
|
||||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
|
||||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
|
||||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
|
||||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
|
||||||
*/
|
|
||||||
const validateClientForWorkspace = async ({
|
|
||||||
authData,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions,
|
|
||||||
requireBlindIndicesEnabled
|
|
||||||
}: {
|
|
||||||
authData: {
|
|
||||||
authMode: string;
|
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
|
||||||
};
|
|
||||||
workspaceId: Types.ObjectId;
|
|
||||||
environment?: string;
|
|
||||||
acceptedRoles: Array<'admin' | 'member'>;
|
|
||||||
requiredPermissions?: string[];
|
|
||||||
requireBlindIndicesEnabled: boolean;
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const workspace = await Workspace.findById(workspaceId);
|
|
||||||
|
|
||||||
if (!workspace) throw WorkspaceNotFoundError({
|
|
||||||
message: 'Failed to find workspace'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (requireBlindIndicesEnabled) {
|
|
||||||
// case: blind indices are not enabled for secrets in this workspace
|
|
||||||
// (i.e. workspace was created before blind indices were introduced
|
|
||||||
// and no admin has enabled it)
|
|
||||||
|
|
||||||
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!secretBlindIndexData) throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed workspace authorization due to blind indices not being enabled'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
|
||||||
const membership = await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return ({ membership });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
|
||||||
await validateServiceAccountClientForWorkspace({
|
|
||||||
serviceAccount: authData.authPayload,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: authData.authPayload,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
|
||||||
const membership = await validateUserClientForWorkspace({
|
|
||||||
user: authData.authPayload,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
acceptedRoles,
|
|
||||||
requiredPermissions
|
|
||||||
});
|
|
||||||
|
|
||||||
return ({ membership });
|
|
||||||
}
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed client authorization for workspace'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a workspace with name [name] in organization with id [organizationId]
|
* Create a workspace with name [name] in organization with id [organizationId]
|
||||||
* and a bot for it.
|
* and a bot for it.
|
||||||
@@ -202,7 +81,6 @@ const deleteWorkspace = async ({ id }: { id: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForWorkspace,
|
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
deleteWorkspace
|
deleteWorkspace
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-10
@@ -6,8 +6,6 @@ import helmet from 'helmet';
|
|||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { DatabaseService } from './services';
|
import { DatabaseService } from './services';
|
||||||
import { setUpHealthEndpoint } from './services/health';
|
import { setUpHealthEndpoint } from './services/health';
|
||||||
import { TelemetryService } from './services';
|
|
||||||
|
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import swaggerUi = require('swagger-ui-express');
|
import swaggerUi = require('swagger-ui-express');
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -72,10 +70,9 @@ import {
|
|||||||
getSmtpHost
|
getSmtpHost
|
||||||
} from './config';
|
} from './config';
|
||||||
import { setup } from './utils/setup';
|
import { setup } from './utils/setup';
|
||||||
|
import { patchRouterParam } from './utils/patchAsyncRoutes';
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
TelemetryService.logTelemetryMessage();
|
|
||||||
|
|
||||||
await setup();
|
await setup();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -117,8 +114,8 @@ const main = async () => {
|
|||||||
app.use('/api/v1/membership', v1MembershipRouter);
|
app.use('/api/v1/membership', v1MembershipRouter);
|
||||||
app.use('/api/v1/key', v1KeyRouter);
|
app.use('/api/v1/key', v1KeyRouter);
|
||||||
app.use('/api/v1/invite-org', v1InviteOrgRouter);
|
app.use('/api/v1/invite-org', v1InviteOrgRouter);
|
||||||
app.use('/api/v1/secret', v1SecretRouter);
|
app.use('/api/v1/secret', v1SecretRouter); // deprecate
|
||||||
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated
|
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecate
|
||||||
app.use('/api/v1/password', v1PasswordRouter);
|
app.use('/api/v1/password', v1PasswordRouter);
|
||||||
app.use('/api/v1/stripe', v1StripeRouter);
|
app.use('/api/v1/stripe', v1StripeRouter);
|
||||||
app.use('/api/v1/integration', v1IntegrationRouter);
|
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||||
@@ -133,9 +130,9 @@ const main = async () => {
|
|||||||
app.use('/api/v2/workspace', v2EnvironmentRouter);
|
app.use('/api/v2/workspace', v2EnvironmentRouter);
|
||||||
app.use('/api/v2/workspace', v2TagsRouter);
|
app.use('/api/v2/workspace', v2TagsRouter);
|
||||||
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
||||||
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
app.use('/api/v2/secret', v2SecretRouter); // deprecate
|
||||||
app.use('/api/v2/secrets', v2SecretsRouter);
|
app.use('/api/v2/secrets', v2SecretsRouter); // note: in the process of moving to v3/secrets
|
||||||
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
app.use('/api/v2/service-token', v2ServiceTokenDataRouter);
|
||||||
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
|
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
|
||||||
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
||||||
|
|
||||||
@@ -146,7 +143,7 @@ const main = async () => {
|
|||||||
// api docs
|
// api docs
|
||||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
||||||
|
|
||||||
// Server status
|
// server status
|
||||||
app.use('/api', healthCheck)
|
app.use('/api', healthCheck)
|
||||||
|
|
||||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import {
|
|||||||
getAuthAPIKeyPayload,
|
getAuthAPIKeyPayload,
|
||||||
getAuthSAAKPayload
|
getAuthSAAKPayload
|
||||||
} from '../helpers/auth';
|
} from '../helpers/auth';
|
||||||
import {
|
|
||||||
UnauthorizedRequestError
|
|
||||||
} from '../utils/errors';
|
|
||||||
import {
|
import {
|
||||||
IUser,
|
IUser,
|
||||||
IServiceAccount,
|
IServiceAccount,
|
||||||
@@ -48,6 +45,7 @@ const requireAuth = ({
|
|||||||
|
|
||||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||||
// and return token type [authTokenType] and value [authTokenValue]
|
// and return token type [authTokenType] and value [authTokenValue]
|
||||||
|
|
||||||
const { authMode, authTokenValue } = validateAuthMode({
|
const { authMode, authTokenValue } = validateAuthMode({
|
||||||
headers: req.headers,
|
headers: req.headers,
|
||||||
acceptedAuthModes
|
acceptedAuthModes
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Bot } from '../models';
|
import { validateClientForBot } from '../validation';
|
||||||
import { validateMembership } from '../helpers/membership';
|
|
||||||
import { validateClientForBot } from '../helpers/bot';
|
|
||||||
import { AccountNotFoundError } from '../utils/errors';
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Integration, IntegrationAuth } from '../models';
|
import { validateClientForIntegration } from '../validation';
|
||||||
import { IntegrationService } from '../services';
|
|
||||||
import { validateMembership } from '../helpers/membership';
|
|
||||||
import { validateClientForIntegration } from '../helpers/integration';
|
|
||||||
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request is a member of workspace with proper roles associated
|
* Validate if user on request is a member of workspace with proper roles associated
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { IntegrationAuth, IWorkspace } from '../models';
|
import { validateClientForIntegrationAuth } from '../validation';
|
||||||
import { IntegrationService } from '../services';
|
|
||||||
import { validateClientForIntegrationAuth } from '../helpers/integrationAuth';
|
|
||||||
import { validateMembership } from '../helpers/membership';
|
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { validateClientForMembership } from '../validation';
|
||||||
import {
|
|
||||||
Membership,
|
|
||||||
} from '../models';
|
|
||||||
import {
|
|
||||||
validateClientForMembership,
|
|
||||||
validateMembership
|
|
||||||
} from '../helpers/membership';
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { validateClientForMembershipOrg } from '../validation';
|
||||||
import {
|
|
||||||
MembershipOrg
|
|
||||||
} from '../models';
|
|
||||||
import {
|
|
||||||
validateClientForMembershipOrg,
|
|
||||||
validateMembershipOrg
|
|
||||||
} from '../helpers/membershipOrg';
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: transform
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { IOrganization, MembershipOrg } from '../models';
|
import { validateClientForOrganization } from '../validation';
|
||||||
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
|
||||||
import { validateMembershipOrg } from '../helpers/membershipOrg';
|
|
||||||
import { validateClientForOrganization } from '../helpers/organization';
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { UnauthorizedRequestError, SecretNotFoundError } from '../utils/errors';
|
import { validateClientForSecret } from '../validation';
|
||||||
import { Secret } from '../models';
|
|
||||||
import {
|
|
||||||
validateMembership
|
|
||||||
} from '../helpers/membership';
|
|
||||||
import {
|
|
||||||
validateClientForSecret
|
|
||||||
} from '../helpers/secrets';
|
|
||||||
|
|
||||||
// note: used for old /v1/secret and /v2/secret routes.
|
// note: used for old /v1/secret and /v2/secret routes.
|
||||||
// newer /v2/secrets routes use [requireSecretsAuth] middleware with the exception
|
// newer /v2/secrets routes use [requireSecretsAuth] middleware with the exception
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { validateClientForSecrets } from '../validation';
|
||||||
import { Secret, Membership } from '../models';
|
|
||||||
import { validateClientForSecrets } from '../helpers/secrets';
|
|
||||||
|
|
||||||
const requireSecretsAuth = ({
|
const requireSecretsAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { ServiceAccount } from '../models';
|
import { validateClientForServiceAccount } from '../validation';
|
||||||
import {
|
|
||||||
ServiceAccountNotFoundError
|
|
||||||
} from '../utils/errors';
|
|
||||||
import {
|
|
||||||
validateMembershipOrg
|
|
||||||
} from '../helpers/membershipOrg';
|
|
||||||
import {
|
|
||||||
validateClientForServiceAccount
|
|
||||||
} from '../helpers/serviceAccount';
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { ServiceToken, ServiceTokenData } from '../models';
|
import { validateClientForServiceTokenData } from '../validation';
|
||||||
import { validateClientForServiceTokenData } from '../helpers/serviceTokenData';
|
|
||||||
import { validateMembership } from '../helpers/membership';
|
|
||||||
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
import { validateClientForWorkspace } from '../helpers/workspace';
|
import { validateClientForWorkspace } from '../validation';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ const botSchema = new Schema<IBot>(
|
|||||||
algorithm: { // the encryption algorithm used
|
algorithm: { // the encryption algorithm used
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ALGORITHM_AES_256_GCM],
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
required: true
|
required: true,
|
||||||
|
select: false
|
||||||
},
|
},
|
||||||
keyEncoding: {
|
keyEncoding: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -65,7 +66,8 @@ const botSchema = new Schema<IBot>(
|
|||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
ENCODING_SCHEME_BASE64
|
ENCODING_SCHEME_BASE64
|
||||||
],
|
],
|
||||||
required: true
|
required: true,
|
||||||
|
select: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
|||||||
ref: 'Workspace',
|
ref: 'Workspace',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
encryptedSaltCiphertext: {
|
encryptedSaltCiphertext: { // TODO: make these select: false
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
@@ -37,7 +37,8 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
|||||||
algorithm: {
|
algorithm: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ALGORITHM_AES_256_GCM],
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
required: true
|
required: true,
|
||||||
|
select: false
|
||||||
},
|
},
|
||||||
keyEncoding: {
|
keyEncoding: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -45,7 +46,8 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
|||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
ENCODING_SCHEME_BASE64
|
ENCODING_SCHEME_BASE64
|
||||||
],
|
],
|
||||||
required: true
|
required: true,
|
||||||
|
select: false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
requireSecretsAuth,
|
requireSecretsAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
|
import { validateClientForSecrets } from '../../validation';
|
||||||
import { query, body } from 'express-validator';
|
import { query, body } from 'express-validator';
|
||||||
import { secretsController } from '../../controllers/v2';
|
import { secretsController } from '../../controllers/v2';
|
||||||
import { validateClientForSecrets } from '../../helpers/secrets';
|
|
||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
|
|||||||
@@ -7,19 +7,14 @@ import {
|
|||||||
IEncryptAsymmetricOutput,
|
IEncryptAsymmetricOutput,
|
||||||
IDecryptAsymmetricInput,
|
IDecryptAsymmetricInput,
|
||||||
IEncryptSymmetricInput,
|
IEncryptSymmetricInput,
|
||||||
IEncryptSymmetricOutput,
|
|
||||||
IDecryptSymmetricInput
|
IDecryptSymmetricInput
|
||||||
} from '../../interfaces/utils';
|
} from '../../interfaces/utils';
|
||||||
import {
|
import { BadRequestError } from '../errors';
|
||||||
BadRequestError,
|
|
||||||
InternalServerError
|
|
||||||
} from '../errors';
|
|
||||||
import {
|
import {
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
NONCE_BYTES_SIZE,
|
NONCE_BYTES_SIZE,
|
||||||
BLOCK_SIZE_BYTES_16
|
BLOCK_SIZE_BYTES_16
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { validateEncryptionKey } from '../../validation';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new base64, NaCl, public-private key pair.
|
* Return new base64, NaCl, public-private key pair.
|
||||||
@@ -96,70 +91,6 @@ const decryptAsymmetric = ({
|
|||||||
return util.encodeUTF8(plaintext);
|
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(NONCE_BYTES_SIZE);
|
|
||||||
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].
|
* Return symmetrically encrypted [plaintext] using [key].
|
||||||
*
|
*
|
||||||
@@ -230,8 +161,6 @@ export {
|
|||||||
generateKeyPair,
|
generateKeyPair,
|
||||||
encryptAsymmetric,
|
encryptAsymmetric,
|
||||||
decryptAsymmetric,
|
decryptAsymmetric,
|
||||||
encryptSymmetric,
|
|
||||||
decryptSymmetric,
|
|
||||||
encryptSymmetric128BitHexKeyUTF8,
|
encryptSymmetric128BitHexKeyUTF8,
|
||||||
decryptSymmetric128BitHexKeyUTF8
|
decryptSymmetric128BitHexKeyUTF8
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
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,
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// backfill integration auth encryption metadata
|
|
||||||
await IntegrationAuth.updateMany(
|
|
||||||
{
|
|
||||||
algorithm: {
|
|
||||||
$exists: false
|
|
||||||
},
|
|
||||||
keySize: {
|
|
||||||
$exists: false
|
|
||||||
},
|
|
||||||
keyEncoding: {
|
|
||||||
$exists: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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,324 @@
|
|||||||
|
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 {
|
||||||
|
generateKeyPair
|
||||||
|
} from '../../utils/crypto';
|
||||||
|
import {
|
||||||
|
client,
|
||||||
|
getEncryptionKey,
|
||||||
|
getRootEncryptionKey
|
||||||
|
} from '../../config';
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
} from '../../variables';
|
||||||
|
import { InternalServerError } from '../errors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfill secrets to ensure that they're all versioned and have
|
||||||
|
* corresponding secret versions
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfill workspace bots to ensure that every workspace has a bot
|
||||||
|
*/
|
||||||
|
export const backfillBots = async () => {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
|
const workspaceIdsWithBot = await Bot.distinct('workspace');
|
||||||
|
const workspaceIdsToAddBot = await Workspace.distinct('_id', {
|
||||||
|
_id: {
|
||||||
|
$nin: workspaceIdsWithBot
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (workspaceIdsToAddBot.length === 0) return;
|
||||||
|
|
||||||
|
const botsToInsert = await Promise.all(
|
||||||
|
workspaceIdsToAddBot.map(async (workspaceToAddBot) => {
|
||||||
|
const { publicKey, privateKey } = generateKeyPair();
|
||||||
|
|
||||||
|
if (rootEncryptionKey) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||||
|
|
||||||
|
return new Bot({
|
||||||
|
name: 'Infisical Bot',
|
||||||
|
workspace: workspaceToAddBot,
|
||||||
|
isActive: false,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
});
|
||||||
|
} else if (encryptionKey) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: privateKey,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Bot({
|
||||||
|
name: 'Infisical Bot',
|
||||||
|
workspace: workspaceToAddBot,
|
||||||
|
isActive: false,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw InternalServerError({
|
||||||
|
message: 'Failed to backfill workspace bots due to missing encryption key'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await Bot.insertMany(botsToInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfill secret blind index data to ensure that every workspace
|
||||||
|
* has a secret blind index data
|
||||||
|
*/
|
||||||
|
export const backfillSecretBlindIndexData = async () => {
|
||||||
|
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
|
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace');
|
||||||
|
const workspaceIdsToBlindIndex = await Workspace.distinct('_id', {
|
||||||
|
_id: {
|
||||||
|
$nin: workspaceIdsBlindIndexed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (workspaceIdsToBlindIndex.length === 0) return;
|
||||||
|
|
||||||
|
const secretBlindIndexDataToInsert = await Promise.all(
|
||||||
|
workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
|
||||||
|
const salt = crypto.randomBytes(16).toString('base64');
|
||||||
|
|
||||||
|
if (rootEncryptionKey) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSaltCiphertext,
|
||||||
|
iv: saltIV,
|
||||||
|
tag: saltTag
|
||||||
|
} = client.encryptSymmetric(salt, rootEncryptionKey)
|
||||||
|
|
||||||
|
return new SecretBlindIndexData({
|
||||||
|
workspace: workspaceToBlindIndex,
|
||||||
|
encryptedSaltCiphertext,
|
||||||
|
saltIV,
|
||||||
|
saltTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
});
|
||||||
|
} else if (encryptionKey) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSaltCiphertext,
|
||||||
|
iv: saltIV,
|
||||||
|
tag: saltTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: salt,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SecretBlindIndexData({
|
||||||
|
workspace: workspaceToBlindIndex,
|
||||||
|
encryptedSaltCiphertext,
|
||||||
|
saltIV,
|
||||||
|
saltTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw InternalServerError({
|
||||||
|
message: 'Failed to backfill secret blind index data due to missing encryption key'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfill Secret, SecretVersion, SecretBlindIndexData, Bot,
|
||||||
|
* BackupPrivateKey, IntegrationAuth collections to ensure that
|
||||||
|
* they all have encryption metadata documented
|
||||||
|
*/
|
||||||
|
export const backfillEncryptionMetadata = async () => {
|
||||||
|
|
||||||
|
// backfill secret encryption metadata
|
||||||
|
await Secret.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill secret version encryption metadata
|
||||||
|
await SecretVersion.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill secret blind index encryption metadata
|
||||||
|
await SecretBlindIndexData.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill bot encryption metadata
|
||||||
|
await Bot.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill backup private key encryption metadata
|
||||||
|
await BackupPrivateKey.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// backfill integration auth encryption metadata
|
||||||
|
await IntegrationAuth.updateMany(
|
||||||
|
{
|
||||||
|
algorithm: {
|
||||||
|
$exists: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,15 +1,23 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { DatabaseService } from '../../services';
|
import { DatabaseService, TelemetryService } from '../../services';
|
||||||
import { setTransporter } from '../../helpers/nodemailer';
|
import { setTransporter } from '../../helpers/nodemailer';
|
||||||
import { initSmtp } from '../../services/smtp';
|
import { initSmtp } from '../../services/smtp';
|
||||||
import { createTestUserForDevelopment } from '../addDevelopmentUser'
|
import { createTestUserForDevelopment } from '../addDevelopmentUser'
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { patchRouterParam } = require('../patchAsyncRoutes');
|
const { patchRouterParam } = require('../patchAsyncRoutes');
|
||||||
|
import {
|
||||||
|
validateEncryptionKeysConfig
|
||||||
|
} from './validateConfig';
|
||||||
import {
|
import {
|
||||||
backfillSecretVersions,
|
backfillSecretVersions,
|
||||||
|
backfillBots,
|
||||||
backfillSecretBlindIndexData,
|
backfillSecretBlindIndexData,
|
||||||
backfillEncryptionMetadata
|
backfillEncryptionMetadata
|
||||||
} from './backfill';
|
} from './backfillData';
|
||||||
|
import {
|
||||||
|
reencryptBotPrivateKeys,
|
||||||
|
reencryptSecretBlindIndexDataSalts
|
||||||
|
} from './reencryptData';
|
||||||
import {
|
import {
|
||||||
getNodeEnv,
|
getNodeEnv,
|
||||||
getMongoURL,
|
getMongoURL,
|
||||||
@@ -18,34 +26,48 @@ import {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare Infisical upon startup. This includes tasks like:
|
* Prepare Infisical upon startup. This includes tasks like:
|
||||||
|
* - Log initial telemetry message
|
||||||
* - Initializing SMTP configuration
|
* - Initializing SMTP configuration
|
||||||
* - Initializing the database connection
|
* - Initializing the database connection
|
||||||
* - Initializing Sentry
|
* - Initializing Sentry
|
||||||
* - Backfilling data
|
* - Backfilling data
|
||||||
|
* - Re-encrypting data
|
||||||
*/
|
*/
|
||||||
export const setup = async () => {
|
export const setup = async () => {
|
||||||
|
patchRouterParam();
|
||||||
|
await validateEncryptionKeysConfig();
|
||||||
|
await TelemetryService.logTelemetryMessage();
|
||||||
|
|
||||||
// initializing SMTP configuration
|
// initializing SMTP configuration
|
||||||
setTransporter(await initSmtp());
|
setTransporter(await initSmtp());
|
||||||
|
|
||||||
// initializing the database connection
|
// initializing the database connection
|
||||||
await DatabaseService.initDatabase(await getMongoURL());
|
await DatabaseService.initDatabase(await getMongoURL());
|
||||||
|
|
||||||
// backfilling data
|
/**
|
||||||
|
* NOTE: the order in this setup function is critical.
|
||||||
|
* It is important to backfill data before performing any re-encryption functionality.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// backfilling data to catch up with new collections and updated fields
|
||||||
await backfillSecretVersions();
|
await backfillSecretVersions();
|
||||||
|
await backfillBots();
|
||||||
await backfillSecretBlindIndexData();
|
await backfillSecretBlindIndexData();
|
||||||
await backfillEncryptionMetadata();
|
await backfillEncryptionMetadata();
|
||||||
|
|
||||||
|
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||||
|
// to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||||
|
await reencryptBotPrivateKeys();
|
||||||
|
await reencryptSecretBlindIndexDataSalts();
|
||||||
|
|
||||||
// initializing Sentry
|
// initializing Sentry
|
||||||
if ((await getNodeEnv()) !== 'development') {
|
Sentry.init({
|
||||||
Sentry.init({
|
dsn: await getSentryDSN(),
|
||||||
dsn: await getSentryDSN(),
|
tracesSampleRate: 1.0,
|
||||||
tracesSampleRate: 1.0,
|
debug: (await getNodeEnv()) === 'production' ? false : true,
|
||||||
debug: await getNodeEnv() === 'production' ? false : true,
|
environment: (await getNodeEnv())
|
||||||
environment: await getNodeEnv()
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
patchRouterParam();
|
|
||||||
await createTestUserForDevelopment();
|
await createTestUserForDevelopment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import {
|
||||||
|
Bot,
|
||||||
|
IBot,
|
||||||
|
ISecretBlindIndexData,
|
||||||
|
SecretBlindIndexData
|
||||||
|
} from '../../models';
|
||||||
|
import { decryptSymmetric128BitHexKeyUTF8 } from '../../utils/crypto';
|
||||||
|
import {
|
||||||
|
client,
|
||||||
|
getEncryptionKey,
|
||||||
|
getRootEncryptionKey
|
||||||
|
} from '../../config';
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-encrypt bot private keys from hex 128-bit ENCRYPTION_KEY
|
||||||
|
* to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||||
|
*/
|
||||||
|
export const reencryptBotPrivateKeys = async () => {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
|
if (encryptionKey && rootEncryptionKey) {
|
||||||
|
// 1: re-encrypt bot private keys under ROOT_ENCRYPTION_KEY
|
||||||
|
const bots = await Bot.find({
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}).select('+encryptedPrivateKey iv tag algorithm keyEncoding');
|
||||||
|
|
||||||
|
if (bots.length === 0) return;
|
||||||
|
|
||||||
|
const operationsBot = await Promise.all(
|
||||||
|
bots.map(async (bot: IBot) => {
|
||||||
|
|
||||||
|
const privateKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: bot.encryptedPrivateKey,
|
||||||
|
iv: bot.iv,
|
||||||
|
tag: bot.tag,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
updateOne: {
|
||||||
|
filter: {
|
||||||
|
_id: bot._id
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await Bot.bulkWrite(operationsBot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-encrypt secret blind index data salts from hex 128-bit ENCRYPTION_KEY
|
||||||
|
* to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||||
|
*/
|
||||||
|
export const reencryptSecretBlindIndexDataSalts = async () => {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
|
// 2. re-encrypt secret blind index salts under ROOT_ENCRYPTION_KEY
|
||||||
|
|
||||||
|
if (encryptionKey && rootEncryptionKey) {
|
||||||
|
const secretBlindIndexData = await SecretBlindIndexData.find({
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}).select('+encryptedSaltCiphertext +saltIV +saltTag +algorithm +keyEncoding');
|
||||||
|
|
||||||
|
if (secretBlindIndexData.length == 0) return;
|
||||||
|
|
||||||
|
const operationsSecretBlindIndexData = await Promise.all(
|
||||||
|
secretBlindIndexData.map(async (secretBlindIndexDatum: ISecretBlindIndexData) => {
|
||||||
|
|
||||||
|
const salt = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secretBlindIndexDatum.encryptedSaltCiphertext,
|
||||||
|
iv: secretBlindIndexDatum.saltIV,
|
||||||
|
tag: secretBlindIndexDatum.saltTag,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSaltCiphertext,
|
||||||
|
iv: saltIV,
|
||||||
|
tag: saltTag
|
||||||
|
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
updateOne: {
|
||||||
|
filter: {
|
||||||
|
_id: secretBlindIndexDatum._id
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
encryptedSaltCiphertext,
|
||||||
|
saltIV,
|
||||||
|
saltTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await SecretBlindIndexData.bulkWrite(operationsSecretBlindIndexData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
getEncryptionKey,
|
||||||
|
getRootEncryptionKey
|
||||||
|
} from '../../config';
|
||||||
|
import {
|
||||||
|
InternalServerError
|
||||||
|
} from '../../utils/errors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate ENCRYPTION_KEY and ROOT_ENCRYPTION_KEY. Specifically:
|
||||||
|
* - ENCRYPTION_KEY is a hex, 128-bit string
|
||||||
|
* - ROOT_ENCRYPTION_KEY is a base64, 128-bit string
|
||||||
|
* - Either ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY are present
|
||||||
|
*
|
||||||
|
* - Encrypted data is consistent with the passed in encryption keys
|
||||||
|
*
|
||||||
|
* NOTE 1: ENCRYPTION_KEY is being transitioned to ROOT_ENCRYPTION_KEY
|
||||||
|
* NOTE 2: In the future, we will have a superior validation function
|
||||||
|
* built into the SDK.
|
||||||
|
*/
|
||||||
|
export const validateEncryptionKeysConfig = async () => {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
|
// TODO: handle case where either of keys can be empty strings
|
||||||
|
// and it would actually count as being defined for encryption
|
||||||
|
// within the application
|
||||||
|
|
||||||
|
// console.log('validateEncryptionKeysConfig');
|
||||||
|
// console.log('encryptionKey: ', encryptionKey);
|
||||||
|
// console.log('rootEncryptionKey: ', rootEncryptionKey);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(encryptionKey === undefined || encryptionKey === "") &&
|
||||||
|
(rootEncryptionKey === undefined || rootEncryptionKey === "")
|
||||||
|
) throw InternalServerError({
|
||||||
|
message: "Failed to find required root encryption key environment variable. Please make sure that you're passing in a ROOT_ENCRYPTION_KEY environment variable."
|
||||||
|
});
|
||||||
|
|
||||||
|
if (encryptionKey && encryptionKey !== '') {
|
||||||
|
// validate [encryptionKey]
|
||||||
|
|
||||||
|
const keyBuffer = Buffer.from(encryptionKey, 'hex');
|
||||||
|
const decoded = keyBuffer.toString('hex');
|
||||||
|
|
||||||
|
if (decoded !== encryptionKey) throw InternalServerError({
|
||||||
|
message: 'Failed to validate that the encryption key is correctly encoded in hex.'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (keyBuffer.length !== 16) throw InternalServerError({
|
||||||
|
message: 'Failed to validate that the encryption key is a 128-bit hex string.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootEncryptionKey && rootEncryptionKey !== '') {
|
||||||
|
// validate [rootEncryptionKey]
|
||||||
|
|
||||||
|
const keyBuffer = Buffer.from(rootEncryptionKey, 'base64')
|
||||||
|
const decoded = keyBuffer.toString('base64');
|
||||||
|
|
||||||
|
if (decoded !== rootEncryptionKey) throw InternalServerError({
|
||||||
|
message: 'Failed to validate that the root encryption key is correctly encoded in base64'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (keyBuffer.length !== 32) throw InternalServerError({
|
||||||
|
message: 'Failed to validate that the encryption key is a 256-bit base64 string'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
IServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
Bot,
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||||
|
import { validateUserClientForWorkspace } from './user';
|
||||||
|
import {
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
BotNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for bot with id [botId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.botId - id of bot to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
*/
|
||||||
|
export const validateClientForBot = async ({
|
||||||
|
authData,
|
||||||
|
botId,
|
||||||
|
acceptedRoles,
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
botId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<"admin" | "member">;
|
||||||
|
}) => {
|
||||||
|
const bot = await Bot.findById(botId);
|
||||||
|
|
||||||
|
if (!bot) throw BotNotFoundError();
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_JWT &&
|
||||||
|
authData.authPayload instanceof User
|
||||||
|
) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace,
|
||||||
|
acceptedRoles,
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||||
|
authData.authPayload instanceof ServiceAccount
|
||||||
|
) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace,
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
||||||
|
authData.authPayload instanceof ServiceTokenData
|
||||||
|
) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed service token authorization for bot",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_API_KEY &&
|
||||||
|
authData.authPayload instanceof User
|
||||||
|
) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace,
|
||||||
|
acceptedRoles,
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw BotNotFoundError({
|
||||||
|
message: "Failed client authorization for bot",
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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;
|
|
||||||
};
|
|
||||||
@@ -1 +1,10 @@
|
|||||||
export * from './config';
|
export * from './workspace';
|
||||||
|
export * from './bot';
|
||||||
|
export * from './integration';
|
||||||
|
export * from './integrationAuth';
|
||||||
|
export * from './membership';
|
||||||
|
export * from './membershipOrg';
|
||||||
|
export * from './organization';
|
||||||
|
export * from './secrets';
|
||||||
|
export * from './serviceAccount';
|
||||||
|
export * from './serviceTokenData';
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
IServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
Integration,
|
||||||
|
IntegrationAuth,
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||||
|
import { validateUserClientForWorkspace } from './user';
|
||||||
|
import { IntegrationService } from '../services';
|
||||||
|
import {
|
||||||
|
IntegrationNotFoundError,
|
||||||
|
IntegrationAuthNotFoundError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for integration with id [integrationId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.integrationId - id of integration to validate against
|
||||||
|
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateClientForIntegration = async ({
|
||||||
|
authData,
|
||||||
|
integrationId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
integrationId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const integration = await Integration.findById(integrationId);
|
||||||
|
if (!integration) throw IntegrationNotFoundError();
|
||||||
|
|
||||||
|
const integrationAuth = await IntegrationAuth
|
||||||
|
.findById(integration.integrationAuth)
|
||||||
|
.select(
|
||||||
|
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||||
|
|
||||||
|
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||||
|
integrationAuthId: integrationAuth._id
|
||||||
|
})).accessToken;
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integration, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: integration.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integration, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for integration'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integration, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for integration'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -20,8 +20,8 @@ import {
|
|||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../services';
|
||||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
import { validateUserClientForWorkspace } from './user';
|
||||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate authenticated clients for integration authorization with id [integrationAuthId] based
|
* Validate authenticated clients for integration authorization with id [integrationAuthId] based
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
IServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
Membership,
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||||
|
import { validateUserClientForWorkspace } from './user';
|
||||||
|
import { validateServiceTokenDataClientForWorkspace } from './serviceTokenData';
|
||||||
|
import {
|
||||||
|
MembershipNotFoundError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for membership with id [membershipId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.membershipId - id of membership to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspaceRoles
|
||||||
|
* @returns {Membership} - validated membership
|
||||||
|
*/
|
||||||
|
export const validateClientForMembership = async ({
|
||||||
|
authData,
|
||||||
|
membershipId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
membershipId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const membership = await Membership.findById(membershipId);
|
||||||
|
|
||||||
|
if (!membership) throw MembershipNotFoundError({
|
||||||
|
message: 'Failed to find membership'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId: new Types.ObjectId(membership.workspace)
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode == AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
IServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
MembershipOrg,
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
import {
|
||||||
|
MembershipOrgNotFoundError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.membershipOrgId - id of organization membership to validate against
|
||||||
|
* @param {Array<'owner' | 'admin' | 'member'>} obj.acceptedRoles - accepted organization roles
|
||||||
|
* @param {MembershipOrg} - validated organization membership
|
||||||
|
*/
|
||||||
|
export const validateClientForMembershipOrg = async ({
|
||||||
|
authData,
|
||||||
|
membershipOrgId,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
membershipOrgId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
}) => {
|
||||||
|
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||||
|
|
||||||
|
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
||||||
|
message: 'Failed to find organization membership '
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: authData.authPayload._id,
|
||||||
|
organizationId: membershipOrg.organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
if (!authData.authPayload.organization.equals(membershipOrg.organization)) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account client authorization for organization membership'
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account client authorization for organization membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: authData.authPayload._id,
|
||||||
|
organizationId: membershipOrg.organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for organization membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
IServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
Organization,
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
OrganizationNotFoundError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import { validateUserClientForOrganization } from './user';
|
||||||
|
import { validateServiceAccountClientForOrganization } from './serviceAccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate accepted clients for organization with id [organizationId]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.organizationId - id of organization to validate against
|
||||||
|
*/
|
||||||
|
export const validateClientForOrganization = async ({
|
||||||
|
authData,
|
||||||
|
organizationId,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses,
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
organizationId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||||
|
acceptedStatuses: Array<"invited" | "accepted">;
|
||||||
|
}) => {
|
||||||
|
const organization = await Organization.findById(organizationId);
|
||||||
|
|
||||||
|
if (!organization) {
|
||||||
|
throw OrganizationNotFoundError({
|
||||||
|
message: "Failed to find organization",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_JWT &&
|
||||||
|
authData.authPayload instanceof User
|
||||||
|
) {
|
||||||
|
const membershipOrg = await validateUserClientForOrganization({
|
||||||
|
user: authData.authPayload,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { organization, membershipOrg };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||||
|
authData.authPayload instanceof ServiceAccount
|
||||||
|
) {
|
||||||
|
await validateServiceAccountClientForOrganization({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
organization,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { organization };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
||||||
|
authData.authPayload instanceof ServiceTokenData
|
||||||
|
) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed service token authorization for organization",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authData.authMode === AUTH_MODE_API_KEY &&
|
||||||
|
authData.authPayload instanceof User
|
||||||
|
) {
|
||||||
|
const membershipOrg = await validateUserClientForOrganization({
|
||||||
|
user: authData.authPayload,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { organization, membershipOrg };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed client authorization for organization",
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
ISecret,
|
||||||
|
Secret,
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import { validateServiceAccountClientForWorkspace, validateServiceAccountClientForSecrets } from './serviceAccount';
|
||||||
|
import { validateUserClientForSecret, validateUserClientForSecrets } from './user';
|
||||||
|
import { validateServiceTokenDataClientForWorkspace, validateServiceTokenDataClientForSecrets } from './serviceTokenData';
|
||||||
|
import { AuthData } from '../interfaces/middleware';
|
||||||
|
import {
|
||||||
|
SecretNotFoundError,
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
BadRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for secrets with id [secretId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.secretId - id of secret to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateClientForSecret = async ({
|
||||||
|
authData,
|
||||||
|
secretId,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: AuthData;
|
||||||
|
secretId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions: string[];
|
||||||
|
}) => {
|
||||||
|
const secret = await Secret.findById(secretId);
|
||||||
|
|
||||||
|
if (!secret) throw SecretNotFoundError({
|
||||||
|
message: 'Failed to find secret'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecret({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
environment: secret.environment,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
environment: secret.environment
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecret({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for secret'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for secrets with ids [secretIds] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId[]} obj.secretIds - id of workspace to validate against
|
||||||
|
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateClientForSecrets = async ({
|
||||||
|
authData,
|
||||||
|
secretIds,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: AuthData;
|
||||||
|
secretIds: Types.ObjectId[];
|
||||||
|
requiredPermissions: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
let secrets: ISecret[] = [];
|
||||||
|
|
||||||
|
secrets = await Secret.find({
|
||||||
|
_id: {
|
||||||
|
$in: secretIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (secrets.length != secretIds.length) {
|
||||||
|
throw BadRequestError({ message: 'Failed to validate non-existent secrets' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecrets({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForSecrets({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForSecrets({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecrets({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for secrets resource'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
IServiceTokenData,
|
IServiceTokenData,
|
||||||
ISecret,
|
ISecret,
|
||||||
IOrganization,
|
IOrganization,
|
||||||
IServiceAccountWorkspacePermission,
|
|
||||||
ServiceAccountWorkspacePermission
|
ServiceAccountWorkspacePermission
|
||||||
} from '../models';
|
} from '../models';
|
||||||
|
import { validateUserClientForServiceAccount } from './user';
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
UnauthorizedRequestError,
|
UnauthorizedRequestError,
|
||||||
@@ -25,11 +25,8 @@ import {
|
|||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_API_KEY
|
AUTH_MODE_API_KEY
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import {
|
|
||||||
validateUserClientForServiceAccount
|
|
||||||
} from '../helpers/user';
|
|
||||||
|
|
||||||
const validateClientForServiceAccount = async ({
|
export const validateClientForServiceAccount = async ({
|
||||||
authData,
|
authData,
|
||||||
serviceAccountId,
|
serviceAccountId,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
@@ -100,7 +97,7 @@ const validateClientForServiceAccount = async ({
|
|||||||
* @param {String} environment - (optional) environment in workspace to validate against
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
const validateServiceAccountClientForWorkspace = async ({
|
export const validateServiceAccountClientForWorkspace = async ({
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -169,7 +166,7 @@ const validateClientForServiceAccount = async ({
|
|||||||
* @param {Secret[]} secrets - secrets to validate against
|
* @param {Secret[]} secrets - secrets to validate against
|
||||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
const validateServiceAccountClientForSecrets = async ({
|
export const validateServiceAccountClientForSecrets = async ({
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
secrets,
|
secrets,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
@@ -226,7 +223,7 @@ const validateClientForServiceAccount = async ({
|
|||||||
* @param {ServiceAccount} targetServiceAccount - target service account to validate against
|
* @param {ServiceAccount} targetServiceAccount - target service account to validate against
|
||||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
const validateServiceAccountClientForServiceAccount = ({
|
export const validateServiceAccountClientForServiceAccount = ({
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
targetServiceAccount,
|
targetServiceAccount,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
@@ -248,7 +245,7 @@ const validateServiceAccountClientForServiceAccount = ({
|
|||||||
* @param {User} obj.user - service account client
|
* @param {User} obj.user - service account client
|
||||||
* @param {Organization} obj.organization - organization to validate against
|
* @param {Organization} obj.organization - organization to validate against
|
||||||
*/
|
*/
|
||||||
const validateServiceAccountClientForOrganization = async ({
|
export const validateServiceAccountClientForOrganization = async ({
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
organization
|
organization
|
||||||
}: {
|
}: {
|
||||||
@@ -261,11 +258,3 @@ const validateServiceAccountClientForOrganization = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
validateClientForServiceAccount,
|
|
||||||
validateServiceAccountClientForWorkspace,
|
|
||||||
validateServiceAccountClientForSecrets,
|
|
||||||
validateServiceAccountClientForServiceAccount,
|
|
||||||
validateServiceAccountClientForOrganization
|
|
||||||
}
|
|
||||||
+5
-11
@@ -18,8 +18,8 @@ import {
|
|||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_API_KEY
|
AUTH_MODE_API_KEY
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
import { validateUserClientForWorkspace } from './user';
|
||||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate authenticated clients for service token with id [serviceTokenId] based
|
* Validate authenticated clients for service token with id [serviceTokenId] based
|
||||||
@@ -29,7 +29,7 @@ import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAcco
|
|||||||
* @param {Types.ObjectId} obj.serviceTokenData - id of service token to validate against
|
* @param {Types.ObjectId} obj.serviceTokenData - id of service token to validate against
|
||||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
*/
|
*/
|
||||||
const validateClientForServiceTokenData = async ({
|
export const validateClientForServiceTokenData = async ({
|
||||||
authData,
|
authData,
|
||||||
serviceTokenDataId,
|
serviceTokenDataId,
|
||||||
acceptedRoles
|
acceptedRoles
|
||||||
@@ -100,7 +100,7 @@ const validateClientForServiceTokenData = async ({
|
|||||||
* @param {String} environment - (optional) environment in workspace to validate against
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
const validateServiceTokenDataClientForWorkspace = async ({
|
export const validateServiceTokenDataClientForWorkspace = async ({
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -146,7 +146,7 @@ const validateClientForServiceTokenData = async ({
|
|||||||
* @param {Secret[]} secrets - secrets to validate against
|
* @param {Secret[]} secrets - secrets to validate against
|
||||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
const validateServiceTokenDataClientForSecrets = async ({
|
export const validateServiceTokenDataClientForSecrets = async ({
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
secrets,
|
secrets,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
@@ -180,9 +180,3 @@ const validateClientForServiceTokenData = async ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
validateClientForServiceTokenData,
|
|
||||||
validateServiceTokenDataClientForWorkspace,
|
|
||||||
validateServiceTokenDataClientForSecrets
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
ISecret,
|
||||||
|
IServiceAccount,
|
||||||
|
Membership,
|
||||||
|
IOrganization,
|
||||||
|
} from '../models';
|
||||||
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
import {
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access workspace
|
||||||
|
* with id [workspaceId] and its environment [environment] with required permissions
|
||||||
|
* [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateUserClientForWorkspace = async ({
|
||||||
|
user,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
// validate user membership in workspace
|
||||||
|
const membership = await validateMembership({
|
||||||
|
userId: user._id,
|
||||||
|
workspaceId,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
let runningIsDisallowed = false;
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
switch (requiredPermission) {
|
||||||
|
case PERMISSION_READ_SECRETS:
|
||||||
|
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||||
|
break;
|
||||||
|
case PERMISSION_WRITE_SECRETS:
|
||||||
|
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningIsDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access secret [secret]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateUserClientForSecret = async ({
|
||||||
|
user,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
secret: ISecret;
|
||||||
|
acceptedRoles?: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
const membership = await validateMembership({
|
||||||
|
userId: user._id,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||||
|
const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
|
||||||
|
if (isDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'You do not have the required permissions to perform this action'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access secrets [secrets]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateUserClientForSecrets = async ({
|
||||||
|
user,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
secrets: ISecret[];
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
// TODO: add acceptedRoles?
|
||||||
|
|
||||||
|
const userMemberships = await Membership.find({ user: user._id })
|
||||||
|
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
||||||
|
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
|
||||||
|
|
||||||
|
// for each secret check if the secret belongs to a workspace the user is a member of
|
||||||
|
secrets.forEach((secret: ISecret) => {
|
||||||
|
if (!workspaceIdsSet.has(secret.workspace.toString())) {
|
||||||
|
throw BadRequestError({
|
||||||
|
message: 'Failed authorization for the secret'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||||
|
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
||||||
|
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
|
||||||
|
if (isDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'You do not have the required permissions to perform this action'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access service account [serviceAccount]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {ServiceAccount} obj.serviceAccount - service account to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateUserClientForServiceAccount = async ({
|
||||||
|
user,
|
||||||
|
serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
if (!serviceAccount.user.equals(user._id)) {
|
||||||
|
// case: user who created service account is not the
|
||||||
|
// same user that is on the request
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: user._id,
|
||||||
|
organizationId: serviceAccount.organization,
|
||||||
|
acceptedRoles: [],
|
||||||
|
acceptedStatuses: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access organization [organization]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Organization} obj.organization - organization to validate against
|
||||||
|
*/
|
||||||
|
export const validateUserClientForOrganization = async ({
|
||||||
|
user,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
organization: IOrganization;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
}) => {
|
||||||
|
const membershipOrg = await validateMembershipOrg({
|
||||||
|
userId: user._id,
|
||||||
|
organizationId: organization._id,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
IServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
Workspace,
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceTokenData,
|
||||||
|
SecretBlindIndexData
|
||||||
|
} from '../models';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||||
|
import { validateUserClientForWorkspace } from './user';
|
||||||
|
import { validateServiceTokenDataClientForWorkspace } from './serviceTokenData';
|
||||||
|
import {
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
WorkspaceNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateClientForWorkspace = async ({
|
||||||
|
authData,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions,
|
||||||
|
requireBlindIndicesEnabled
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
requireBlindIndicesEnabled: boolean;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
|
|
||||||
|
if (!workspace) throw WorkspaceNotFoundError({
|
||||||
|
message: 'Failed to find workspace'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (requireBlindIndicesEnabled) {
|
||||||
|
// case: blind indices are not enabled for secrets in this workspace
|
||||||
|
// (i.e. workspace was created before blind indices were introduced
|
||||||
|
// and no admin has enabled it)
|
||||||
|
|
||||||
|
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!secretBlindIndexData) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed workspace authorization due to blind indices not being enabled'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
const membership = await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ membership });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
const membership = await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ membership });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -32,7 +32,6 @@ export default function SignUp() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {data: serverDetails } = useFetchServerStatus()
|
const {data: serverDetails } = useFetchServerStatus()
|
||||||
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user