mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 15:31:19 +00:00
Checkpoint finish preliminary support for ROOT_ENCRYPTION_KEY
This commit is contained in:
@@ -41,7 +41,6 @@ const validateAuthMode = ({
|
||||
headers: { [key: string]: string | string[] | undefined },
|
||||
acceptedAuthModes: string[]
|
||||
}) => {
|
||||
// TODO: refactor middleware
|
||||
const apiKey = headers['x-api-key'];
|
||||
const authHeader = headers['authorization'];
|
||||
|
||||
|
||||
+89
-121
@@ -4,12 +4,7 @@ import {
|
||||
BotKey,
|
||||
Secret,
|
||||
ISecret,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData,
|
||||
IUser
|
||||
} from "../models";
|
||||
import {
|
||||
generateKeyPair,
|
||||
@@ -19,91 +14,16 @@ import {
|
||||
} from '../utils/crypto';
|
||||
import {
|
||||
SECRET_SHARED,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from "../variables";
|
||||
import { getEncryptionKey } from "../config";
|
||||
import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { validateUserClientForWorkspace } from "../helpers/user";
|
||||
import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount";
|
||||
|
||||
/**
|
||||
* 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",
|
||||
});
|
||||
};
|
||||
import {
|
||||
getEncryptionKey,
|
||||
getRootEncryptionKey,
|
||||
client
|
||||
} from "../config";
|
||||
import { InternalServerError } from "../utils/errors";
|
||||
|
||||
/**
|
||||
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
||||
@@ -118,23 +38,52 @@ const createBot = async ({
|
||||
name: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const { publicKey, privateKey } = generateKeyPair();
|
||||
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: privateKey,
|
||||
key: await getEncryptionKey(),
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
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
|
||||
*/
|
||||
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const botKey = await BotKey.findOne({
|
||||
workspace: workspaceId,
|
||||
}).populate<{ sender: IUser }>("sender", "publicKey");
|
||||
})
|
||||
.populate<{ sender: IUser }>("sender", "publicKey");
|
||||
|
||||
if (!botKey) throw new Error("Failed to find bot key");
|
||||
|
||||
const bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
}).select("+encryptedPrivateKey +iv +tag");
|
||||
}).select("+encryptedPrivateKey +iv +tag +algorithm +keyEncoding");
|
||||
|
||||
if (!bot) throw new Error("Failed to find bot");
|
||||
if (!bot.isActive) throw new Error("Bot is not active");
|
||||
|
||||
const privateKeyBot = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: bot.encryptedPrivateKey,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
key: await getEncryptionKey(),
|
||||
});
|
||||
if (rootEncryptionKey && bot.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||
// case: encoding scheme is base64
|
||||
const privateKeyBot = client.decryptSymmetric(bot.encryptedPrivateKey, rootEncryptionKey, bot.iv, bot.tag);
|
||||
|
||||
const key = decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
nonce: botKey.nonce,
|
||||
publicKey: botKey.sender.publicKey as string,
|
||||
privateKey: privateKeyBot,
|
||||
});
|
||||
return decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
nonce: botKey.nonce,
|
||||
publicKey: botKey.sender.publicKey as string,
|
||||
privateKey: privateKeyBot,
|
||||
});
|
||||
} else if (encryptionKey && bot.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
|
||||
return key;
|
||||
throw InternalServerError({
|
||||
message: "Failed to obtain bot's copy of workspace key needed for bot operations"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -276,7 +245,6 @@ const decryptSymmetricHelper = async ({
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForBot,
|
||||
createBot,
|
||||
getSecretsHelper,
|
||||
encryptSymmetricHelper,
|
||||
|
||||
@@ -3,40 +3,20 @@ import { Types } from 'mongoose';
|
||||
import {
|
||||
Bot,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
IntegrationAuth
|
||||
} from '../models';
|
||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||
import { BotService } from '../services';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY
|
||||
INTEGRATION_NETLIFY,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8
|
||||
} from '../variables';
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
IntegrationAuthNotFoundError,
|
||||
IntegrationNotFoundError
|
||||
} from '../utils/errors';
|
||||
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 {
|
||||
workspace: string;
|
||||
@@ -45,84 +25,6 @@ interface Update {
|
||||
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
|
||||
* named [integration]
|
||||
@@ -400,7 +302,9 @@ const setIntegrationAuthRefreshHelper = async ({
|
||||
}, {
|
||||
refreshCiphertext: obj.ciphertext,
|
||||
refreshIV: obj.iv,
|
||||
refreshTag: obj.tag
|
||||
refreshTag: obj.tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}, {
|
||||
new: true
|
||||
});
|
||||
@@ -461,7 +365,9 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj.iv,
|
||||
accessTag: encryptedAccessTokenObj.tag,
|
||||
accessExpiresAt
|
||||
accessExpiresAt,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}, {
|
||||
new: true
|
||||
});
|
||||
@@ -475,7 +381,6 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForIntegration,
|
||||
handleOAuthExchangeHelper,
|
||||
syncIntegrationsHelper,
|
||||
getIntegrationAuthRefreshHelper,
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IntegrationAuth,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData,
|
||||
IWorkspace
|
||||
} from '../models';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import {
|
||||
IntegrationAuthNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import { IntegrationService } from '../services';
|
||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for integration authorization with id [integrationAuthId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.integrationAuthId - id of integration authorization to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForIntegrationAuth = async ({
|
||||
authData,
|
||||
integrationAuthId,
|
||||
acceptedRoles,
|
||||
attachAccessToken
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
integrationAuthId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
attachAccessToken?: boolean;
|
||||
}) => {
|
||||
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integrationAuthId)
|
||||
.populate<{ workspace: IWorkspace }>('workspace')
|
||||
.select(
|
||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
|
||||
let accessToken;
|
||||
if (attachAccessToken) {
|
||||
accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id
|
||||
})).accessToken;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integrationAuth.workspace._id,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: integrationAuth.workspace._id
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for integration authorization'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integrationAuth.workspace._id,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for integration authorization'
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForIntegrationAuth
|
||||
};
|
||||
@@ -2,105 +2,12 @@ import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Membership,
|
||||
Key,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
Key
|
||||
} from '../models';
|
||||
import {
|
||||
MembershipNotFoundError,
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError
|
||||
BadRequestError
|
||||
} 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]
|
||||
@@ -230,7 +137,6 @@ const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForMembership,
|
||||
validateMembership,
|
||||
addMemberships,
|
||||
findMembership,
|
||||
|
||||
@@ -3,95 +3,12 @@ import {
|
||||
MembershipOrg,
|
||||
Workspace,
|
||||
Membership,
|
||||
Key,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
Key
|
||||
} from '../models';
|
||||
import {
|
||||
MembershipOrgNotFoundError,
|
||||
BadRequestError,
|
||||
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
|
||||
*/
|
||||
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]
|
||||
@@ -234,7 +151,6 @@ const deleteMembershipOrg = async ({
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForMembershipOrg,
|
||||
validateMembershipOrg,
|
||||
findMembershipOrg,
|
||||
addMembershipsOrg,
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
import Stripe from "stripe";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData,
|
||||
} from "../models";
|
||||
import { Organization, MembershipOrg } from "../models";
|
||||
import {
|
||||
ACCEPTED,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
OWNER,
|
||||
ACCEPTED
|
||||
} from "../variables";
|
||||
import {
|
||||
getStripeSecretKey,
|
||||
@@ -23,94 +10,6 @@ import {
|
||||
getStripeProductTeam,
|
||||
getStripeProductStarter,
|
||||
} 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]
|
||||
@@ -258,8 +157,7 @@ const updateSubscriptionOrgQuantity = async ({
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForOrganization,
|
||||
createOrganization,
|
||||
initSubscriptionOrg,
|
||||
updateSubscriptionOrgQuantity,
|
||||
updateSubscriptionOrgQuantity
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
} from "../variables";
|
||||
import _ from "lodash";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||
@@ -194,6 +196,8 @@ const v1PushSecrets = async ({
|
||||
secretValueIV: newSecret.ivValue,
|
||||
secretValueTag: newSecret.tagValue,
|
||||
secretValueHash: newSecret.hashValue,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -225,6 +229,8 @@ const v1PushSecrets = async ({
|
||||
secretCommentIV: s.ivComment,
|
||||
secretCommentTag: s.tagComment,
|
||||
secretCommentHash: s.hashComment,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
};
|
||||
|
||||
if (toAdd[idx].type === "personal") {
|
||||
@@ -254,6 +260,8 @@ const v1PushSecrets = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash,
|
||||
algorithm,
|
||||
keyEncoding
|
||||
}) =>
|
||||
new SecretVersion({
|
||||
secret: _id,
|
||||
@@ -271,6 +279,8 @@ const v1PushSecrets = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash,
|
||||
algorithm,
|
||||
keyEncoding
|
||||
})
|
||||
),
|
||||
});
|
||||
@@ -467,6 +477,8 @@ const v2PushSecrets = async ({
|
||||
workspace: workspaceId,
|
||||
type: toAdd[idx].type,
|
||||
environment,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(toAdd[idx].type === "personal" ? { user: userId } : {}),
|
||||
}))
|
||||
);
|
||||
@@ -478,6 +490,8 @@ const v2PushSecrets = async ({
|
||||
...secretDocument,
|
||||
secret: secretDocument._id,
|
||||
isDeleted: false,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
+80
-215
@@ -7,50 +7,27 @@ import {
|
||||
DeleteSecretParams
|
||||
} from '../interfaces/services/SecretService';
|
||||
import {
|
||||
AuthData
|
||||
} from '../interfaces/middleware';
|
||||
import {
|
||||
User,
|
||||
Workspace,
|
||||
ServiceAccount,
|
||||
ServiceTokenData,
|
||||
Secret,
|
||||
ISecret,
|
||||
SecretBlindIndexData,
|
||||
} from '../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 {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
SecretNotFoundError,
|
||||
SecretBlindIndexDataNotFoundError
|
||||
SecretBlindIndexDataNotFoundError,
|
||||
InternalServerError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS
|
||||
ACTION_DELETE_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../variables';
|
||||
import crypto from 'crypto';
|
||||
import * as argon2 from 'argon2';
|
||||
@@ -58,7 +35,7 @@ import {
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
decryptSymmetric128BitHexKeyUTF8
|
||||
} from '../utils/crypto';
|
||||
import { getEncryptionKey } from '../config';
|
||||
import { getEncryptionKey, client, getRootEncryptionKey } from '../config';
|
||||
import { TelemetryService } from '../services';
|
||||
import {
|
||||
EESecretService,
|
||||
@@ -69,157 +46,6 @@ import {
|
||||
getAuthDataPayloadUserObj
|
||||
} 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]
|
||||
* for workspace with id [workspaceId]
|
||||
@@ -231,26 +57,47 @@ const createSecretBlindIndexDataHelper = async ({
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
|
||||
// initialize random blind index salt for workspace
|
||||
const salt = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: salt,
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
const secretBlindIndexData = await new SecretBlindIndexData({
|
||||
workspace: workspaceId,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag
|
||||
}).save();
|
||||
|
||||
return secretBlindIndexData;
|
||||
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
||||
|
||||
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;
|
||||
}) => {
|
||||
// check if workspace blind index data exists
|
||||
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId
|
||||
});
|
||||
}).select('+algorithm +keyEncoding');
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
// decrypt workspace salt
|
||||
const salt = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
key: await getEncryptionKey()
|
||||
|
||||
if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||
return client.decryptSymmetric(
|
||||
secretBlindIndexData.encryptedSaltCiphertext,
|
||||
rootEncryptionKey,
|
||||
secretBlindIndexData.saltIV,
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to obtain workspace salt needed for secret blind indexing'
|
||||
});
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,7 +283,9 @@ const createSecretHelper = async ({
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
secretCommentTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).save();
|
||||
|
||||
const secretVersion = new SecretVersion({
|
||||
@@ -439,7 +302,9 @@ const createSecretHelper = async ({
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
|
||||
// // (EE) add version for new secret
|
||||
@@ -729,7 +594,9 @@ const updateSecretHelper = async ({
|
||||
secretKeyTag: secret.secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
|
||||
// (EE) add version for new secret
|
||||
@@ -890,8 +757,6 @@ const deleteSecretHelper = async ({
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForSecret,
|
||||
validateClientForSecrets,
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
User,
|
||||
IUser,
|
||||
ServiceAccount,
|
||||
IServiceAccount,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
ISecret,
|
||||
IOrganization,
|
||||
IServiceAccountWorkspacePermission,
|
||||
ServiceAccountWorkspacePermission
|
||||
} from '../models';
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
ServiceAccountNotFoundError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import {
|
||||
validateUserClientForServiceAccount
|
||||
} from '../helpers/user';
|
||||
|
||||
const validateClientForServiceAccount = async ({
|
||||
authData,
|
||||
serviceAccountId,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
},
|
||||
serviceAccountId: Types.ObjectId;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||
|
||||
if (!serviceAccount) {
|
||||
throw ServiceAccountNotFoundError({
|
||||
message: 'Failed to find service account'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForServiceAccount({
|
||||
user: authData.authPayload,
|
||||
serviceAccount,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return serviceAccount;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForServiceAccount({
|
||||
serviceAccount: authData.authPayload,
|
||||
targetServiceAccount: serviceAccount,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return serviceAccount;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for service account resource'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForServiceAccount({
|
||||
user: authData.authPayload,
|
||||
serviceAccount,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return serviceAccount;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for service account resource'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that service account (client) can access workspace
|
||||
* with id [workspaceId] and its environment [environment] with required permissions
|
||||
* [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {ServiceAccount} obj.serviceAccount - service account 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 validateServiceAccountClientForWorkspace = async ({
|
||||
serviceAccount,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
}: {
|
||||
serviceAccount: IServiceAccount;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
if (environment) {
|
||||
// case: environment specified ->
|
||||
// evaluate service account authorization for workspace
|
||||
// in the context of a specific environment [environment]
|
||||
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||
serviceAccount,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
});
|
||||
|
||||
if (!permission) throw UnauthorizedRequestError({
|
||||
message: 'Failed service account authorization for the given workspace environment'
|
||||
});
|
||||
|
||||
let runningIsDisallowed = false;
|
||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||
switch (requiredPermission) {
|
||||
case PERMISSION_READ_SECRETS:
|
||||
if (!permission.read) runningIsDisallowed = true;
|
||||
break;
|
||||
case PERMISSION_WRITE_SECRETS:
|
||||
if (!permission.write) runningIsDisallowed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (runningIsDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// case: no environment specified ->
|
||||
// evaluate service account authorization for workspace
|
||||
// without need of environment [environment]
|
||||
|
||||
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||
serviceAccount,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!permission) throw UnauthorizedRequestError({
|
||||
message: 'Failed service account authorization for the given workspace'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that service account (client) can access secrets
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {ServiceAccount} obj.serviceAccount - service account client
|
||||
* @param {Secret[]} secrets - secrets to validate against
|
||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceAccountClientForSecrets = async ({
|
||||
serviceAccount,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
}: {
|
||||
serviceAccount: IServiceAccount;
|
||||
secrets: ISecret[];
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
const permissions = await ServiceAccountWorkspacePermission.find({
|
||||
serviceAccount: serviceAccount._id
|
||||
});
|
||||
|
||||
const permissionsObj = _.keyBy(permissions, (p) => {
|
||||
return `${p.workspace.toString()}-${p.environment}`
|
||||
});
|
||||
|
||||
secrets.forEach((secret: ISecret) => {
|
||||
const permission = permissionsObj[`${secret.workspace.toString()}-${secret.environment}`];
|
||||
|
||||
if (!permission) throw BadRequestError({
|
||||
message: 'Failed to find any permission for the secret workspace and environment'
|
||||
});
|
||||
|
||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||
let runningIsDisallowed = false;
|
||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||
switch (requiredPermission) {
|
||||
case PERMISSION_READ_SECRETS:
|
||||
if (!permission.read) runningIsDisallowed = true;
|
||||
break;
|
||||
case PERMISSION_WRITE_SECRETS:
|
||||
if (!permission.write) runningIsDisallowed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (runningIsDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that service account (client) can access target service
|
||||
* account [serviceAccount] with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {SerivceAccount} obj.serviceAccount - service account client
|
||||
* @param {ServiceAccount} targetServiceAccount - target service account to validate against
|
||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceAccountClientForServiceAccount = ({
|
||||
serviceAccount,
|
||||
targetServiceAccount,
|
||||
requiredPermissions
|
||||
}: {
|
||||
serviceAccount: IServiceAccount;
|
||||
targetServiceAccount: IServiceAccount;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
if (!serviceAccount.organization.equals(targetServiceAccount.organization)) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service account authorization for the given service account'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that service account (client) can access organization [organization]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - service account client
|
||||
* @param {Organization} obj.organization - organization to validate against
|
||||
*/
|
||||
const validateServiceAccountClientForOrganization = async ({
|
||||
serviceAccount,
|
||||
organization
|
||||
}: {
|
||||
serviceAccount: IServiceAccount;
|
||||
organization: IOrganization;
|
||||
}) => {
|
||||
if (!serviceAccount.organization.equals(organization._id)) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service account authorization for the given organization'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForServiceAccount,
|
||||
validateServiceAccountClientForWorkspace,
|
||||
validateServiceAccountClientForSecrets,
|
||||
validateServiceAccountClientForServiceAccount,
|
||||
validateServiceAccountClientForOrganization
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
ISecret,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
} from '../models';
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
ServiceTokenDataNotFoundError
|
||||
} 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';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for service token with id [serviceTokenId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.serviceTokenData - id of service token to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
*/
|
||||
const validateClientForServiceTokenData = async ({
|
||||
authData,
|
||||
serviceTokenDataId,
|
||||
acceptedRoles
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
serviceTokenDataId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
}) => {
|
||||
const serviceTokenData = await ServiceTokenData
|
||||
.findById(serviceTokenDataId)
|
||||
.select('+encryptedKey +iv +tag')
|
||||
.populate<{ user: IUser }>('user');
|
||||
|
||||
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({
|
||||
message: 'Failed to find service token data'
|
||||
});
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: serviceTokenData.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return serviceTokenData;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: serviceTokenData.workspace
|
||||
});
|
||||
|
||||
return serviceTokenData;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for service token data'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: serviceTokenData.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return serviceTokenData;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for service token data'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that service token (client) can access workspace
|
||||
* with id [workspaceId] and its environment [environment] with required permissions
|
||||
* [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {ServiceTokenData} obj.serviceTokenData - service token 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 validateServiceTokenDataClientForWorkspace = async ({
|
||||
serviceTokenData,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
}: {
|
||||
serviceTokenData: IServiceTokenData;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
if (!serviceTokenData.workspace.equals(workspaceId)) {
|
||||
// case: invalid workspaceId passed
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for the given workspace'
|
||||
});
|
||||
}
|
||||
|
||||
if (environment) {
|
||||
// case: environment is specified
|
||||
|
||||
if (serviceTokenData.environment !== environment) {
|
||||
// case: invalid environment passed
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for the given workspace environment'
|
||||
});
|
||||
}
|
||||
|
||||
requiredPermissions?.forEach((permission) => {
|
||||
if (!serviceTokenData.permissions.includes(permission)) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: `Failed service token authorization for the given workspace environment action: ${permission}`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that service token (client) can access secrets
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {ServiceTokenData} obj.serviceTokenData - service token client
|
||||
* @param {Secret[]} secrets - secrets to validate against
|
||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceTokenDataClientForSecrets = async ({
|
||||
serviceTokenData,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
}: {
|
||||
serviceTokenData: IServiceTokenData;
|
||||
secrets: ISecret[];
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
secrets.forEach((secret: ISecret) => {
|
||||
if (!serviceTokenData.workspace.equals(secret.workspace)) {
|
||||
// case: invalid workspaceId passed
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for the given workspace'
|
||||
});
|
||||
}
|
||||
|
||||
if (serviceTokenData.environment !== secret.environment) {
|
||||
// case: invalid environment passed
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for the given workspace environment'
|
||||
});
|
||||
}
|
||||
|
||||
requiredPermissions?.forEach((permission) => {
|
||||
if (!serviceTokenData.permissions.includes(permission)) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: `Failed service token authorization for the given workspace environment action: ${permission}`
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForServiceTokenData,
|
||||
validateServiceTokenDataClientForWorkspace,
|
||||
validateServiceTokenDataClientForSecrets
|
||||
}
|
||||
+4
-222
@@ -1,24 +1,8 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
ISecret,
|
||||
IServiceAccount,
|
||||
User,
|
||||
Membership,
|
||||
IOrganization,
|
||||
Organization,
|
||||
} from '../models';
|
||||
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]
|
||||
@@ -26,7 +10,7 @@ import {
|
||||
* @param {String} obj.email - email of user to initialize
|
||||
* @returns {Object} user - the initialized user
|
||||
*/
|
||||
const setupAccount = async ({ email }: { email: string }) => {
|
||||
export const setupAccount = async ({ email }: { email: string }) => {
|
||||
const user = await new User({
|
||||
email
|
||||
}).save();
|
||||
@@ -52,7 +36,7 @@ const setupAccount = async ({ email }: { email: string }) => {
|
||||
* @param {String} obj.verifier - verifier for auth SRP
|
||||
* @returns {Object} user - the completed user
|
||||
*/
|
||||
const completeAccount = async ({
|
||||
export const completeAccount = async ({
|
||||
userId,
|
||||
firstName,
|
||||
lastName,
|
||||
@@ -113,7 +97,7 @@ const completeAccount = async ({
|
||||
* @param {String} obj.ip - login ip address
|
||||
* @param {String} obj.userAgent - login user-agent
|
||||
*/
|
||||
const checkUserDevice = async ({
|
||||
export const checkUserDevice = async ({
|
||||
user,
|
||||
ip,
|
||||
userAgent
|
||||
@@ -148,206 +132,4 @@ 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 crypto from 'crypto';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Workspace,
|
||||
Bot,
|
||||
Membership,
|
||||
Key,
|
||||
Secret,
|
||||
User,
|
||||
IUser,
|
||||
ServiceAccountWorkspacePermission,
|
||||
ServiceAccount,
|
||||
IServiceAccount,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
SecretBlindIndexData
|
||||
Secret
|
||||
} from '../models';
|
||||
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';
|
||||
|
||||
/**
|
||||
* 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]
|
||||
* and a bot for it.
|
||||
@@ -202,7 +81,6 @@ const deleteWorkspace = async ({ id }: { id: string }) => {
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForWorkspace,
|
||||
createWorkspace,
|
||||
deleteWorkspace
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user