mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Begin refactoring middleware for service accounts
This commit is contained in:
83
backend/package-lock.json
generated
83
backend/package-lock.json
generated
@@ -12,8 +12,8 @@
|
||||
"@aws-sdk/client-secrets-manager": "^3.281.0",
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/tracing": "^7.39.0",
|
||||
"@sentry/node": "^7.40.0",
|
||||
"@sentry/tracing": "^7.39.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"await-to-js": "^3.0.0",
|
||||
@@ -2988,24 +2988,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.38.0.tgz",
|
||||
"integrity": "sha512-+hXh/SO3Ie6WC2b+wi01xLhyVREdkRXS5QBmCiv3z2ks2HvYXp7PoKSXJvNKiwCP+pBD+enOnM1YEzM2yEy5yw==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.38.0",
|
||||
"@sentry/utils": "7.38.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@sentry/node": {
|
||||
"version": "7.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.40.0.tgz",
|
||||
@@ -3113,31 +3095,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@sentry/types": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.38.0.tgz",
|
||||
"integrity": "sha512-NKOALR6pNUMzUrsk2m+dkPrO8uGNvNh1LD0BCPswKNjC2qHo1h1mDGCgBmF9+EWyii8ZoACTIsxvsda+MBf97Q==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/utils": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.38.0.tgz",
|
||||
"integrity": "sha512-MgbI3YmYuyyhUtvcXkgGBqjOW+nuLLNGUdWCK+C4kObf8VbLt3dSE/7SEMT6TSHLYQmxs2BxFgx5Agn97m68kQ==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.38.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/utils/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.25.24",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
||||
@@ -14804,23 +14761,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"@sentry/core": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.38.0.tgz",
|
||||
"integrity": "sha512-+hXh/SO3Ie6WC2b+wi01xLhyVREdkRXS5QBmCiv3z2ks2HvYXp7PoKSXJvNKiwCP+pBD+enOnM1YEzM2yEy5yw==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.38.0",
|
||||
"@sentry/utils": "7.38.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/node": {
|
||||
"version": "7.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.40.0.tgz",
|
||||
@@ -14908,27 +14848,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.38.0.tgz",
|
||||
"integrity": "sha512-NKOALR6pNUMzUrsk2m+dkPrO8uGNvNh1LD0BCPswKNjC2qHo1h1mDGCgBmF9+EWyii8ZoACTIsxvsda+MBf97Q=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.38.0.tgz",
|
||||
"integrity": "sha512-MgbI3YmYuyyhUtvcXkgGBqjOW+nuLLNGUdWCK+C4kObf8VbLt3dSE/7SEMT6TSHLYQmxs2BxFgx5Agn97m68kQ==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.38.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sinclair/typebox": {
|
||||
"version": "0.25.24",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { pushKeys } from '../../helpers/key';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
import { EventService } from '../../services';
|
||||
import { getPostHogClient } from '../../services';
|
||||
import { TelemetryService } from '../../services';
|
||||
|
||||
interface PushSecret {
|
||||
ciphertextKey: string;
|
||||
@@ -38,7 +38,7 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||
|
||||
try {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
let { secrets }: { secrets: PushSecret[] } = req.body;
|
||||
const { keys, environment, channel } = req.body;
|
||||
const { workspaceId } = req.params;
|
||||
@@ -112,7 +112,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
let key;
|
||||
try {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
@@ -181,7 +181,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
let key;
|
||||
try {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
@@ -7,7 +7,7 @@ const { ValidationError } = mongoose.Error;
|
||||
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
|
||||
import { AnyBulkWriteOperation } from 'mongodb';
|
||||
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
|
||||
import { getPostHogClient } from '../../services';
|
||||
import { TelemetryService } from '../../services';
|
||||
|
||||
/**
|
||||
* Create secret for workspace with id [workspaceId] and environment [environment]
|
||||
@@ -15,7 +15,7 @@ import { getPostHogClient } from '../../services';
|
||||
* @param res
|
||||
*/
|
||||
export const createSecret = async (req: Request, res: Response) => {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const secretToCreate: CreateSecretRequestBody = req.body.secret;
|
||||
const { workspaceId, environment } = req.params
|
||||
const sanitizedSecret: SanitizedSecretForCreate = {
|
||||
@@ -68,7 +68,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const createSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
|
||||
const { workspaceId, environment } = req.params
|
||||
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
|
||||
@@ -130,7 +130,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretIdsToDelete: string[] = req.body.secretIds
|
||||
|
||||
@@ -184,7 +184,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecret = async (req: Request, res: Response) => {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
await Secret.findByIdAndDelete(req._secret._id)
|
||||
|
||||
if (postHogClient) {
|
||||
@@ -213,7 +213,7 @@ export const deleteSecret = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const updateSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
|
||||
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
|
||||
@@ -281,7 +281,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const updateSecret = async (req: Request, res: Response) => {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
||||
|
||||
@@ -335,7 +335,7 @@ export const updateSecret = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const getSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const { environment } = req.query;
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
||||
import { EventService } from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
import { EESecretService, EELogService } from '../../ee/services';
|
||||
import { getPostHogClient } from '../../services';
|
||||
import { TelemetryService } from '../../services';
|
||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||
import Tag from '../../models/tag';
|
||||
import _ from 'lodash';
|
||||
import _, { eq } from 'lodash';
|
||||
import {
|
||||
BatchSecretRequest,
|
||||
BatchSecret
|
||||
@@ -28,12 +28,13 @@ import {
|
||||
|
||||
/**
|
||||
* Peform a batch of any specified CUD secret operations
|
||||
* (used by dashboard)
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const batchSecrets = async (req: Request, res: Response) => {
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent']);
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
|
||||
const {
|
||||
workspaceId,
|
||||
@@ -91,7 +92,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
const addAction = await EELogService.createAction({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
userId: req.user._id,
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: createdSecrets.map((n) => n._id)
|
||||
}) as IAction;
|
||||
@@ -328,14 +331,15 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
const postHogClient = getPostHogClient();
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
||||
|
||||
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_WRITE)
|
||||
if (!hasAccess) {
|
||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||
if (req.user) {
|
||||
const hasAccess = await userHasWorkspaceAccess(req.user, new Types.ObjectId(workspaceId), environment, ABILITY_WRITE)
|
||||
if (!hasAccess) {
|
||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||
}
|
||||
}
|
||||
|
||||
let listOfSecretsToCreate;
|
||||
@@ -378,7 +382,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
version: 1,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type,
|
||||
user: type === SECRET_PERSONAL ? req.user : undefined,
|
||||
user: (req.user && type === SECRET_PERSONAL) ? req.user : undefined,
|
||||
environment,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
@@ -391,7 +395,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentTag,
|
||||
tags
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
|
||||
|
||||
@@ -447,14 +451,18 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
const addAction = await EELogService.createAction({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
userId: req.user._id,
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
addAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions: [addAction],
|
||||
channel,
|
||||
@@ -466,10 +474,15 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId
|
||||
});
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets added',
|
||||
distinctId: req.user.email,
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: listOfSecretsToCreate.length,
|
||||
environment,
|
||||
@@ -533,91 +546,118 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
|
||||
const postHogClient = getPostHogClient();
|
||||
const { tagSlugs } = req.query;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
|
||||
const { workspaceId, environment, tagSlugs } = req.query;
|
||||
// tags logic
|
||||
let tagIds = [];
|
||||
const tagNamesList = typeof tagSlugs === 'string' && tagSlugs !== '' ? tagSlugs.split(',') : [];
|
||||
let userId = "" // used for getting personal secrets for user
|
||||
let userEmail = "" // used for posthog
|
||||
if (req.user) {
|
||||
userId = req.user._id;
|
||||
userEmail = req.user.email;
|
||||
}
|
||||
|
||||
if (req.serviceTokenData) {
|
||||
userId = req.serviceTokenData.user._id
|
||||
userEmail = req.serviceTokenData.user.email;
|
||||
}
|
||||
|
||||
// none service token case as service tokens are already scoped to env and project
|
||||
let hasWriteOnlyAccess
|
||||
if (!req.serviceTokenData) {
|
||||
hasWriteOnlyAccess = await userHasWriteOnlyAbility(userId, workspaceId, environment)
|
||||
const hasNoAccess = await userHasNoAbility(userId, workspaceId, environment)
|
||||
if (hasNoAccess) {
|
||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||
}
|
||||
}
|
||||
let secrets: any
|
||||
let secretQuery: any
|
||||
|
||||
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId })
|
||||
|
||||
const tagIds = _.map(tagNamesList, (tagName) => {
|
||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId });
|
||||
tagIds = _.map(tagNamesList, (tagName) => {
|
||||
const tag = _.find(workspaceFromDB, { slug: tagName });
|
||||
return tag ? tag.id : null;
|
||||
});
|
||||
}
|
||||
|
||||
let secrets: ISecret[] = [];
|
||||
|
||||
if (req.user) {
|
||||
// case: client authorization is via JWT
|
||||
|
||||
secretQuery = {
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
$or: [
|
||||
{ user: userId },
|
||||
{ user: { $exists: false } }
|
||||
],
|
||||
tags: { $in: tagIds },
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
let hasWriteOnlyAccess
|
||||
if (!req.serviceTokenData) {
|
||||
hasWriteOnlyAccess = await userHasWriteOnlyAbility(req.user._id, new Types.ObjectId(workspaceId), environment)
|
||||
const hasNoAccess = await userHasNoAbility(req.user._id, new Types.ObjectId(workspaceId), environment)
|
||||
if (hasNoAccess) {
|
||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
secretQuery = {
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
$or: [
|
||||
{ user: userId },
|
||||
{ user: { $exists: false } }
|
||||
],
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
|
||||
let secretQuery: any;
|
||||
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId })
|
||||
|
||||
const tagIds = _.map(tagNamesList, (tagName) => {
|
||||
const tag = _.find(workspaceFromDB, { slug: tagName });
|
||||
return tag ? tag.id : null;
|
||||
});
|
||||
|
||||
secretQuery = {
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
$or: [
|
||||
{ user: req.user._id },
|
||||
{ user: { $exists: false } }
|
||||
],
|
||||
tags: { $in: tagIds },
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
}
|
||||
} else {
|
||||
secretQuery = {
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
$or: [
|
||||
{ user: req.user._id },
|
||||
{ user: { $exists: false } }
|
||||
],
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWriteOnlyAccess) {
|
||||
// (i.e. you don't get values to decrypt since you can only write)
|
||||
secrets = await Secret.find(secretQuery).select("secretKeyCiphertext secretKeyIV secretKeyTag")
|
||||
} else {
|
||||
secrets = await Secret.find(secretQuery).populate("tags")
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWriteOnlyAccess) {
|
||||
secrets = await Secret.find(secretQuery).select("secretKeyCiphertext secretKeyIV secretKeyTag")
|
||||
} else {
|
||||
secrets = await Secret.find(secretQuery).populate("tags")
|
||||
|
||||
if (req.serviceAccount || req.serviceTokenData) {
|
||||
// case: client authorization is either via service account or service token
|
||||
|
||||
secrets = await Secret.find({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
user: {
|
||||
$exists: false
|
||||
},
|
||||
...(tagIds.length > 0 ? { tags: { $in: tagIds } } : {}),
|
||||
type: SECRET_SHARED
|
||||
});
|
||||
}
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||
|
||||
const readAction = await EELogService.createAction({
|
||||
name: ACTION_READ_SECRETS,
|
||||
userId: new Types.ObjectId(userId),
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
secretIds: secrets.map((n: any) => n._id)
|
||||
});
|
||||
|
||||
|
||||
readAction && await EELogService.createLog({
|
||||
userId: new Types.ObjectId(userId),
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
actions: [readAction],
|
||||
channel,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pulled',
|
||||
distinctId: userEmail,
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
@@ -633,59 +673,6 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const getOnlySecretKeys = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment } = req.query;
|
||||
|
||||
let userId = "" // used for getting personal secrets for user
|
||||
let userEmail = "" // used for posthog
|
||||
if (req.user) {
|
||||
userId = req.user._id;
|
||||
userEmail = req.user.email;
|
||||
}
|
||||
|
||||
if (req.serviceTokenData) {
|
||||
userId = req.serviceTokenData.user._id
|
||||
userEmail = req.serviceTokenData.user.email;
|
||||
}
|
||||
|
||||
// none service token case as service tokens are already scoped
|
||||
if (!req.serviceTokenData) {
|
||||
const hasAccess = await userHasWorkspaceAccess(userId, workspaceId, environment, ABILITY_READ)
|
||||
if (!hasAccess) {
|
||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||
}
|
||||
}
|
||||
|
||||
const [err, secretKeys] = await to(Secret.find(
|
||||
{
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
$or: [
|
||||
{ user: userId },
|
||||
{ user: { $exists: false } }
|
||||
],
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
}
|
||||
)
|
||||
.select("secretKeyIV secretKeyTag secretKeyCiphertext")
|
||||
.then())
|
||||
|
||||
if (err) throw ValidationError({ message: 'Failed to get secrets', stack: err.stack });
|
||||
|
||||
// readAction && await EELogService.createLog({
|
||||
// userId: new Types.ObjectId(userId),
|
||||
// workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
// actions: [readAction],
|
||||
// channel,
|
||||
// ipAddress: req.ip
|
||||
// });
|
||||
|
||||
return res.status(200).send({
|
||||
secretKeys
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secret(s)
|
||||
* @param req
|
||||
@@ -736,10 +723,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
const postHogClient = getPostHogClient();
|
||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||
|
||||
// TODO: move type
|
||||
interface PatchSecret {
|
||||
id: string;
|
||||
secretKeyCiphertext: string;
|
||||
@@ -865,14 +850,18 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
const updateAction = await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
userId: req.user._id,
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
updateAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [updateAction],
|
||||
channel,
|
||||
@@ -884,10 +873,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: key
|
||||
})
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets modified',
|
||||
distinctId: req.user.email,
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
@@ -909,7 +903,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secret(s) with id [workspaceId] and environment [environment]
|
||||
* Delete secret(s)
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
@@ -958,7 +952,11 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
const postHogClient = getPostHogClient();
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'delete secrets!!'
|
||||
});
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||
const toDelete = req.secrets.map((s: any) => s._id);
|
||||
|
||||
@@ -992,14 +990,18 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
});
|
||||
const deleteAction = await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id,
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
deleteAction && await EELogService.createLog({
|
||||
userId: req.user._id.toString(),
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [deleteAction],
|
||||
channel,
|
||||
@@ -1011,10 +1013,15 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: key
|
||||
})
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: req.user.email,
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
|
||||
@@ -195,10 +195,8 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
|
||||
const {
|
||||
environment,
|
||||
workspaceId,
|
||||
canRead = false,
|
||||
canWrite = false,
|
||||
canUpdate = false,
|
||||
canDelete = false,
|
||||
read = false,
|
||||
write = false,
|
||||
encryptedKey,
|
||||
nonce
|
||||
} = req.body;
|
||||
@@ -221,10 +219,8 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
|
||||
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
canRead,
|
||||
canWrite,
|
||||
canUpdate,
|
||||
canDelete
|
||||
read,
|
||||
write
|
||||
}).save();
|
||||
|
||||
const existingServiceAccountKey = await ServiceAccountKey.findOne({
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
reformatPullSecrets
|
||||
} from '../../helpers/secret';
|
||||
import { pushKeys } from '../../helpers/key';
|
||||
import { getPostHogClient, EventService } from '../../services';
|
||||
import { TelemetryService, EventService } from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
|
||||
interface V2PushSecret {
|
||||
@@ -48,7 +48,7 @@ interface V2PushSecret {
|
||||
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||
try {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||
const { keys, environment, channel } = req.body;
|
||||
const { workspaceId } = req.params;
|
||||
@@ -122,7 +122,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
export const pullSecrets = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
try {
|
||||
const postHogClient = getPostHogClient();
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
@@ -24,11 +24,15 @@ import {
|
||||
const createActionUpdateSecret = async ({
|
||||
name,
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
workspaceId: Types.ObjectId;
|
||||
secretIds: Types.ObjectId[];
|
||||
}) => {
|
||||
@@ -46,6 +50,8 @@ const createActionUpdateSecret = async ({
|
||||
action = await new Action({
|
||||
name,
|
||||
user: userId,
|
||||
serviceAccount: serviceAccountId,
|
||||
serviceTokenData: serviceTokenDataId,
|
||||
workspace: workspaceId,
|
||||
payload: {
|
||||
secretVersions: latestSecretVersions
|
||||
@@ -72,11 +78,15 @@ const createActionUpdateSecret = async ({
|
||||
const createActionSecret = async ({
|
||||
name,
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
workspaceId: Types.ObjectId;
|
||||
secretIds: Types.ObjectId[];
|
||||
}) => {
|
||||
@@ -94,6 +104,8 @@ const createActionSecret = async ({
|
||||
action = await new Action({
|
||||
name,
|
||||
user: userId,
|
||||
serviceAccount: serviceAccountId,
|
||||
serviceTokenData: serviceTokenDataId,
|
||||
workspace: workspaceId,
|
||||
payload: {
|
||||
secretVersions: latestSecretVersions
|
||||
@@ -110,29 +122,36 @@ const createActionSecret = async ({
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an (audit) action for user with id [userId]
|
||||
* Create an (audit) action for client with id [userId],
|
||||
* [serviceAccountId], or [serviceTokenDataId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.name - name of action
|
||||
* @param {String} obj.userId - id of user associated with action
|
||||
* @returns
|
||||
*/
|
||||
const createActionUser = ({
|
||||
const createActionClient = ({
|
||||
name,
|
||||
userId
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
}) => {
|
||||
let action;
|
||||
try {
|
||||
action = new Action({
|
||||
name,
|
||||
user: userId
|
||||
user: userId,
|
||||
serviceAccount: serviceAccountId,
|
||||
serviceTokenData: serviceTokenDataId
|
||||
}).save();
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to create user action');
|
||||
throw new Error('Failed to create client action');
|
||||
}
|
||||
|
||||
return action;
|
||||
@@ -149,11 +168,15 @@ const createActionUser = ({
|
||||
const createActionHelper = async ({
|
||||
name,
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
secretIds,
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
secretIds?: Types.ObjectId[];
|
||||
}) => {
|
||||
@@ -162,7 +185,7 @@ const createActionHelper = async ({
|
||||
switch (name) {
|
||||
case ACTION_LOGIN:
|
||||
case ACTION_LOGOUT:
|
||||
action = await createActionUser({
|
||||
action = await createActionClient({
|
||||
name,
|
||||
userId
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Types } from 'mongoose';
|
||||
import _ from "lodash";
|
||||
import { Membership } from "../../models";
|
||||
import { ABILITY_READ, ABILITY_WRITE } from "../../variables/organization";
|
||||
|
||||
export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, environment: any, action: any) => {
|
||||
export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => {
|
||||
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||
if (!membershipForWorkspace) {
|
||||
return false
|
||||
@@ -18,7 +19,7 @@ export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, envi
|
||||
return true
|
||||
}
|
||||
|
||||
export const userHasWriteOnlyAbility = async (userId: any, workspaceId: any, environment: any) => {
|
||||
export const userHasWriteOnlyAbility = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string) => {
|
||||
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||
if (!membershipForWorkspace) {
|
||||
return false
|
||||
@@ -36,7 +37,7 @@ export const userHasWriteOnlyAbility = async (userId: any, workspaceId: any, env
|
||||
return false
|
||||
}
|
||||
|
||||
export const userHasNoAbility = async (userId: any, workspaceId: any, environment: any) => {
|
||||
export const userHasNoAbility = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string) => {
|
||||
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||
if (!membershipForWorkspace) {
|
||||
return true
|
||||
|
||||
@@ -16,12 +16,16 @@ import {
|
||||
*/
|
||||
const createLogHelper = async ({
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
actions,
|
||||
channel,
|
||||
ipAddress
|
||||
}: {
|
||||
userId: Types.ObjectId;
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
actions: IAction[];
|
||||
channel: string;
|
||||
@@ -31,6 +35,8 @@ const createLogHelper = async ({
|
||||
try {
|
||||
log = await new Log({
|
||||
user: userId,
|
||||
serviceAccount: serviceAccountId,
|
||||
serviceTokenData: serviceTokenDataId,
|
||||
workspace: workspaceId ?? undefined,
|
||||
actionNames: actions.map((a) => a.name),
|
||||
actions,
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
export interface IAction {
|
||||
name: string;
|
||||
user?: Types.ObjectId,
|
||||
serviceAccount?: Types.ObjectId,
|
||||
serviceTokenData?: Types.ObjectId,
|
||||
workspace?: Types.ObjectId,
|
||||
payload?: {
|
||||
secretVersions?: Types.ObjectId[]
|
||||
@@ -33,8 +35,15 @@ const actionSchema = new Schema<IAction>(
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
ref: 'User'
|
||||
},
|
||||
serviceAccount: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'ServiceAccount'
|
||||
},
|
||||
serviceTokenData: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'ServiceTokenData'
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
export interface ILog {
|
||||
_id: Types.ObjectId;
|
||||
user?: Types.ObjectId;
|
||||
serviceAccount?: Types.ObjectId;
|
||||
serviceTokenData?: Types.ObjectId;
|
||||
workspace?: Types.ObjectId;
|
||||
actionNames: string[];
|
||||
actions: Types.ObjectId[];
|
||||
@@ -24,6 +26,14 @@ const logSchema = new Schema<ILog>(
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
serviceAccount: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'ServiceAccount'
|
||||
},
|
||||
serviceTokenData: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'ServiceTokenData'
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Workspace'
|
||||
|
||||
@@ -26,12 +26,16 @@ class EELogService {
|
||||
*/
|
||||
static async createLog({
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
actions,
|
||||
channel,
|
||||
ipAddress
|
||||
}: {
|
||||
userId: Types.ObjectId;
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
actions: IAction[];
|
||||
channel: string;
|
||||
@@ -40,6 +44,8 @@ class EELogService {
|
||||
if (!EELicenseService.isLicenseValid) return null;
|
||||
return await createLogHelper({
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
actions,
|
||||
channel,
|
||||
@@ -59,17 +65,23 @@ class EELogService {
|
||||
static async createAction({
|
||||
name,
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
}: {
|
||||
name: string;
|
||||
userId: Types.ObjectId;
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
workspaceId?: Types.ObjectId;
|
||||
secretIds?: Types.ObjectId[];
|
||||
}) {
|
||||
return await createActionHelper({
|
||||
name,
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
workspaceId,
|
||||
secretIds
|
||||
});
|
||||
|
||||
@@ -22,6 +22,12 @@ import {
|
||||
getJwtRefreshLifetime,
|
||||
getJwtRefreshSecret
|
||||
} from '../config';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -39,7 +45,7 @@ const validateAuthMode = ({
|
||||
const apiKey = headers['x-api-key'];
|
||||
const authHeader = headers['authorization'];
|
||||
|
||||
let authTokenType, authTokenValue;
|
||||
let authMode, authTokenValue;
|
||||
if (apiKey === undefined && authHeader === undefined) {
|
||||
// case: no auth or X-API-KEY header present
|
||||
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' });
|
||||
@@ -47,7 +53,7 @@ const validateAuthMode = ({
|
||||
|
||||
if (typeof apiKey === 'string') {
|
||||
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
||||
authTokenType = 'apiKey';
|
||||
authMode = AUTH_MODE_API_KEY;
|
||||
authTokenValue = apiKey;
|
||||
}
|
||||
|
||||
@@ -63,24 +69,24 @@ const validateAuthMode = ({
|
||||
|
||||
switch (tokenValue.split('.', 1)[0]) {
|
||||
case 'st':
|
||||
authTokenType = 'serviceToken';
|
||||
authMode = AUTH_MODE_SERVICE_TOKEN;
|
||||
break;
|
||||
case 'sa':
|
||||
authTokenType = 'serviceAccount';
|
||||
authMode = AUTH_MODE_SERVICE_ACCOUNT;
|
||||
break;
|
||||
default:
|
||||
authTokenType = 'jwt';
|
||||
authMode = AUTH_MODE_JWT;
|
||||
}
|
||||
|
||||
authTokenValue = tokenValue;
|
||||
}
|
||||
|
||||
if (!authTokenType || !authTokenValue) throw BadRequestError({ message: 'Missing valid Authorization or X-API-KEY in request header.' });
|
||||
if (!authMode || !authTokenValue) throw BadRequestError({ message: 'Missing valid Authorization or X-API-KEY in request header.' });
|
||||
|
||||
if (!acceptedAuthModes.includes(authTokenType)) throw BadRequestError({ message: 'The provided authentication type is not supported.' });
|
||||
if (!acceptedAuthModes.includes(authMode)) throw BadRequestError({ message: 'The provided authentication type is not supported.' });
|
||||
|
||||
return ({
|
||||
authTokenType,
|
||||
authMode,
|
||||
authTokenValue
|
||||
});
|
||||
}
|
||||
@@ -155,8 +161,7 @@ const getAuthSTDPayload = async ({
|
||||
|
||||
serviceTokenData = await ServiceTokenData
|
||||
.findById(TOKEN_IDENTIFIER)
|
||||
.select('+encryptedKey +iv +tag')
|
||||
.populate<{user: IUser}>('user');
|
||||
.select('+encryptedKey +iv +tag');
|
||||
|
||||
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
||||
|
||||
@@ -216,7 +221,7 @@ const getAuthAPIKeyPayload = async ({
|
||||
|
||||
const apiKeyData = await APIKeyData
|
||||
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt')
|
||||
.populate('user', '+publicKey');
|
||||
.populate<{user: IUser}>('user', '+publicKey');
|
||||
|
||||
if (!apiKeyData) {
|
||||
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
|
||||
|
||||
@@ -24,57 +24,6 @@ import _ from 'lodash';
|
||||
import { ABILITY_WRITE } from '../variables/organization';
|
||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||
|
||||
/**
|
||||
* Validate that user with id [userId] can modify secrets with ids [secretIds]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.userId - id of user to validate
|
||||
* @param {Object} obj.secretIds - secret ids
|
||||
* @returns {Secret[]} secrets
|
||||
*/
|
||||
const validateSecrets = async ({
|
||||
userId,
|
||||
secretIds
|
||||
}: {
|
||||
userId: string;
|
||||
secretIds: string[];
|
||||
}) => {
|
||||
let secrets;
|
||||
try {
|
||||
secrets = await Secret.find({
|
||||
_id: {
|
||||
$in: secretIds.map((secretId: string) => new Types.ObjectId(secretId))
|
||||
}
|
||||
});
|
||||
|
||||
if (secrets.length != secretIds.length) {
|
||||
throw BadRequestError({ message: 'Unable to validate some secrets' })
|
||||
}
|
||||
|
||||
const userMemberships = await Membership.find({ user: userId })
|
||||
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())) {
|
||||
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
||||
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: ABILITY_WRITE });
|
||||
|
||||
if (isDisallowed) {
|
||||
throw UnauthorizedRequestError({ message: 'You do not have the required permissions to perform this action' });
|
||||
}
|
||||
} else {
|
||||
throw BadRequestError({ message: 'You cannot edit secrets of a workspace you are not a member of' });
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
throw BadRequestError({ message: 'Unable to validate secrets' })
|
||||
}
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
interface V1PushSecret {
|
||||
ciphertextKey: string;
|
||||
ivKey: string;
|
||||
@@ -714,7 +663,6 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
||||
};
|
||||
|
||||
export {
|
||||
validateSecrets,
|
||||
v1PushSecrets,
|
||||
v2PushSecrets,
|
||||
pullSecrets,
|
||||
|
||||
102
backend/src/helpers/secrets.ts
Normal file
102
backend/src/helpers/secrets.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
User,
|
||||
IUser,
|
||||
ServiceAccount,
|
||||
IServiceAccount,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
Secret,
|
||||
ISecret
|
||||
} from '../models';
|
||||
import {
|
||||
validateUserClientForSecrets
|
||||
} from '../helpers/user';
|
||||
import {
|
||||
validateServiceTokenDataClientForSecrets
|
||||
} from '../helpers/serviceTokenData';
|
||||
import {
|
||||
validateServiceAccountClientForSecrets
|
||||
} from '../helpers/serviceAccount';
|
||||
import { BadRequestError } from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate accepted clients for secrets with ids [secretIds]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {ServiceAccount} obj.serviceAccount - service account client
|
||||
* @param {ServiceTokenData} obj.service - service token client
|
||||
* @param {String[]} obj.secretIds - ids of secrets to validate against
|
||||
*/
|
||||
const validateClientForSecrets = async ({
|
||||
authData,
|
||||
secretIds,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
},
|
||||
secretIds: string[];
|
||||
requiredPermissions: string[];
|
||||
}) => {
|
||||
|
||||
let secrets: ISecret[] = [];
|
||||
|
||||
secrets = await Secret.find({
|
||||
_id: {
|
||||
$in: secretIds.map((secretId: string) => new Types.ObjectId(secretId))
|
||||
}
|
||||
});
|
||||
|
||||
if (secrets.length != secretIds.length) {
|
||||
throw BadRequestError({ message: 'Failed to validate non-existent secrets' })
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
// TODO
|
||||
await validateUserClientForSecrets({
|
||||
user: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
// TODO
|
||||
await validateServiceAccountClientForSecrets({
|
||||
serviceAccount: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForSecrets({
|
||||
serviceTokenData: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
// TODO
|
||||
await validateUserClientForSecrets({
|
||||
user: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
}
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForSecrets
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import _ from 'lodash';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IServiceAccount,
|
||||
ISecret,
|
||||
ServiceAccountWorkspacePermission
|
||||
} from '../models';
|
||||
|
||||
/**
|
||||
* Validate that serviceAccount (client) can access workspace
|
||||
* with id [workspaceId] and its environment [environment] with required permissions
|
||||
* [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.
|
||||
*/
|
||||
const validateServiceAccountClientForWorkspace = async ({
|
||||
serviceAccount,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
}: {
|
||||
serviceAccount: IServiceAccount;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
requiredPermissions: string[];
|
||||
}) => {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that service account (client) can access secrets
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {ServiceTokenData} 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}`
|
||||
});
|
||||
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
export {
|
||||
validateServiceAccountClientForWorkspace,
|
||||
validateServiceAccountClientForSecrets
|
||||
}
|
||||
99
backend/src/helpers/serviceTokenData.ts
Normal file
99
backend/src/helpers/serviceTokenData.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
ISecret,
|
||||
IServiceTokenData
|
||||
} from '../models';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
|
||||
/**
|
||||
* 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 (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 {
|
||||
validateServiceTokenDataClientForWorkspace,
|
||||
validateServiceTokenDataClientForSecrets
|
||||
}
|
||||
0
backend/src/helpers/telemetry.ts
Normal file
0
backend/src/helpers/telemetry.ts
Normal file
@@ -1,6 +1,18 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { IUser, User } from '../models';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
ISecret,
|
||||
User,
|
||||
Membership
|
||||
} from '../models';
|
||||
import { sendMail } from './nodemailer';
|
||||
import { validateMembership } from './membership';
|
||||
import _ from 'lodash';
|
||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||
import {
|
||||
ABILITY_WRITE
|
||||
} from '../variables/organization';
|
||||
|
||||
/**
|
||||
* Initialize a user under email [email]
|
||||
@@ -146,4 +158,90 @@ const checkUserDevice = async ({
|
||||
}
|
||||
}
|
||||
|
||||
export { setupAccount, completeAccount, checkUserDevice };
|
||||
/**
|
||||
* 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,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
// org-level and workspace-level permissions? - workspace-level env scoped?
|
||||
|
||||
// validate user membership in workspace
|
||||
const membership = await validateMembership({
|
||||
userId: user._id,
|
||||
workspaceId
|
||||
});
|
||||
|
||||
// validate user permission
|
||||
|
||||
|
||||
// TODO: validate that user can perform action on environment in workspace
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access secrets with ids [secretIds]
|
||||
* 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: consider refactor
|
||||
|
||||
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())) {
|
||||
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
||||
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: ABILITY_WRITE });
|
||||
|
||||
if (isDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'You do not have the required permissions to perform this action'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
throw BadRequestError({
|
||||
message: 'You cannot edit secrets of a workspace you are not a member of'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
setupAccount,
|
||||
completeAccount,
|
||||
checkUserDevice,
|
||||
validateUserClientForWorkspace,
|
||||
validateUserClientForSecrets
|
||||
};
|
||||
|
||||
@@ -5,47 +5,98 @@ import {
|
||||
Bot,
|
||||
Membership,
|
||||
Key,
|
||||
Secret
|
||||
Secret,
|
||||
User,
|
||||
IUser,
|
||||
ServiceAccountWorkspacePermission,
|
||||
ServiceAccount,
|
||||
IServiceAccount,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
} 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 } from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate accepted clients by id including [userId], [serviceAccountId],
|
||||
* and [serviceTokenDataId] for workspace with id [workspaceId] based
|
||||
* Validate accepted clients for workspace with id [workspaceId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.userId - id of user
|
||||
* @param {User} obj.user - user client
|
||||
* @param {ServiceAccount} obj.serviceAccount - service account client
|
||||
* @param {ServiceTokenData} obj.serviceTokenData - service token client
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForWorkspace = async ({
|
||||
userId,
|
||||
serviceAccountId,
|
||||
serviceTokenDataId,
|
||||
authData,
|
||||
workspaceId,
|
||||
environment
|
||||
environment,
|
||||
requiredPermissions
|
||||
}: {
|
||||
userId?: Types.ObjectId;
|
||||
serviceAccountId?: Types.ObjectId;
|
||||
serviceTokenDataId?: Types.ObjectId;
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
},
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
|
||||
let membership;
|
||||
if (userId) {
|
||||
membership = await validateMembership({
|
||||
userId,
|
||||
workspaceId
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
// TODO: validate user against [requiredPermissions]
|
||||
}
|
||||
|
||||
if (serviceAccountId) {
|
||||
// TODO
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||
serviceAccount: authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
});
|
||||
|
||||
if (!permission) throw UnauthorizedRequestError({
|
||||
message: 'Failed service account authorization for the given workspace environment'
|
||||
});
|
||||
|
||||
// TODO: validate [requiredPermissions] against [permission]
|
||||
}
|
||||
|
||||
if (serviceTokenDataId) {
|
||||
// TODO
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
// TODO: validate [requiredPermissions] against [permission]
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
}
|
||||
|
||||
return ({
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as Sentry from '@sentry/node';
|
||||
import { DatabaseService } from './services';
|
||||
import { setUpHealthEndpoint } from './services/health';
|
||||
import { initSmtp } from './services/smtp';
|
||||
import { logTelemetryMessage } from './services';
|
||||
import { TelemetryService } from './services';
|
||||
import { setTransporter } from './helpers/nodemailer';
|
||||
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@@ -80,7 +80,7 @@ const main = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
logTelemetryMessage();
|
||||
TelemetryService.logTelemetryMessage();
|
||||
setTransporter(initSmtp());
|
||||
|
||||
await DatabaseService.initDatabase(getMongoURL());
|
||||
|
||||
@@ -10,6 +10,17 @@ import {
|
||||
import {
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData
|
||||
} from '../models';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
declare module 'jsonwebtoken' {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
@@ -28,59 +39,51 @@ declare module 'jsonwebtoken' {
|
||||
* @returns
|
||||
*/
|
||||
const requireAuth = ({
|
||||
acceptedAuthModes = ['jwt'],
|
||||
requiredServiceTokenPermissions = []
|
||||
acceptedAuthModes = [AUTH_MODE_JWT],
|
||||
}: {
|
||||
acceptedAuthModes: string[];
|
||||
requiredServiceTokenPermissions?: string[];
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||
// and return token type [authTokenType] and value [authTokenValue]
|
||||
const { authTokenType, authTokenValue } = validateAuthMode({
|
||||
const { authMode, authTokenValue } = validateAuthMode({
|
||||
headers: req.headers,
|
||||
acceptedAuthModes
|
||||
});
|
||||
|
||||
req.authTokenType = authTokenType;
|
||||
|
||||
// attach auth payloads
|
||||
let serviceTokenData: any;
|
||||
switch (authTokenType) {
|
||||
case 'serviceAccount':
|
||||
req.serviceAccount = await getAuthSAAKPayload({
|
||||
let authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
switch (authMode) {
|
||||
case AUTH_MODE_SERVICE_ACCOUNT:
|
||||
authPayload = await getAuthSAAKPayload({
|
||||
authTokenValue
|
||||
});
|
||||
req.serviceAccount = authPayload;
|
||||
break;
|
||||
case 'serviceToken':
|
||||
serviceTokenData = await getAuthSTDPayload({
|
||||
case AUTH_MODE_SERVICE_TOKEN:
|
||||
authPayload = await getAuthSTDPayload({
|
||||
authTokenValue
|
||||
});
|
||||
|
||||
// TODO: bring this into a separate collection
|
||||
requiredServiceTokenPermissions.forEach((requiredServiceTokenPermission) => {
|
||||
if (!serviceTokenData.permissions.includes(requiredServiceTokenPermission)) {
|
||||
return next(UnauthorizedRequestError({ message: 'Failed to authorize service token for endpoint' }));
|
||||
}
|
||||
});
|
||||
|
||||
req.serviceTokenData = serviceTokenData;
|
||||
req.user = serviceTokenData?.user;
|
||||
|
||||
req.serviceTokenData = authPayload;
|
||||
break;
|
||||
case 'apiKey':
|
||||
// TODO: deprecate API key
|
||||
req.user = await getAuthAPIKeyPayload({
|
||||
case AUTH_MODE_API_KEY:
|
||||
authPayload = await getAuthAPIKeyPayload({
|
||||
authTokenValue
|
||||
});
|
||||
req.user = authPayload;
|
||||
break;
|
||||
default:
|
||||
req.user = await getAuthUserPayload({
|
||||
authPayload = await getAuthUserPayload({
|
||||
authTokenValue
|
||||
});
|
||||
req.user = authPayload;
|
||||
break;
|
||||
}
|
||||
|
||||
req.authData = {
|
||||
authMode,
|
||||
authPayload
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,34 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
import { Secret, Membership } from '../models';
|
||||
import { validateSecrets } from '../helpers/secret';
|
||||
|
||||
// TODO: make this work for delete route
|
||||
import { validateClientForSecrets } from '../helpers/secrets';
|
||||
|
||||
const requireSecretsAuth = ({
|
||||
acceptedRoles
|
||||
acceptedRoles,
|
||||
requiredPermissions = []
|
||||
}: {
|
||||
acceptedRoles: string[];
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
let secrets;
|
||||
try {
|
||||
if (Array.isArray(req.body.secrets)) {
|
||||
// case: validate multiple secrets
|
||||
secrets = await validateSecrets({
|
||||
userId: req.user._id.toString(),
|
||||
secretIds: req.body.secrets.map((s: any) => s.id)
|
||||
});
|
||||
} else if (typeof req.body.secrets === 'object') { // change this to check for object
|
||||
// case: validate 1 secret
|
||||
secrets = await validateSecrets({
|
||||
userId: req.user._id.toString(),
|
||||
secretIds: [req.body.secrets.id]
|
||||
});
|
||||
} else if (Array.isArray(req.body.secretIds)) {
|
||||
secrets = await validateSecrets({
|
||||
userId: req.user._id.toString(),
|
||||
secretIds: req.body.secretIds
|
||||
});
|
||||
} else if (typeof req.body.secretIds === 'string') {
|
||||
// case: validate secretIds
|
||||
secrets = await validateSecrets({
|
||||
userId: req.user._id.toString(),
|
||||
secretIds: [req.body.secretIds]
|
||||
});
|
||||
}
|
||||
|
||||
req.secrets = secrets;
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret(s)' }));
|
||||
let secretIds = [];
|
||||
if (Array.isArray(req.body.secrets)) {
|
||||
secretIds = req.body.secrets.map((s: any) => s.id);
|
||||
} else if (typeof req.body.secrets === 'object') {
|
||||
secretIds = [req.body.secrets.id];
|
||||
} else if (Array.isArray(req.body.secretIds)) {
|
||||
secretIds = req.body.secretIds;
|
||||
} else if (typeof req.body.secretIds === 'string') {
|
||||
secretIds = [req.body.secretIds];
|
||||
}
|
||||
|
||||
req.secrets = await validateClientForSecrets({
|
||||
authData: req.authData,
|
||||
secretIds: [req.body.secretIds],
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,44 +16,32 @@ type req = 'params' | 'body' | 'query';
|
||||
const requireWorkspaceAuth = ({
|
||||
acceptedRoles,
|
||||
locationWorkspaceId,
|
||||
locationEnvironment = undefined
|
||||
locationEnvironment = undefined,
|
||||
requiredPermissions = []
|
||||
}: {
|
||||
acceptedRoles: string[];
|
||||
locationWorkspaceId: req;
|
||||
locationEnvironment?: req | undefined;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// TODO: throw errors if workspaceId or environemnt are not present
|
||||
|
||||
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
||||
|
||||
// validate clients
|
||||
const { membership } = await validateClientForWorkspace({
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
req.membership = membership;
|
||||
}
|
||||
|
||||
if (
|
||||
req.serviceTokenData
|
||||
&& req.serviceTokenData.workspace.toString() !== workspaceId
|
||||
&& req.serviceTokenData.environment !== req.body.environment
|
||||
) {
|
||||
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
||||
}
|
||||
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
||||
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
||||
|
||||
// validate clients
|
||||
const { membership } = await validateClientForWorkspace({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
req.membership = membership;
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@ export interface IServiceAccountWorkspacePermission extends Document {
|
||||
serviceAccount: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
canRead: boolean;
|
||||
canWrite: boolean;
|
||||
canUpdate: boolean;
|
||||
canDelete: boolean;
|
||||
read: boolean;
|
||||
write: boolean;
|
||||
}
|
||||
|
||||
const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorkspacePermission>(
|
||||
@@ -27,19 +25,11 @@ const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorksp
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
canRead: {
|
||||
read: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
canWrite: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
canUpdate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
canDelete: {
|
||||
write: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import { Schema, model, Types, Document } from 'mongoose';
|
||||
|
||||
export interface IServiceTokenData {
|
||||
export interface IServiceTokenData extends Document {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
|
||||
@@ -16,6 +16,7 @@ router.post(
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'params'
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body('environmentSlug').exists().trim(),
|
||||
@@ -31,6 +32,7 @@ router.put(
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'params'
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body('environmentSlug').exists().trim(),
|
||||
@@ -47,6 +49,7 @@ router.delete(
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
locationWorkspaceId: 'params'
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body('environmentSlug').exists().trim(),
|
||||
@@ -61,6 +64,7 @@ router.get(
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [MEMBER, ADMIN],
|
||||
locationWorkspaceId: 'params'
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
validateRequest,
|
||||
|
||||
@@ -8,12 +8,14 @@ import {
|
||||
} from '../../middleware';
|
||||
import { query, body } from 'express-validator';
|
||||
import { secretsController } from '../../controllers/v2';
|
||||
import { validateSecrets } from '../../helpers/secret';
|
||||
import { validateClientForSecrets } from '../../helpers/secrets';
|
||||
import {
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED
|
||||
SECRET_SHARED,
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS
|
||||
} from '../../variables';
|
||||
import {
|
||||
BatchSecretRequest
|
||||
@@ -22,8 +24,7 @@ import {
|
||||
router.post(
|
||||
'/batch',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||
requiredServiceTokenPermissions: ['read', 'write']
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@@ -40,12 +41,11 @@ router.post(
|
||||
.filter((secretId) => secretId !== undefined)
|
||||
|
||||
if (secretIds.length > 0) {
|
||||
const relevantSecrets = await validateSecrets({
|
||||
userId: req.user._id.toString(),
|
||||
secretIds
|
||||
req.secrets = await validateClientForSecrets({
|
||||
authData: req.authData,
|
||||
secretIds,
|
||||
requiredPermissions: []
|
||||
});
|
||||
|
||||
req.secrets = relevantSecrets;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -100,12 +100,13 @@ router.post(
|
||||
}),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||
requiredServiceTokenPermissions: ['write']
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body'
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||
}),
|
||||
secretsController.createSecrets
|
||||
);
|
||||
@@ -117,12 +118,13 @@ router.get(
|
||||
query('tagSlugs'),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||
requiredServiceTokenPermissions: ['read']
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'query'
|
||||
locationWorkspaceId: 'query',
|
||||
locationEnvironment: 'query',
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||
}),
|
||||
secretsController.getSecrets
|
||||
);
|
||||
@@ -157,11 +159,11 @@ router.patch(
|
||||
}),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||
requiredServiceTokenPermissions: ['write']
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||
}),
|
||||
requireSecretsAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||
}),
|
||||
secretsController.updateSecrets
|
||||
);
|
||||
@@ -186,11 +188,11 @@ router.delete(
|
||||
.isEmpty(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||
requiredServiceTokenPermissions: ['write']
|
||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||
}),
|
||||
requireSecretsAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||
}),
|
||||
secretsController.deleteSecrets
|
||||
);
|
||||
|
||||
@@ -125,10 +125,8 @@ router.post(
|
||||
param('serviceAccountId').exists().isString().trim(),
|
||||
body('workspaceId').exists().isString().notEmpty(),
|
||||
body('environment').exists().isString().notEmpty(),
|
||||
body('canRead').isBoolean().optional(),
|
||||
body('canWrite').isBoolean().optional(),
|
||||
body('canUpdate').isBoolean().optional(),
|
||||
body('canDelete').isBoolean().optional(),
|
||||
body('read').isBoolean().optional(),
|
||||
body('write').isBoolean().optional(),
|
||||
body('encryptedKey').exists().isString().notEmpty(),
|
||||
body('nonce').exists().isString().notEmpty(),
|
||||
validateRequest,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { tagController } from '../../controllers/v2';
|
||||
import {
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { ADMIN, MEMBER } from '../../variables';
|
||||
|
||||
@@ -16,6 +16,7 @@ router.get(
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [MEMBER, ADMIN],
|
||||
locationWorkspaceId: 'params'
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
validateRequest,
|
||||
@@ -39,6 +40,7 @@ router.post(
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [MEMBER, ADMIN],
|
||||
locationWorkspaceId: 'params'
|
||||
}),
|
||||
param('workspaceId').exists().trim(),
|
||||
body('name').exists().trim(),
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { PostHog } from 'posthog-node';
|
||||
import { getLogger } from '../utils/logger';
|
||||
import {
|
||||
getNodeEnv,
|
||||
getTelemetryEnabled,
|
||||
getPostHogProjectApiKey,
|
||||
getPostHogHost
|
||||
} from '../config';
|
||||
|
||||
/**
|
||||
* Logs telemetry enable/disable notice.
|
||||
*/
|
||||
const logTelemetryMessage = () => {
|
||||
if(!getTelemetryEnabled()){
|
||||
getLogger("backend-main").info([
|
||||
"",
|
||||
"To improve, Infisical collects telemetry data about general usage.",
|
||||
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
||||
"To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.",
|
||||
].join('\n'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the PostHog client initialized.
|
||||
* @returns
|
||||
*/
|
||||
const getPostHogClient = () => {
|
||||
let postHogClient: any;
|
||||
if (getNodeEnv() === 'production' && getTelemetryEnabled()) {
|
||||
// case: enable opt-out telemetry in production
|
||||
postHogClient = new PostHog(getPostHogProjectApiKey(), {
|
||||
host: getPostHogHost()
|
||||
});
|
||||
}
|
||||
|
||||
return postHogClient;
|
||||
}
|
||||
|
||||
export {
|
||||
logTelemetryMessage,
|
||||
getPostHogClient
|
||||
}
|
||||
|
||||
85
backend/src/services/TelemetryService.ts
Normal file
85
backend/src/services/TelemetryService.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { PostHog } from 'posthog-node';
|
||||
import { getLogger } from '../utils/logger';
|
||||
import {
|
||||
getNodeEnv,
|
||||
getTelemetryEnabled,
|
||||
getPostHogProjectApiKey,
|
||||
getPostHogHost
|
||||
} from '../config';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData
|
||||
} from '../models';
|
||||
import {
|
||||
BadRequestError
|
||||
} from '../utils/errors';
|
||||
|
||||
class Telemetry {
|
||||
/**
|
||||
* Logs telemetry enable/disable notice.
|
||||
*/
|
||||
static logTelemetryMessage = () => {
|
||||
if(!getTelemetryEnabled()){
|
||||
getLogger("backend-main").info([
|
||||
"",
|
||||
"To improve, Infisical collects telemetry data about general usage.",
|
||||
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
||||
"To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.",
|
||||
].join('\n'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the PostHog client initialized.
|
||||
* @returns
|
||||
*/
|
||||
static getPostHogClient = () => {
|
||||
let postHogClient: any;
|
||||
if (getNodeEnv() === 'production' && getTelemetryEnabled()) {
|
||||
// case: enable opt-out telemetry in production
|
||||
postHogClient = new PostHog(getPostHogProjectApiKey(), {
|
||||
host: getPostHogHost()
|
||||
});
|
||||
}
|
||||
|
||||
return postHogClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a distinct id for client to be used for logging telemetry
|
||||
*/
|
||||
static getDistinctId = ({
|
||||
user,
|
||||
serviceAccount,
|
||||
serviceTokenData
|
||||
}: {
|
||||
user?: IUser;
|
||||
serviceAccount?: IServiceAccount;
|
||||
serviceTokenData?: IServiceTokenData;
|
||||
}) => {
|
||||
let distinctId = '';
|
||||
|
||||
if (user) {
|
||||
distinctId = user.email;
|
||||
}
|
||||
|
||||
if (serviceAccount) {
|
||||
distinctId = `sa.${serviceAccount._id}`;
|
||||
}
|
||||
|
||||
if (serviceTokenData) {
|
||||
distinctId = `st.${serviceTokenData._id}`;
|
||||
}
|
||||
|
||||
if (distinctId === '') {
|
||||
throw BadRequestError({
|
||||
message: 'Failed to obtain distinct id for logging telemetry'
|
||||
});
|
||||
}
|
||||
|
||||
return distinctId;
|
||||
}
|
||||
}
|
||||
|
||||
export default Telemetry;
|
||||
@@ -1,13 +1,15 @@
|
||||
import DatabaseService from './DatabaseService';
|
||||
import { logTelemetryMessage, getPostHogClient } from './PostHogClient';
|
||||
// import { logTelemetryMessage, getPostHogClient } from './TelemetryService';
|
||||
import TelemetryService from './TelemetryService';
|
||||
import BotService from './BotService';
|
||||
import EventService from './EventService';
|
||||
import IntegrationService from './IntegrationService';
|
||||
import TokenService from './TokenService';
|
||||
|
||||
export {
|
||||
logTelemetryMessage,
|
||||
getPostHogClient,
|
||||
TelemetryService,
|
||||
// logTelemetryMessage,
|
||||
// getPostHogClient,
|
||||
DatabaseService,
|
||||
BotService,
|
||||
EventService,
|
||||
|
||||
2
backend/src/types/express/index.d.ts
vendored
2
backend/src/types/express/index.d.ts
vendored
@@ -24,7 +24,7 @@ declare global {
|
||||
serviceTokenData: any;
|
||||
apiKeyData: any;
|
||||
query?: any;
|
||||
authTokenType: string;
|
||||
authData: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
backend/src/variables/authentication.ts
Normal file
11
backend/src/variables/authentication.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const AUTH_MODE_JWT = 'jwt';
|
||||
const AUTH_MODE_SERVICE_ACCOUNT = 'serviceAccount';
|
||||
const AUTH_MODE_SERVICE_TOKEN = 'serviceToken';
|
||||
const AUTH_MODE_API_KEY = 'apiKey'; // TODO: deprecate
|
||||
|
||||
export {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
}
|
||||
@@ -63,6 +63,16 @@ import {
|
||||
TOKEN_EMAIL_ORG_INVITATION,
|
||||
TOKEN_EMAIL_PASSWORD_RESET
|
||||
} from './token';
|
||||
import {
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS
|
||||
} from './permission';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from './authentication';
|
||||
|
||||
export {
|
||||
OWNER,
|
||||
@@ -113,6 +123,8 @@ export {
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
getIntegrationOptions,
|
||||
SMTP_HOST_SENDGRID,
|
||||
SMTP_HOST_MAILGUN,
|
||||
@@ -124,5 +136,9 @@ export {
|
||||
TOKEN_EMAIL_CONFIRMATION,
|
||||
TOKEN_EMAIL_MFA,
|
||||
TOKEN_EMAIL_ORG_INVITATION,
|
||||
TOKEN_EMAIL_PASSWORD_RESET
|
||||
TOKEN_EMAIL_PASSWORD_RESET,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
};
|
||||
|
||||
7
backend/src/variables/permission.ts
Normal file
7
backend/src/variables/permission.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
const PERMISSION_READ_SECRETS = 'read';
|
||||
const PERMISSION_WRITE_SECRETS = 'write';
|
||||
|
||||
export {
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS
|
||||
}
|
||||
@@ -31,20 +31,16 @@ export type ServiceAccountWorkspacePermission = {
|
||||
serviceAccount: string;
|
||||
workspace: Workspace;
|
||||
environment: string;
|
||||
canRead: boolean;
|
||||
canWrite: boolean;
|
||||
canUpdate: boolean;
|
||||
canDelete: boolean;
|
||||
read: boolean;
|
||||
write: boolean;
|
||||
}
|
||||
|
||||
export type CreateServiceAccountWorkspacePermissionDTO = {
|
||||
serviceAccountId: string;
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
canRead: boolean;
|
||||
canWrite: boolean;
|
||||
canUpdate: boolean;
|
||||
canDelete: boolean;
|
||||
read: boolean;
|
||||
write: boolean;
|
||||
encryptedKey: string;
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
@@ -48,10 +48,8 @@ const createProjectLevelPermissionSchema = yup.object({
|
||||
workspace: yup.string().required().label('Workspace'),
|
||||
environment: yup.string().required().label('Environment'),
|
||||
permissions: yup.object().shape({
|
||||
canRead: yup.boolean().required(),
|
||||
canWrite: yup.boolean().required(),
|
||||
canUpdate: yup.boolean().required(),
|
||||
canDelete: yup.boolean().required(),
|
||||
read: yup.boolean().required(),
|
||||
write: yup.boolean().required()
|
||||
}).defined().required()
|
||||
});
|
||||
|
||||
@@ -91,7 +89,7 @@ export const SAProjectLevelPermissionsTable = ({
|
||||
privateKey,
|
||||
workspace,
|
||||
environment,
|
||||
permissions: { canRead, canWrite, canUpdate, canDelete }
|
||||
permissions: { read, write }
|
||||
}: CreateProjectLevelPermissionForm) => {
|
||||
|
||||
// TODO: clean up / modularize this function
|
||||
@@ -126,10 +124,8 @@ export const SAProjectLevelPermissionsTable = ({
|
||||
serviceAccountId,
|
||||
workspaceId: workspace,
|
||||
environment,
|
||||
canRead,
|
||||
canWrite,
|
||||
canUpdate,
|
||||
canDelete,
|
||||
read,
|
||||
write,
|
||||
encryptedKey: ciphertext,
|
||||
nonce
|
||||
});
|
||||
@@ -187,10 +183,8 @@ export const SAProjectLevelPermissionsTable = ({
|
||||
_id,
|
||||
workspace,
|
||||
environment,
|
||||
canRead,
|
||||
canWrite,
|
||||
canUpdate,
|
||||
canDelete
|
||||
read,
|
||||
write
|
||||
}) => {
|
||||
const environmentName = (workspace.environments.find((env) => env.slug === environment))?.name;
|
||||
return (
|
||||
@@ -200,28 +194,14 @@ export const SAProjectLevelPermissionsTable = ({
|
||||
<Td>
|
||||
<Checkbox
|
||||
id="isReadPermissionEnabled"
|
||||
isChecked={canRead}
|
||||
isChecked={read}
|
||||
isDisabled
|
||||
>{/**/}</Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
id="isWritePermissionEnabled"
|
||||
isChecked={canWrite}
|
||||
isDisabled
|
||||
>{/**/}</Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
id="isUpdatePermissionEnabled"
|
||||
isChecked={canUpdate}
|
||||
isDisabled
|
||||
>{/**/}</Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
id="isDeletePermissionEnabled"
|
||||
isChecked={canDelete}
|
||||
isChecked={write}
|
||||
isDisabled
|
||||
>{/**/}</Checkbox>
|
||||
</Td>
|
||||
@@ -352,28 +332,18 @@ export const SAProjectLevelPermissionsTable = ({
|
||||
control={control}
|
||||
name="permissions"
|
||||
defaultValue={{
|
||||
canRead: true,
|
||||
canWrite: false,
|
||||
canUpdate: false,
|
||||
canDelete: false
|
||||
read: true,
|
||||
write: false
|
||||
}}
|
||||
render={({ field: { onChange, value }, fieldState: { error }}) => {
|
||||
const options = [
|
||||
{
|
||||
label: 'Read (default)',
|
||||
value: 'canRead'
|
||||
value: 'read'
|
||||
},
|
||||
{
|
||||
label: 'Write',
|
||||
value: 'canWrite'
|
||||
},
|
||||
{
|
||||
label: 'Update',
|
||||
value: 'canUpdate'
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
value: 'canDelete'
|
||||
value: 'write'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user