mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 11:27:32 +00:00
Begin refactoring middleware for service accounts
This commit is contained in:
Generated
+1
-82
@@ -12,8 +12,8 @@
|
|||||||
"@aws-sdk/client-secrets-manager": "^3.281.0",
|
"@aws-sdk/client-secrets-manager": "^3.281.0",
|
||||||
"@godaddy/terminus": "^4.11.2",
|
"@godaddy/terminus": "^4.11.2",
|
||||||
"@octokit/rest": "^19.0.5",
|
"@octokit/rest": "^19.0.5",
|
||||||
"@sentry/tracing": "^7.39.0",
|
|
||||||
"@sentry/node": "^7.40.0",
|
"@sentry/node": "^7.40.0",
|
||||||
|
"@sentry/tracing": "^7.39.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/libsodium-wrappers": "^0.7.10",
|
"@types/libsodium-wrappers": "^0.7.10",
|
||||||
"await-to-js": "^3.0.0",
|
"await-to-js": "^3.0.0",
|
||||||
@@ -2988,24 +2988,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
"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": {
|
"node_modules/@sentry/node": {
|
||||||
"version": "7.40.0",
|
"version": "7.40.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.40.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"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": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.25.24",
|
"version": "0.25.24",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
"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": {
|
"@sentry/node": {
|
||||||
"version": "7.40.0",
|
"version": "7.40.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.40.0.tgz",
|
"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": {
|
"@sinclair/typebox": {
|
||||||
"version": "0.25.24",
|
"version": "0.25.24",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import { pushKeys } from '../../helpers/key';
|
import { pushKeys } from '../../helpers/key';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
import { getPostHogClient } from '../../services';
|
import { TelemetryService } from '../../services';
|
||||||
|
|
||||||
interface PushSecret {
|
interface PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
@@ -38,7 +38,7 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
|||||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
let { secrets }: { secrets: PushSecret[] } = req.body;
|
let { secrets }: { secrets: PushSecret[] } = req.body;
|
||||||
const { keys, environment, channel } = req.body;
|
const { keys, environment, channel } = req.body;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -112,7 +112,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
|||||||
let secrets;
|
let secrets;
|
||||||
let key;
|
let key;
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const environment: string = req.query.environment as string;
|
const environment: string = req.query.environment as string;
|
||||||
const channel: string = req.query.channel as string;
|
const channel: string = req.query.channel as string;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -181,7 +181,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
|||||||
let secrets;
|
let secrets;
|
||||||
let key;
|
let key;
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const environment: string = req.query.environment as string;
|
const environment: string = req.query.environment as string;
|
||||||
const channel: string = req.query.channel as string;
|
const channel: string = req.query.channel as string;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const { ValidationError } = mongoose.Error;
|
|||||||
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
|
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
|
||||||
import { AnyBulkWriteOperation } from 'mongodb';
|
import { AnyBulkWriteOperation } from 'mongodb';
|
||||||
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
|
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]
|
* Create secret for workspace with id [workspaceId] and environment [environment]
|
||||||
@@ -15,7 +15,7 @@ import { getPostHogClient } from '../../services';
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const createSecret = async (req: Request, res: Response) => {
|
export const createSecret = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const secretToCreate: CreateSecretRequestBody = req.body.secret;
|
const secretToCreate: CreateSecretRequestBody = req.body.secret;
|
||||||
const { workspaceId, environment } = req.params
|
const { workspaceId, environment } = req.params
|
||||||
const sanitizedSecret: SanitizedSecretForCreate = {
|
const sanitizedSecret: SanitizedSecretForCreate = {
|
||||||
@@ -68,7 +68,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const createSecrets = async (req: Request, res: Response) => {
|
export const createSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
|
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
|
||||||
const { workspaceId, environment } = req.params
|
const { workspaceId, environment } = req.params
|
||||||
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
|
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
|
||||||
@@ -130,7 +130,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const deleteSecrets = async (req: Request, res: Response) => {
|
export const deleteSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { workspaceId, environmentName } = req.params
|
const { workspaceId, environmentName } = req.params
|
||||||
const secretIdsToDelete: string[] = req.body.secretIds
|
const secretIdsToDelete: string[] = req.body.secretIds
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const deleteSecret = async (req: Request, res: Response) => {
|
export const deleteSecret = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
await Secret.findByIdAndDelete(req._secret._id)
|
await Secret.findByIdAndDelete(req._secret._id)
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
@@ -213,7 +213,7 @@ export const deleteSecret = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateSecrets = async (req: Request, res: Response) => {
|
export const updateSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { workspaceId, environmentName } = req.params
|
const { workspaceId, environmentName } = req.params
|
||||||
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
|
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
|
||||||
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
|
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
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateSecret = async (req: Request, res: Response) => {
|
export const updateSecret = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { workspaceId, environmentName } = req.params
|
const { workspaceId, environmentName } = req.params
|
||||||
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ export const updateSecret = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getSecrets = async (req: Request, res: Response) => {
|
export const getSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { environment } = req.query;
|
const { environment } = req.query;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
|||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
import { EESecretService, EELogService } from '../../ee/services';
|
import { EESecretService, EELogService } from '../../ee/services';
|
||||||
import { getPostHogClient } from '../../services';
|
import { TelemetryService } from '../../services';
|
||||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
||||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||||
import Tag from '../../models/tag';
|
import Tag from '../../models/tag';
|
||||||
import _ from 'lodash';
|
import _, { eq } from 'lodash';
|
||||||
import {
|
import {
|
||||||
BatchSecretRequest,
|
BatchSecretRequest,
|
||||||
BatchSecret
|
BatchSecret
|
||||||
@@ -28,12 +28,13 @@ import {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Peform a batch of any specified CUD secret operations
|
* Peform a batch of any specified CUD secret operations
|
||||||
|
* (used by dashboard)
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const batchSecrets = async (req: Request, res: Response) => {
|
export const batchSecrets = async (req: Request, res: Response) => {
|
||||||
const channel = getChannelFromUserAgent(req.headers['user-agent']);
|
const channel = getChannelFromUserAgent(req.headers['user-agent']);
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -91,7 +92,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const addAction = await EELogService.createAction({
|
const addAction = await EELogService.createAction({
|
||||||
name: ACTION_ADD_SECRETS,
|
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),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: createdSecrets.map((n) => n._id)
|
secretIds: createdSecrets.map((n) => n._id)
|
||||||
}) as IAction;
|
}) as IAction;
|
||||||
@@ -328,15 +331,16 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const postHogClient = getPostHogClient();
|
|
||||||
|
|
||||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||||
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
||||||
|
|
||||||
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_WRITE)
|
if (req.user) {
|
||||||
|
const hasAccess = await userHasWorkspaceAccess(req.user, new Types.ObjectId(workspaceId), environment, ABILITY_WRITE)
|
||||||
if (!hasAccess) {
|
if (!hasAccess) {
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let listOfSecretsToCreate;
|
let listOfSecretsToCreate;
|
||||||
if (Array.isArray(req.body.secrets)) {
|
if (Array.isArray(req.body.secrets)) {
|
||||||
@@ -378,7 +382,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
type,
|
type,
|
||||||
user: type === SECRET_PERSONAL ? req.user : undefined,
|
user: (req.user && type === SECRET_PERSONAL) ? req.user : undefined,
|
||||||
environment,
|
environment,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
@@ -391,7 +395,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
tags
|
tags
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
|
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({
|
const addAction = await EELogService.createAction({
|
||||||
name: ACTION_ADD_SECRETS,
|
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),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
addAction && await EELogService.createLog({
|
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),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
actions: [addAction],
|
actions: [addAction],
|
||||||
channel,
|
channel,
|
||||||
@@ -466,10 +474,15 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId
|
workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets added',
|
event: 'secrets added',
|
||||||
distinctId: req.user.email,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: listOfSecretsToCreate.length,
|
numberOfSecrets: listOfSecretsToCreate.length,
|
||||||
environment,
|
environment,
|
||||||
@@ -533,34 +546,36 @@ 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(',') : [];
|
const tagNamesList = typeof tagSlugs === 'string' && tagSlugs !== '' ? tagSlugs.split(',') : [];
|
||||||
let userId = "" // used for getting personal secrets for user
|
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||||
let userEmail = "" // used for posthog
|
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) {
|
if (req.user) {
|
||||||
userId = req.user._id;
|
// case: client authorization is via JWT
|
||||||
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
|
let hasWriteOnlyAccess
|
||||||
if (!req.serviceTokenData) {
|
if (!req.serviceTokenData) {
|
||||||
hasWriteOnlyAccess = await userHasWriteOnlyAbility(userId, workspaceId, environment)
|
hasWriteOnlyAccess = await userHasWriteOnlyAbility(req.user._id, new Types.ObjectId(workspaceId), environment)
|
||||||
const hasNoAccess = await userHasNoAbility(userId, workspaceId, environment)
|
const hasNoAccess = await userHasNoAbility(req.user._id, new Types.ObjectId(workspaceId), environment)
|
||||||
if (hasNoAccess) {
|
if (hasNoAccess) {
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let secrets: any
|
|
||||||
let secretQuery: any
|
|
||||||
|
|
||||||
|
let secretQuery: any;
|
||||||
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId })
|
const workspaceFromDB = await Tag.find({ workspace: workspaceId })
|
||||||
|
|
||||||
@@ -573,7 +588,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
$or: [
|
$or: [
|
||||||
{ user: userId },
|
{ user: req.user._id },
|
||||||
{ user: { $exists: false } }
|
{ user: { $exists: false } }
|
||||||
],
|
],
|
||||||
tags: { $in: tagIds },
|
tags: { $in: tagIds },
|
||||||
@@ -584,7 +599,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
$or: [
|
$or: [
|
||||||
{ user: userId },
|
{ user: req.user._id },
|
||||||
{ user: { $exists: false } }
|
{ user: { $exists: false } }
|
||||||
],
|
],
|
||||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||||
@@ -592,32 +607,57 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasWriteOnlyAccess) {
|
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")
|
secrets = await Secret.find(secretQuery).select("secretKeyCiphertext secretKeyIV secretKeyTag")
|
||||||
} else {
|
} else {
|
||||||
secrets = await Secret.find(secretQuery).populate("tags")
|
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 channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||||
|
|
||||||
const readAction = await EELogService.createAction({
|
const readAction = await EELogService.createAction({
|
||||||
name: ACTION_READ_SECRETS,
|
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),
|
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||||
secretIds: secrets.map((n: any) => n._id)
|
secretIds: secrets.map((n: any) => n._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
readAction && await EELogService.createLog({
|
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),
|
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||||
actions: [readAction],
|
actions: [readAction],
|
||||||
channel,
|
channel,
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets pulled',
|
event: 'secrets pulled',
|
||||||
distinctId: userEmail,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
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)
|
* Update secret(s)
|
||||||
* @param req
|
* @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';
|
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||||
|
|
||||||
// TODO: move type
|
|
||||||
interface PatchSecret {
|
interface PatchSecret {
|
||||||
id: string;
|
id: string;
|
||||||
secretKeyCiphertext: string;
|
secretKeyCiphertext: string;
|
||||||
@@ -865,14 +850,18 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const updateAction = await EELogService.createAction({
|
const updateAction = await EELogService.createAction({
|
||||||
name: ACTION_UPDATE_SECRETS,
|
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),
|
workspaceId: new Types.ObjectId(key),
|
||||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
updateAction && await EELogService.createLog({
|
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),
|
workspaceId: new Types.ObjectId(key),
|
||||||
actions: [updateAction],
|
actions: [updateAction],
|
||||||
channel,
|
channel,
|
||||||
@@ -884,10 +873,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: key
|
workspaceId: key
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets modified',
|
event: 'secrets modified',
|
||||||
distinctId: req.user.email,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: workspaceSecretObj[key].length,
|
numberOfSecrets: workspaceSecretObj[key].length,
|
||||||
environment: workspaceSecretObj[key][0].environment,
|
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 req
|
||||||
* @param res
|
* @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 channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||||
const toDelete = req.secrets.map((s: any) => s._id);
|
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({
|
const deleteAction = await EELogService.createAction({
|
||||||
name: ACTION_DELETE_SECRETS,
|
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),
|
workspaceId: new Types.ObjectId(key),
|
||||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
deleteAction && await EELogService.createLog({
|
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),
|
workspaceId: new Types.ObjectId(key),
|
||||||
actions: [deleteAction],
|
actions: [deleteAction],
|
||||||
channel,
|
channel,
|
||||||
@@ -1011,10 +1013,15 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: key
|
workspaceId: key
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets deleted',
|
event: 'secrets deleted',
|
||||||
distinctId: req.user.email,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: workspaceSecretObj[key].length,
|
numberOfSecrets: workspaceSecretObj[key].length,
|
||||||
environment: workspaceSecretObj[key][0].environment,
|
environment: workspaceSecretObj[key][0].environment,
|
||||||
|
|||||||
@@ -195,10 +195,8 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
|
|||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
canRead = false,
|
read = false,
|
||||||
canWrite = false,
|
write = false,
|
||||||
canUpdate = false,
|
|
||||||
canDelete = false,
|
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
nonce
|
nonce
|
||||||
} = req.body;
|
} = req.body;
|
||||||
@@ -221,10 +219,8 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
|
|||||||
serviceAccount: new Types.ObjectId(serviceAccountId),
|
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
canRead,
|
read,
|
||||||
canWrite,
|
write
|
||||||
canUpdate,
|
|
||||||
canDelete
|
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
const existingServiceAccountKey = await ServiceAccountKey.findOne({
|
const existingServiceAccountKey = await ServiceAccountKey.findOne({
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
reformatPullSecrets
|
reformatPullSecrets
|
||||||
} from '../../helpers/secret';
|
} from '../../helpers/secret';
|
||||||
import { pushKeys } from '../../helpers/key';
|
import { pushKeys } from '../../helpers/key';
|
||||||
import { getPostHogClient, EventService } from '../../services';
|
import { TelemetryService, EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
|
|
||||||
interface V2PushSecret {
|
interface V2PushSecret {
|
||||||
@@ -48,7 +48,7 @@ interface V2PushSecret {
|
|||||||
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||||
const { keys, environment, channel } = req.body;
|
const { keys, environment, channel } = req.body;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -122,7 +122,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
export const pullSecrets = async (req: Request, res: Response) => {
|
export const pullSecrets = async (req: Request, res: Response) => {
|
||||||
let secrets;
|
let secrets;
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const environment: string = req.query.environment as string;
|
const environment: string = req.query.environment as string;
|
||||||
const channel: string = req.query.channel as string;
|
const channel: string = req.query.channel as string;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|||||||
@@ -24,11 +24,15 @@ import {
|
|||||||
const createActionUpdateSecret = async ({
|
const createActionUpdateSecret = async ({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
secretIds: Types.ObjectId[];
|
secretIds: Types.ObjectId[];
|
||||||
}) => {
|
}) => {
|
||||||
@@ -46,6 +50,8 @@ const createActionUpdateSecret = async ({
|
|||||||
action = await new Action({
|
action = await new Action({
|
||||||
name,
|
name,
|
||||||
user: userId,
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
payload: {
|
payload: {
|
||||||
secretVersions: latestSecretVersions
|
secretVersions: latestSecretVersions
|
||||||
@@ -72,11 +78,15 @@ const createActionUpdateSecret = async ({
|
|||||||
const createActionSecret = async ({
|
const createActionSecret = async ({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
secretIds: Types.ObjectId[];
|
secretIds: Types.ObjectId[];
|
||||||
}) => {
|
}) => {
|
||||||
@@ -94,6 +104,8 @@ const createActionSecret = async ({
|
|||||||
action = await new Action({
|
action = await new Action({
|
||||||
name,
|
name,
|
||||||
user: userId,
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
payload: {
|
payload: {
|
||||||
secretVersions: latestSecretVersions
|
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 {Object} obj
|
||||||
* @param {String} obj.name - name of action
|
* @param {String} obj.name - name of action
|
||||||
* @param {String} obj.userId - id of user associated with action
|
* @param {String} obj.userId - id of user associated with action
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const createActionUser = ({
|
const createActionClient = ({
|
||||||
name,
|
name,
|
||||||
userId
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
let action;
|
let action;
|
||||||
try {
|
try {
|
||||||
action = new Action({
|
action = new Action({
|
||||||
name,
|
name,
|
||||||
user: userId
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId
|
||||||
}).save();
|
}).save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to create user action');
|
throw new Error('Failed to create client action');
|
||||||
}
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
@@ -149,11 +168,15 @@ const createActionUser = ({
|
|||||||
const createActionHelper = async ({
|
const createActionHelper = async ({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds,
|
secretIds,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
secretIds?: Types.ObjectId[];
|
secretIds?: Types.ObjectId[];
|
||||||
}) => {
|
}) => {
|
||||||
@@ -162,7 +185,7 @@ const createActionHelper = async ({
|
|||||||
switch (name) {
|
switch (name) {
|
||||||
case ACTION_LOGIN:
|
case ACTION_LOGIN:
|
||||||
case ACTION_LOGOUT:
|
case ACTION_LOGOUT:
|
||||||
action = await createActionUser({
|
action = await createActionClient({
|
||||||
name,
|
name,
|
||||||
userId
|
userId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { Membership } from "../../models";
|
import { Membership } from "../../models";
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from "../../variables/organization";
|
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 })
|
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||||
if (!membershipForWorkspace) {
|
if (!membershipForWorkspace) {
|
||||||
return false
|
return false
|
||||||
@@ -18,7 +19,7 @@ export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, envi
|
|||||||
return true
|
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 })
|
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||||
if (!membershipForWorkspace) {
|
if (!membershipForWorkspace) {
|
||||||
return false
|
return false
|
||||||
@@ -36,7 +37,7 @@ export const userHasWriteOnlyAbility = async (userId: any, workspaceId: any, env
|
|||||||
return false
|
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 })
|
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||||
if (!membershipForWorkspace) {
|
if (!membershipForWorkspace) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ import {
|
|||||||
*/
|
*/
|
||||||
const createLogHelper = async ({
|
const createLogHelper = async ({
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
actions,
|
actions,
|
||||||
channel,
|
channel,
|
||||||
ipAddress
|
ipAddress
|
||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
actions: IAction[];
|
actions: IAction[];
|
||||||
channel: string;
|
channel: string;
|
||||||
@@ -31,6 +35,8 @@ const createLogHelper = async ({
|
|||||||
try {
|
try {
|
||||||
log = await new Log({
|
log = await new Log({
|
||||||
user: userId,
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId,
|
||||||
workspace: workspaceId ?? undefined,
|
workspace: workspaceId ?? undefined,
|
||||||
actionNames: actions.map((a) => a.name),
|
actionNames: actions.map((a) => a.name),
|
||||||
actions,
|
actions,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
export interface IAction {
|
export interface IAction {
|
||||||
name: string;
|
name: string;
|
||||||
user?: Types.ObjectId,
|
user?: Types.ObjectId,
|
||||||
|
serviceAccount?: Types.ObjectId,
|
||||||
|
serviceTokenData?: Types.ObjectId,
|
||||||
workspace?: Types.ObjectId,
|
workspace?: Types.ObjectId,
|
||||||
payload?: {
|
payload?: {
|
||||||
secretVersions?: Types.ObjectId[]
|
secretVersions?: Types.ObjectId[]
|
||||||
@@ -33,8 +35,15 @@ const actionSchema = new Schema<IAction>(
|
|||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'User',
|
ref: 'User'
|
||||||
required: true
|
},
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount'
|
||||||
|
},
|
||||||
|
serviceTokenData: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceTokenData'
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
export interface ILog {
|
export interface ILog {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
user?: Types.ObjectId;
|
user?: Types.ObjectId;
|
||||||
|
serviceAccount?: Types.ObjectId;
|
||||||
|
serviceTokenData?: Types.ObjectId;
|
||||||
workspace?: Types.ObjectId;
|
workspace?: Types.ObjectId;
|
||||||
actionNames: string[];
|
actionNames: string[];
|
||||||
actions: Types.ObjectId[];
|
actions: Types.ObjectId[];
|
||||||
@@ -24,6 +26,14 @@ const logSchema = new Schema<ILog>(
|
|||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'User'
|
ref: 'User'
|
||||||
},
|
},
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount'
|
||||||
|
},
|
||||||
|
serviceTokenData: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceTokenData'
|
||||||
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Workspace'
|
ref: 'Workspace'
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ class EELogService {
|
|||||||
*/
|
*/
|
||||||
static async createLog({
|
static async createLog({
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
actions,
|
actions,
|
||||||
channel,
|
channel,
|
||||||
ipAddress
|
ipAddress
|
||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
actions: IAction[];
|
actions: IAction[];
|
||||||
channel: string;
|
channel: string;
|
||||||
@@ -40,6 +44,8 @@ class EELogService {
|
|||||||
if (!EELicenseService.isLicenseValid) return null;
|
if (!EELicenseService.isLicenseValid) return null;
|
||||||
return await createLogHelper({
|
return await createLogHelper({
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
actions,
|
actions,
|
||||||
channel,
|
channel,
|
||||||
@@ -59,17 +65,23 @@ class EELogService {
|
|||||||
static async createAction({
|
static async createAction({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
secretIds?: Types.ObjectId[];
|
secretIds?: Types.ObjectId[];
|
||||||
}) {
|
}) {
|
||||||
return await createActionHelper({
|
return await createActionHelper({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
});
|
});
|
||||||
|
|||||||
+16
-11
@@ -22,6 +22,12 @@ import {
|
|||||||
getJwtRefreshLifetime,
|
getJwtRefreshLifetime,
|
||||||
getJwtRefreshSecret
|
getJwtRefreshSecret
|
||||||
} from '../config';
|
} 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 apiKey = headers['x-api-key'];
|
||||||
const authHeader = headers['authorization'];
|
const authHeader = headers['authorization'];
|
||||||
|
|
||||||
let authTokenType, authTokenValue;
|
let authMode, authTokenValue;
|
||||||
if (apiKey === undefined && authHeader === undefined) {
|
if (apiKey === undefined && authHeader === undefined) {
|
||||||
// case: no auth or X-API-KEY header present
|
// case: no auth or X-API-KEY header present
|
||||||
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' });
|
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' });
|
||||||
@@ -47,7 +53,7 @@ const validateAuthMode = ({
|
|||||||
|
|
||||||
if (typeof apiKey === 'string') {
|
if (typeof apiKey === 'string') {
|
||||||
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
||||||
authTokenType = 'apiKey';
|
authMode = AUTH_MODE_API_KEY;
|
||||||
authTokenValue = apiKey;
|
authTokenValue = apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,24 +69,24 @@ const validateAuthMode = ({
|
|||||||
|
|
||||||
switch (tokenValue.split('.', 1)[0]) {
|
switch (tokenValue.split('.', 1)[0]) {
|
||||||
case 'st':
|
case 'st':
|
||||||
authTokenType = 'serviceToken';
|
authMode = AUTH_MODE_SERVICE_TOKEN;
|
||||||
break;
|
break;
|
||||||
case 'sa':
|
case 'sa':
|
||||||
authTokenType = 'serviceAccount';
|
authMode = AUTH_MODE_SERVICE_ACCOUNT;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
authTokenType = 'jwt';
|
authMode = AUTH_MODE_JWT;
|
||||||
}
|
}
|
||||||
|
|
||||||
authTokenValue = tokenValue;
|
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 ({
|
return ({
|
||||||
authTokenType,
|
authMode,
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -155,8 +161,7 @@ const getAuthSTDPayload = async ({
|
|||||||
|
|
||||||
serviceTokenData = await ServiceTokenData
|
serviceTokenData = await ServiceTokenData
|
||||||
.findById(TOKEN_IDENTIFIER)
|
.findById(TOKEN_IDENTIFIER)
|
||||||
.select('+encryptedKey +iv +tag')
|
.select('+encryptedKey +iv +tag');
|
||||||
.populate<{user: IUser}>('user');
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
||||||
|
|
||||||
@@ -216,7 +221,7 @@ const getAuthAPIKeyPayload = async ({
|
|||||||
|
|
||||||
const apiKeyData = await APIKeyData
|
const apiKeyData = await APIKeyData
|
||||||
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt')
|
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt')
|
||||||
.populate('user', '+publicKey');
|
.populate<{user: IUser}>('user', '+publicKey');
|
||||||
|
|
||||||
if (!apiKeyData) {
|
if (!apiKeyData) {
|
||||||
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
|
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
|
||||||
|
|||||||
@@ -24,57 +24,6 @@ import _ from 'lodash';
|
|||||||
import { ABILITY_WRITE } from '../variables/organization';
|
import { ABILITY_WRITE } from '../variables/organization';
|
||||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
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 {
|
interface V1PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
ivKey: string;
|
ivKey: string;
|
||||||
@@ -714,7 +663,6 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateSecrets,
|
|
||||||
v1PushSecrets,
|
v1PushSecrets,
|
||||||
v2PushSecrets,
|
v2PushSecrets,
|
||||||
pullSecrets,
|
pullSecrets,
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+100
-2
@@ -1,6 +1,18 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
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 { 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]
|
* 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,
|
Bot,
|
||||||
Membership,
|
Membership,
|
||||||
Key,
|
Key,
|
||||||
Secret
|
Secret,
|
||||||
|
User,
|
||||||
|
IUser,
|
||||||
|
ServiceAccountWorkspacePermission,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceTokenData,
|
||||||
|
IServiceTokenData,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { createBot } from '../helpers/bot';
|
import { createBot } from '../helpers/bot';
|
||||||
|
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||||
|
import { validateServiceTokenDataClientForWorkspace } from '../helpers/serviceTokenData';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { 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],
|
* Validate accepted clients for workspace with id [workspaceId] based
|
||||||
* and [serviceTokenDataId] for workspace with id [workspaceId] based
|
|
||||||
* on any known permissions.
|
* on any known permissions.
|
||||||
* @param {Object} obj
|
* @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 ({
|
const validateClientForWorkspace = async ({
|
||||||
userId,
|
authData,
|
||||||
serviceAccountId,
|
|
||||||
serviceTokenDataId,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment
|
environment,
|
||||||
|
requiredPermissions
|
||||||
}: {
|
}: {
|
||||||
userId?: Types.ObjectId;
|
authData: {
|
||||||
serviceAccountId?: Types.ObjectId;
|
authMode: string;
|
||||||
serviceTokenDataId?: Types.ObjectId;
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
},
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let membership;
|
let membership;
|
||||||
if (userId) {
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
membership = await validateMembership({
|
membership = await validateUserClientForWorkspace({
|
||||||
userId,
|
user: authData.authPayload,
|
||||||
workspaceId
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: validate user against [requiredPermissions]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceAccountId) {
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
// TODO
|
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) {
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
// TODO
|
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 ({
|
return ({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as Sentry from '@sentry/node';
|
|||||||
import { DatabaseService } from './services';
|
import { DatabaseService } from './services';
|
||||||
import { setUpHealthEndpoint } from './services/health';
|
import { setUpHealthEndpoint } from './services/health';
|
||||||
import { initSmtp } from './services/smtp';
|
import { initSmtp } from './services/smtp';
|
||||||
import { logTelemetryMessage } from './services';
|
import { TelemetryService } from './services';
|
||||||
import { setTransporter } from './helpers/nodemailer';
|
import { setTransporter } from './helpers/nodemailer';
|
||||||
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -80,7 +80,7 @@ const main = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logTelemetryMessage();
|
TelemetryService.logTelemetryMessage();
|
||||||
setTransporter(initSmtp());
|
setTransporter(initSmtp());
|
||||||
|
|
||||||
await DatabaseService.initDatabase(getMongoURL());
|
await DatabaseService.initDatabase(getMongoURL());
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ import {
|
|||||||
import {
|
import {
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from '../utils/errors';
|
} 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' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -28,59 +39,51 @@ declare module 'jsonwebtoken' {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const requireAuth = ({
|
const requireAuth = ({
|
||||||
acceptedAuthModes = ['jwt'],
|
acceptedAuthModes = [AUTH_MODE_JWT],
|
||||||
requiredServiceTokenPermissions = []
|
|
||||||
}: {
|
}: {
|
||||||
acceptedAuthModes: string[];
|
acceptedAuthModes: string[];
|
||||||
requiredServiceTokenPermissions?: string[];
|
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||||
// and return token type [authTokenType] and value [authTokenValue]
|
// and return token type [authTokenType] and value [authTokenValue]
|
||||||
const { authTokenType, authTokenValue } = validateAuthMode({
|
const { authMode, authTokenValue } = validateAuthMode({
|
||||||
headers: req.headers,
|
headers: req.headers,
|
||||||
acceptedAuthModes
|
acceptedAuthModes
|
||||||
});
|
});
|
||||||
|
|
||||||
req.authTokenType = authTokenType;
|
let authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
switch (authMode) {
|
||||||
// attach auth payloads
|
case AUTH_MODE_SERVICE_ACCOUNT:
|
||||||
let serviceTokenData: any;
|
authPayload = await getAuthSAAKPayload({
|
||||||
switch (authTokenType) {
|
|
||||||
case 'serviceAccount':
|
|
||||||
req.serviceAccount = await getAuthSAAKPayload({
|
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
req.serviceAccount = authPayload;
|
||||||
break;
|
break;
|
||||||
case 'serviceToken':
|
case AUTH_MODE_SERVICE_TOKEN:
|
||||||
serviceTokenData = await getAuthSTDPayload({
|
authPayload = await getAuthSTDPayload({
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
req.serviceTokenData = authPayload;
|
||||||
// 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;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'apiKey':
|
case AUTH_MODE_API_KEY:
|
||||||
// TODO: deprecate API key
|
authPayload = await getAuthAPIKeyPayload({
|
||||||
req.user = await getAuthAPIKeyPayload({
|
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
req.user = authPayload;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
req.user = await getAuthUserPayload({
|
authPayload = await getAuthUserPayload({
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
req.user = authPayload;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.authData = {
|
||||||
|
authMode,
|
||||||
|
authPayload
|
||||||
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,34 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
import { Secret, Membership } from '../models';
|
import { Secret, Membership } from '../models';
|
||||||
import { validateSecrets } from '../helpers/secret';
|
import { validateClientForSecrets } from '../helpers/secrets';
|
||||||
|
|
||||||
// TODO: make this work for delete route
|
|
||||||
|
|
||||||
const requireSecretsAuth = ({
|
const requireSecretsAuth = ({
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
|
requiredPermissions = []
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
let secrets;
|
let secretIds = [];
|
||||||
try {
|
|
||||||
if (Array.isArray(req.body.secrets)) {
|
if (Array.isArray(req.body.secrets)) {
|
||||||
// case: validate multiple secrets
|
secretIds = req.body.secrets.map((s: any) => s.id);
|
||||||
secrets = await validateSecrets({
|
} else if (typeof req.body.secrets === 'object') {
|
||||||
userId: req.user._id.toString(),
|
secretIds = [req.body.secrets.id];
|
||||||
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)) {
|
} else if (Array.isArray(req.body.secretIds)) {
|
||||||
secrets = await validateSecrets({
|
secretIds = req.body.secretIds;
|
||||||
userId: req.user._id.toString(),
|
|
||||||
secretIds: req.body.secretIds
|
|
||||||
});
|
|
||||||
} else if (typeof req.body.secretIds === 'string') {
|
} else if (typeof req.body.secretIds === 'string') {
|
||||||
// case: validate secretIds
|
secretIds = [req.body.secretIds];
|
||||||
secrets = await validateSecrets({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
secretIds: [req.body.secretIds]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.secrets = secrets;
|
req.secrets = await validateClientForSecrets({
|
||||||
|
authData: req.authData,
|
||||||
|
secretIds: [req.body.secretIds],
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret(s)' }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,44 +16,32 @@ type req = 'params' | 'body' | 'query';
|
|||||||
const requireWorkspaceAuth = ({
|
const requireWorkspaceAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
locationWorkspaceId,
|
locationWorkspaceId,
|
||||||
locationEnvironment = undefined
|
locationEnvironment = undefined,
|
||||||
|
requiredPermissions = []
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
locationWorkspaceId: req;
|
locationWorkspaceId: req;
|
||||||
locationEnvironment?: req | undefined;
|
locationEnvironment?: req | undefined;
|
||||||
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
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 workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||||
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
||||||
|
|
||||||
// validate clients
|
// validate clients
|
||||||
const { membership } = await validateClientForWorkspace({
|
const { membership } = await validateClientForWorkspace({
|
||||||
userId: req.user?._id,
|
authData: req.authData,
|
||||||
serviceAccountId: req.serviceAccount?._id,
|
|
||||||
serviceTokenDataId: req.serviceTokenData?._id,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment
|
environment,
|
||||||
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
if (membership) {
|
if (membership) {
|
||||||
req.membership = 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();
|
return next();
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ export interface IServiceAccountWorkspacePermission extends Document {
|
|||||||
serviceAccount: Types.ObjectId;
|
serviceAccount: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
canRead: boolean;
|
read: boolean;
|
||||||
canWrite: boolean;
|
write: boolean;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorkspacePermission>(
|
const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorkspacePermission>(
|
||||||
@@ -27,19 +25,11 @@ const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorksp
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
canRead: {
|
read: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
canWrite: {
|
write: {
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
canUpdate: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
canDelete: {
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
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;
|
name: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('environmentSlug').exists().trim(),
|
body('environmentSlug').exists().trim(),
|
||||||
@@ -31,6 +32,7 @@ router.put(
|
|||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('environmentSlug').exists().trim(),
|
body('environmentSlug').exists().trim(),
|
||||||
@@ -47,6 +49,7 @@ router.delete(
|
|||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN],
|
acceptedRoles: [ADMIN],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('environmentSlug').exists().trim(),
|
body('environmentSlug').exists().trim(),
|
||||||
@@ -61,6 +64,7 @@ router.get(
|
|||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [MEMBER, ADMIN],
|
acceptedRoles: [MEMBER, ADMIN],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import {
|
|||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { query, body } from 'express-validator';
|
import { query, body } from 'express-validator';
|
||||||
import { secretsController } from '../../controllers/v2';
|
import { secretsController } from '../../controllers/v2';
|
||||||
import { validateSecrets } from '../../helpers/secret';
|
import { validateClientForSecrets } from '../../helpers/secrets';
|
||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED
|
SECRET_SHARED,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import {
|
import {
|
||||||
BatchSecretRequest
|
BatchSecretRequest
|
||||||
@@ -22,8 +24,7 @@ import {
|
|||||||
router.post(
|
router.post(
|
||||||
'/batch',
|
'/batch',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken']
|
||||||
requiredServiceTokenPermissions: ['read', 'write']
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -40,12 +41,11 @@ router.post(
|
|||||||
.filter((secretId) => secretId !== undefined)
|
.filter((secretId) => secretId !== undefined)
|
||||||
|
|
||||||
if (secretIds.length > 0) {
|
if (secretIds.length > 0) {
|
||||||
const relevantSecrets = await validateSecrets({
|
req.secrets = await validateClientForSecrets({
|
||||||
userId: req.user._id.toString(),
|
authData: req.authData,
|
||||||
secretIds
|
secretIds,
|
||||||
|
requiredPermissions: []
|
||||||
});
|
});
|
||||||
|
|
||||||
req.secrets = relevantSecrets;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -100,12 +100,13 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||||
requiredServiceTokenPermissions: ['write']
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: 'body'
|
locationWorkspaceId: 'body',
|
||||||
|
locationEnvironment: 'body',
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.createSecrets
|
secretsController.createSecrets
|
||||||
);
|
);
|
||||||
@@ -117,12 +118,13 @@ router.get(
|
|||||||
query('tagSlugs'),
|
query('tagSlugs'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||||
requiredServiceTokenPermissions: ['read']
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: 'query'
|
locationWorkspaceId: 'query',
|
||||||
|
locationEnvironment: 'query',
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
@@ -157,11 +159,11 @@ router.patch(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||||
requiredServiceTokenPermissions: ['write']
|
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecrets
|
secretsController.updateSecrets
|
||||||
);
|
);
|
||||||
@@ -186,11 +188,11 @@ router.delete(
|
|||||||
.isEmpty(),
|
.isEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken', 'serviceAccount']
|
||||||
requiredServiceTokenPermissions: ['write']
|
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecrets
|
secretsController.deleteSecrets
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -125,10 +125,8 @@ router.post(
|
|||||||
param('serviceAccountId').exists().isString().trim(),
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
body('workspaceId').exists().isString().notEmpty(),
|
body('workspaceId').exists().isString().notEmpty(),
|
||||||
body('environment').exists().isString().notEmpty(),
|
body('environment').exists().isString().notEmpty(),
|
||||||
body('canRead').isBoolean().optional(),
|
body('read').isBoolean().optional(),
|
||||||
body('canWrite').isBoolean().optional(),
|
body('write').isBoolean().optional(),
|
||||||
body('canUpdate').isBoolean().optional(),
|
|
||||||
body('canDelete').isBoolean().optional(),
|
|
||||||
body('encryptedKey').exists().isString().notEmpty(),
|
body('encryptedKey').exists().isString().notEmpty(),
|
||||||
body('nonce').exists().isString().notEmpty(),
|
body('nonce').exists().isString().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { tagController } from '../../controllers/v2';
|
|||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest,
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import { ADMIN, MEMBER } from '../../variables';
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ router.get(
|
|||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [MEMBER, ADMIN],
|
acceptedRoles: [MEMBER, ADMIN],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -39,6 +40,7 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [MEMBER, ADMIN],
|
acceptedRoles: [MEMBER, ADMIN],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('name').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
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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 DatabaseService from './DatabaseService';
|
||||||
import { logTelemetryMessage, getPostHogClient } from './PostHogClient';
|
// import { logTelemetryMessage, getPostHogClient } from './TelemetryService';
|
||||||
|
import TelemetryService from './TelemetryService';
|
||||||
import BotService from './BotService';
|
import BotService from './BotService';
|
||||||
import EventService from './EventService';
|
import EventService from './EventService';
|
||||||
import IntegrationService from './IntegrationService';
|
import IntegrationService from './IntegrationService';
|
||||||
import TokenService from './TokenService';
|
import TokenService from './TokenService';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
logTelemetryMessage,
|
TelemetryService,
|
||||||
getPostHogClient,
|
// logTelemetryMessage,
|
||||||
|
// getPostHogClient,
|
||||||
DatabaseService,
|
DatabaseService,
|
||||||
BotService,
|
BotService,
|
||||||
EventService,
|
EventService,
|
||||||
|
|||||||
Vendored
+1
-1
@@ -24,7 +24,7 @@ declare global {
|
|||||||
serviceTokenData: any;
|
serviceTokenData: any;
|
||||||
apiKeyData: any;
|
apiKeyData: any;
|
||||||
query?: any;
|
query?: any;
|
||||||
authTokenType: string;
|
authData: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ORG_INVITATION,
|
||||||
TOKEN_EMAIL_PASSWORD_RESET
|
TOKEN_EMAIL_PASSWORD_RESET
|
||||||
} from './token';
|
} 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 {
|
export {
|
||||||
OWNER,
|
OWNER,
|
||||||
@@ -113,6 +123,8 @@ export {
|
|||||||
ACTION_UPDATE_SECRETS,
|
ACTION_UPDATE_SECRETS,
|
||||||
ACTION_DELETE_SECRETS,
|
ACTION_DELETE_SECRETS,
|
||||||
ACTION_READ_SECRETS,
|
ACTION_READ_SECRETS,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS,
|
||||||
getIntegrationOptions,
|
getIntegrationOptions,
|
||||||
SMTP_HOST_SENDGRID,
|
SMTP_HOST_SENDGRID,
|
||||||
SMTP_HOST_MAILGUN,
|
SMTP_HOST_MAILGUN,
|
||||||
@@ -124,5 +136,9 @@ export {
|
|||||||
TOKEN_EMAIL_CONFIRMATION,
|
TOKEN_EMAIL_CONFIRMATION,
|
||||||
TOKEN_EMAIL_MFA,
|
TOKEN_EMAIL_MFA,
|
||||||
TOKEN_EMAIL_ORG_INVITATION,
|
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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
serviceAccount: string;
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
environment: string;
|
environment: string;
|
||||||
canRead: boolean;
|
read: boolean;
|
||||||
canWrite: boolean;
|
write: boolean;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateServiceAccountWorkspacePermissionDTO = {
|
export type CreateServiceAccountWorkspacePermissionDTO = {
|
||||||
serviceAccountId: string;
|
serviceAccountId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
canRead: boolean;
|
read: boolean;
|
||||||
canWrite: boolean;
|
write: boolean;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
nonce: string;
|
nonce: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-43
@@ -48,10 +48,8 @@ const createProjectLevelPermissionSchema = yup.object({
|
|||||||
workspace: yup.string().required().label('Workspace'),
|
workspace: yup.string().required().label('Workspace'),
|
||||||
environment: yup.string().required().label('Environment'),
|
environment: yup.string().required().label('Environment'),
|
||||||
permissions: yup.object().shape({
|
permissions: yup.object().shape({
|
||||||
canRead: yup.boolean().required(),
|
read: yup.boolean().required(),
|
||||||
canWrite: yup.boolean().required(),
|
write: yup.boolean().required()
|
||||||
canUpdate: yup.boolean().required(),
|
|
||||||
canDelete: yup.boolean().required(),
|
|
||||||
}).defined().required()
|
}).defined().required()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,7 +89,7 @@ export const SAProjectLevelPermissionsTable = ({
|
|||||||
privateKey,
|
privateKey,
|
||||||
workspace,
|
workspace,
|
||||||
environment,
|
environment,
|
||||||
permissions: { canRead, canWrite, canUpdate, canDelete }
|
permissions: { read, write }
|
||||||
}: CreateProjectLevelPermissionForm) => {
|
}: CreateProjectLevelPermissionForm) => {
|
||||||
|
|
||||||
// TODO: clean up / modularize this function
|
// TODO: clean up / modularize this function
|
||||||
@@ -126,10 +124,8 @@ export const SAProjectLevelPermissionsTable = ({
|
|||||||
serviceAccountId,
|
serviceAccountId,
|
||||||
workspaceId: workspace,
|
workspaceId: workspace,
|
||||||
environment,
|
environment,
|
||||||
canRead,
|
read,
|
||||||
canWrite,
|
write,
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
encryptedKey: ciphertext,
|
encryptedKey: ciphertext,
|
||||||
nonce
|
nonce
|
||||||
});
|
});
|
||||||
@@ -187,10 +183,8 @@ export const SAProjectLevelPermissionsTable = ({
|
|||||||
_id,
|
_id,
|
||||||
workspace,
|
workspace,
|
||||||
environment,
|
environment,
|
||||||
canRead,
|
read,
|
||||||
canWrite,
|
write
|
||||||
canUpdate,
|
|
||||||
canDelete
|
|
||||||
}) => {
|
}) => {
|
||||||
const environmentName = (workspace.environments.find((env) => env.slug === environment))?.name;
|
const environmentName = (workspace.environments.find((env) => env.slug === environment))?.name;
|
||||||
return (
|
return (
|
||||||
@@ -200,28 +194,14 @@ export const SAProjectLevelPermissionsTable = ({
|
|||||||
<Td>
|
<Td>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="isReadPermissionEnabled"
|
id="isReadPermissionEnabled"
|
||||||
isChecked={canRead}
|
isChecked={read}
|
||||||
isDisabled
|
isDisabled
|
||||||
>{/**/}</Checkbox>
|
>{/**/}</Checkbox>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="isWritePermissionEnabled"
|
id="isWritePermissionEnabled"
|
||||||
isChecked={canWrite}
|
isChecked={write}
|
||||||
isDisabled
|
|
||||||
>{/**/}</Checkbox>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Checkbox
|
|
||||||
id="isUpdatePermissionEnabled"
|
|
||||||
isChecked={canUpdate}
|
|
||||||
isDisabled
|
|
||||||
>{/**/}</Checkbox>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Checkbox
|
|
||||||
id="isDeletePermissionEnabled"
|
|
||||||
isChecked={canDelete}
|
|
||||||
isDisabled
|
isDisabled
|
||||||
>{/**/}</Checkbox>
|
>{/**/}</Checkbox>
|
||||||
</Td>
|
</Td>
|
||||||
@@ -352,28 +332,18 @@ export const SAProjectLevelPermissionsTable = ({
|
|||||||
control={control}
|
control={control}
|
||||||
name="permissions"
|
name="permissions"
|
||||||
defaultValue={{
|
defaultValue={{
|
||||||
canRead: true,
|
read: true,
|
||||||
canWrite: false,
|
write: false
|
||||||
canUpdate: false,
|
|
||||||
canDelete: false
|
|
||||||
}}
|
}}
|
||||||
render={({ field: { onChange, value }, fieldState: { error }}) => {
|
render={({ field: { onChange, value }, fieldState: { error }}) => {
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
label: 'Read (default)',
|
label: 'Read (default)',
|
||||||
value: 'canRead'
|
value: 'read'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Write',
|
label: 'Write',
|
||||||
value: 'canWrite'
|
value: 'write'
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Update',
|
|
||||||
value: 'canUpdate'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
value: 'canDelete'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user