Merge branch 'Infisical:main' into patch-1

This commit is contained in:
JesseBot
2023-09-08 18:50:24 +02:00
committed by GitHub
254 changed files with 16932 additions and 10165 deletions
+114 -1
View File
@@ -10,6 +10,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.319.0", "@aws-sdk/client-secrets-manager": "^3.319.0",
"@casl/ability": "^6.5.0",
"@casl/mongoose": "^7.2.1",
"@godaddy/terminus": "^4.12.0", "@godaddy/terminus": "^4.12.0",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
@@ -17,6 +19,7 @@
"@sentry/tracing": "^7.48.0", "@sentry/tracing": "^7.48.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",
"@ucast/mongo2js": "^1.3.4",
"argon2": "^0.30.3", "argon2": "^0.30.3",
"aws-sdk": "^2.1364.0", "aws-sdk": "^2.1364.0",
"axios": "^1.3.5", "axios": "^1.3.5",
@@ -59,7 +62,8 @@
"typescript": "^4.9.3", "typescript": "^4.9.3",
"utility-types": "^3.10.0", "utility-types": "^3.10.0",
"winston": "^3.8.2", "winston": "^3.8.2",
"winston-loki": "^6.0.7" "winston-loki": "^6.0.6",
"zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^29.3.1", "@jest/globals": "^29.3.1",
@@ -3339,6 +3343,26 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true "dev": true
}, },
"node_modules/@casl/ability": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.5.0.tgz",
"integrity": "sha512-3guc94ugr5ylZQIpJTLz0CDfwNi0mxKVECj1vJUPAvs+Lwunh/dcuUjwzc4MHM9D8JOYX0XUZMEPedpB3vIbOw==",
"dependencies": {
"@ucast/mongo2js": "^1.3.0"
},
"funding": {
"url": "https://github.com/stalniy/casl/blob/master/BACKERS.md"
}
},
"node_modules/@casl/mongoose": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@casl/mongoose/-/mongoose-7.2.1.tgz",
"integrity": "sha512-pojgSWYKNIwFM6wWDNct1YD0+8nIxhe2jp5jBbK8JGU60dEs2o0Yw3mCo2y7nBwbvRC2oEots/BlLMVb1Wdo8A==",
"peerDependencies": {
"@casl/ability": "^6.3.2",
"mongoose": "^6.0.13 || ^7.0.0"
}
},
"node_modules/@colors/colors": { "node_modules/@colors/colors": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -6174,6 +6198,37 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@ucast/core": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz",
"integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g=="
},
"node_modules/@ucast/js": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.3.tgz",
"integrity": "sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ==",
"dependencies": {
"@ucast/core": "^1.0.0"
}
},
"node_modules/@ucast/mongo": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz",
"integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==",
"dependencies": {
"@ucast/core": "^1.4.1"
}
},
"node_modules/@ucast/mongo2js": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz",
"integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==",
"dependencies": {
"@ucast/core": "^1.6.1",
"@ucast/js": "^3.0.0",
"@ucast/mongo": "^2.4.0"
}
},
"node_modules/@xmldom/xmldom": { "node_modules/@xmldom/xmldom": {
"version": "0.8.10", "version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
@@ -16626,6 +16681,14 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
}, },
"dependencies": { "dependencies": {
@@ -19324,6 +19387,20 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true "dev": true
}, },
"@casl/ability": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.5.0.tgz",
"integrity": "sha512-3guc94ugr5ylZQIpJTLz0CDfwNi0mxKVECj1vJUPAvs+Lwunh/dcuUjwzc4MHM9D8JOYX0XUZMEPedpB3vIbOw==",
"requires": {
"@ucast/mongo2js": "^1.3.0"
}
},
"@casl/mongoose": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@casl/mongoose/-/mongoose-7.2.1.tgz",
"integrity": "sha512-pojgSWYKNIwFM6wWDNct1YD0+8nIxhe2jp5jBbK8JGU60dEs2o0Yw3mCo2y7nBwbvRC2oEots/BlLMVb1Wdo8A==",
"requires": {}
},
"@colors/colors": { "@colors/colors": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -21575,6 +21652,37 @@
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
} }
}, },
"@ucast/core": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz",
"integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g=="
},
"@ucast/js": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.3.tgz",
"integrity": "sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ==",
"requires": {
"@ucast/core": "^1.0.0"
}
},
"@ucast/mongo": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz",
"integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==",
"requires": {
"@ucast/core": "^1.4.1"
}
},
"@ucast/mongo2js": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz",
"integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==",
"requires": {
"@ucast/core": "^1.6.1",
"@ucast/js": "^3.0.0",
"@ucast/mongo": "^2.4.0"
}
},
"@xmldom/xmldom": { "@xmldom/xmldom": {
"version": "0.8.10", "version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
@@ -29273,6 +29381,11 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true "dev": true
},
"zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
} }
} }
} }
+5 -1
View File
@@ -1,6 +1,8 @@
{ {
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.319.0", "@aws-sdk/client-secrets-manager": "^3.319.0",
"@casl/ability": "^6.5.0",
"@casl/mongoose": "^7.2.1",
"@godaddy/terminus": "^4.12.0", "@godaddy/terminus": "^4.12.0",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
@@ -8,6 +10,7 @@
"@sentry/tracing": "^7.48.0", "@sentry/tracing": "^7.48.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",
"@ucast/mongo2js": "^1.3.4",
"argon2": "^0.30.3", "argon2": "^0.30.3",
"aws-sdk": "^2.1364.0", "aws-sdk": "^2.1364.0",
"axios": "^1.3.5", "axios": "^1.3.5",
@@ -50,7 +53,8 @@
"typescript": "^4.9.3", "typescript": "^4.9.3",
"utility-types": "^3.10.0", "utility-types": "^3.10.0",
"winston": "^3.8.2", "winston": "^3.8.2",
"winston-loki": "^6.0.7" "winston-loki": "^6.0.6",
"zod": "^3.21.4"
}, },
"name": "infisical-api", "name": "infisical-api",
"version": "1.0.0", "version": "1.0.0",
+9 -2
View File
@@ -17,6 +17,8 @@ import {
getJwtRefreshSecret getJwtRefreshSecret
} from "../../config"; } from "../../config";
import { ActorType } from "../../ee/models"; import { ActorType } from "../../ee/models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
declare module "jsonwebtoken" { declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -32,7 +34,9 @@ declare module "jsonwebtoken" {
* @returns * @returns
*/ */
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body; const {
body: { email, clientPublicKey }
} = await validateRequest(reqValidator.Login1V1, req);
const user = await User.findOne({ const user = await User.findOne({
email email
@@ -76,7 +80,10 @@ export const login1 = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
const { email, clientProof } = req.body; const {
body: { email, clientProof }
} = await validateRequest(reqValidator.Login2V1, req);
const user = await User.findOne({ const user = await User.findOne({
email email
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag"); }).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
+64 -25
View File
@@ -2,6 +2,15 @@ import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Bot, BotKey } from "../../models"; import { Bot, BotKey } from "../../models";
import { createBot } from "../../helpers/bot"; import { createBot } from "../../helpers/bot";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/bot";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../utils/errors";
interface BotKey { interface BotKey {
encryptedKey: string; encryptedKey: string;
@@ -16,10 +25,17 @@ interface BotKey {
* @returns * @returns
*/ */
export const getBotByWorkspaceId = async (req: Request, res: Response) => { export const getBotByWorkspaceId = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
let bot = await Bot.findOne({ let bot = await Bot.findOne({
workspace: workspaceId, workspace: workspaceId
}); });
if (!bot) { if (!bot) {
@@ -27,12 +43,12 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
// -> create a new bot and return it // -> create a new bot and return it
bot = await createBot({ bot = await createBot({
name: "Infisical Bot", name: "Infisical Bot",
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
} }
return res.status(200).send({ return res.status(200).send({
bot, bot
}); });
}; };
@@ -43,46 +59,69 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const setBotActiveState = async (req: Request, res: Response) => { export const setBotActiveState = async (req: Request, res: Response) => {
const { isActive, botKey }: { isActive: boolean, botKey: BotKey } = req.body; const {
body: { botKey, isActive },
params: { botId }
} = await validateRequest(reqValidator.SetBotActiveStateV1, req);
const bot = await Bot.findById(botId);
if (!bot) {
throw BadRequestError({ message: "Bot not found" });
}
const userId = req.user._id;
const { permission } = await getUserProjectPermissions(userId, bot.workspace.toString());
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
);
if (isActive) { if (isActive) {
// bot state set to active -> share workspace key with bot // bot state set to active -> share workspace key with bot
if (!botKey?.encryptedKey || !botKey?.nonce) { if (!botKey?.encryptedKey || !botKey?.nonce) {
return res.status(400).send({ return res.status(400).send({
message: "Failed to set bot state to active - missing bot key", message: "Failed to set bot state to active - missing bot key"
}); });
} }
await BotKey.findOneAndUpdate({ await BotKey.findOneAndUpdate(
workspace: req.bot.workspace, {
}, { workspace: bot.workspace
},
{
encryptedKey: botKey.encryptedKey, encryptedKey: botKey.encryptedKey,
nonce: botKey.nonce, nonce: botKey.nonce,
sender: req.user._id, sender: userId,
bot: req.bot._id, bot: bot._id,
workspace: req.bot.workspace, workspace: bot.workspace
}, { },
{
upsert: true, upsert: true,
new: true, new: true
}); }
);
} else { } else {
// case: bot state set to inactive -> delete bot's workspace key // case: bot state set to inactive -> delete bot's workspace key
await BotKey.deleteOne({ await BotKey.deleteOne({
bot: req.bot._id, bot: bot._id
}); });
} }
const bot = await Bot.findOneAndUpdate({ const updatedBot = await Bot.findOneAndUpdate(
_id: req.bot._id, {
}, { _id: bot._id
isActive, },
}, { {
new: true, isActive
}); },
{
new: true
}
);
if (!bot) throw new Error("Failed to update bot active state"); if (!updatedBot) throw new Error("Failed to update bot active state");
return res.status(200).send({ return res.status(200).send({
bot, bot
}); });
}; };
+2
View File
@@ -16,6 +16,7 @@ import * as workspaceController from "./workspaceController";
import * as secretScanningController from "./secretScanningController"; import * as secretScanningController from "./secretScanningController";
import * as webhookController from "./webhookController"; import * as webhookController from "./webhookController";
import * as secretImportController from "./secretImportController"; import * as secretImportController from "./secretImportController";
import * as roleController from "./roleController";
export { export {
authController, authController,
@@ -35,5 +36,6 @@ export {
workspaceController, workspaceController,
secretScanningController, secretScanningController,
webhookController, webhookController,
roleController,
secretImportController secretImportController
}; };
@@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { standardRequest } from "../../config/request"; import { standardRequest } from "../../config/request";
import { getApps, getTeams, revokeAccess } from "../../integrations"; import { getApps, getTeams, revokeAccess } from "../../integrations";
import { Bot, IntegrationAuth } from "../../models"; import { Bot, IntegrationAuth, Workspace } from "../../models";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { IntegrationService } from "../../services"; import { IntegrationService } from "../../services";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
@@ -18,12 +18,25 @@ import {
getIntegrationOptions as getIntegrationOptionsFunc getIntegrationOptions as getIntegrationOptionsFunc
} from "../../variables"; } from "../../variables";
import { exchangeRefresh } from "../../integrations"; import { exchangeRefresh } from "../../integrations";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/integrationAuth";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { getIntegrationAuthAccessHelper } from "../../helpers";
import { ObjectId } from "mongodb";
/*** /***
* Return integration authorization with id [integrationAuthId] * Return integration authorization with id [integrationAuthId]
*/ */
export const getIntegrationAuth = async (req: Request, res: Response) => { export const getIntegrationAuth = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params; const {
params: { integrationAuthId }
} = await validateRequest(reqValidator.GetIntegrationAuthV1, req);
const integrationAuth = await IntegrationAuth.findById(integrationAuthId); const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
if (!integrationAuth) if (!integrationAuth)
@@ -31,6 +44,15 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
message: "Failed to find integration authorization" message: "Failed to find integration authorization"
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
return res.status(200).send({ return res.status(200).send({
integrationAuth integrationAuth
}); });
@@ -52,14 +74,18 @@ export const getIntegrationOptions = async (req: Request, res: Response) => {
*/ */
export const oAuthExchange = async (req: Request, res: Response) => { export const oAuthExchange = async (req: Request, res: Response) => {
const { const {
workspaceId, body: { integration, workspaceId, code, url }
code, } = await validateRequest(reqValidator.OauthExchangeV1, req);
integration,
url
} = req.body;
if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration"); if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
const environments = req.membership.workspace?.environments || []; const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const workspace = await Workspace.findById(workspaceId);
const environments = workspace?.environments || [];
if (environments.length === 0) { if (environments.length === 0) {
throw new Error("Failed to get environments"); throw new Error("Failed to get environments");
} }
@@ -99,25 +125,16 @@ export const oAuthExchange = async (req: Request, res: Response) => {
export const saveIntegrationToken = async (req: Request, res: Response) => { export const saveIntegrationToken = async (req: Request, res: Response) => {
// TODO: refactor // TODO: refactor
// TODO: check if access token is valid for each integration // TODO: check if access token is valid for each integration
let integrationAuth; let integrationAuth;
const { const {
workspaceId, body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
accessId, } = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
refreshToken,
accessToken, const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
url, ForbiddenError.from(permission).throwUnlessCan(
namespace, ProjectPermissionActions.Create,
integration ProjectPermissionSub.Integrations
}: { );
workspaceId: string;
accessId: string | undefined;
refreshToken: string | undefined;
accessToken: string | undefined;
url: string;
namespace: string;
integration: string;
} = req.body;
const bot = await Bot.findOne({ const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
@@ -195,13 +212,29 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getIntegrationAuthApps = async (req: Request, res: Response) => { export const getIntegrationAuthApps = async (req: Request, res: Response) => {
const teamId = req.query.teamId as string; const {
const workspaceSlug = req.query.workspaceSlug as string; params: { integrationAuthId },
query: { teamId, workspaceSlug }
} = await validateRequest(reqValidator.GetIntegrationAuthAppsV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const apps = await getApps({ const apps = await getApps({
integrationAuth: req.integrationAuth, integrationAuth: integrationAuth,
accessToken: req.accessToken, accessToken: accessToken,
accessId: req.accessId, accessId: accessId,
...(teamId && { teamId }), ...(teamId && { teamId }),
...(workspaceSlug && { workspaceSlug }) ...(workspaceSlug && { workspaceSlug })
}); });
@@ -218,9 +251,27 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getIntegrationAuthTeams = async (req: Request, res: Response) => { export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
const {
params: { integrationAuthId }
} = await validateRequest(reqValidator.GetIntegrationAuthTeamsV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const teams = await getTeams({ const teams = await getTeams({
integrationAuth: req.integrationAuth, integrationAuth: integrationAuth,
accessToken: req.accessToken accessToken: accessToken
}); });
return res.status(200).send({ return res.status(200).send({
@@ -235,7 +286,24 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => { export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => {
const appId = req.query.appId as string; const {
params: { integrationAuthId },
query: { appId }
} = await validateRequest(reqValidator.GetIntegrationAuthVercelBranchesV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
interface VercelBranch { interface VercelBranch {
ref: string; ref: string;
@@ -245,9 +313,9 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
const params = new URLSearchParams({ const params = new URLSearchParams({
projectId: appId, projectId: appId,
...(req.integrationAuth.teamId ...(integrationAuth.teamId
? { ? {
teamId: req.integrationAuth.teamId teamId: integrationAuth.teamId
} }
: {}) : {})
}); });
@@ -260,7 +328,7 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
{ {
params, params,
headers: { headers: {
Authorization: `Bearer ${req.accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json" "Accept-Encoding": "application/json"
} }
} }
@@ -281,7 +349,24 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
* @param res * @param res
*/ */
export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => { export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => {
const appId = req.query.appId as string; const {
params: { integrationAuthId },
query: { appId }
} = await validateRequest(reqValidator.GetIntegrationAuthRailwayEnvironmentsV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
interface RailwayEnvironment { interface RailwayEnvironment {
node: { node: {
@@ -331,7 +416,7 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
}, },
{ {
headers: { headers: {
Authorization: `Bearer ${req.accessToken}`, Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json" "Content-Type": "application/json"
} }
} }
@@ -357,7 +442,24 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
* @param res * @param res
*/ */
export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => { export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => {
const appId = req.query.appId as string; const {
params: { integrationAuthId },
query: { appId }
} = await validateRequest(reqValidator.GetIntegrationAuthRailwayServicesV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
interface RailwayService { interface RailwayService {
node: { node: {
@@ -422,7 +524,7 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
}, },
{ {
headers: { headers: {
Authorization: `Bearer ${req.accessToken}`, Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json" "Content-Type": "application/json"
} }
} }
@@ -446,7 +548,6 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
* @returns * @returns
*/ */
export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: Response) => { export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: Response) => {
interface WorkspaceResponse { interface WorkspaceResponse {
size: number; size: number;
page: number; page: number;
@@ -466,31 +567,46 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
updated_on: string; updated_on: string;
} }
const {
params: { integrationAuthId }
} = await validateRequest(reqValidator.GetIntegrationAuthBitbucketWorkspacesV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const workspaces: Workspace[] = []; const workspaces: Workspace[] = [];
let hasNextPage = true; let hasNextPage = true;
let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces` let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces`;
while (hasNextPage) { while (hasNextPage) {
const { data }: { data: WorkspaceResponse } = await standardRequest.get( const { data }: { data: WorkspaceResponse } = await standardRequest.get(workspaceUrl, {
workspaceUrl,
{
headers: { headers: {
Authorization: `Bearer ${req.accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json" "Accept-Encoding": "application/json"
} }
} });
);
if (data?.values.length > 0) { if (data?.values.length > 0) {
data.values.forEach((workspace) => { data.values.forEach((workspace) => {
workspaces.push(workspace) workspaces.push(workspace);
}) });
} }
if (data.next) { if (data.next) {
workspaceUrl = data.next workspaceUrl = data.next;
} else { } else {
hasNextPage = false hasNextPage = false;
} }
} }
@@ -506,7 +622,24 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
* @returns * @returns
*/ */
export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res: Response) => { export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res: Response) => {
const appId = req.query.appId as string; const {
params: { integrationAuthId },
query: { appId }
} = await validateRequest(reqValidator.GetIntegrationAuthNorthflankSecretGroupsV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
interface NorthflankSecretGroup { interface NorthflankSecretGroup {
id: string; id: string;
@@ -532,23 +665,21 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage), per_page: String(perPage),
filter: "all", filter: "all"
}); });
const { const {
data: { data: {
data: { data: { secrets }
secrets
}
} }
} = await standardRequest.get<{ data: { secrets: NorthflankSecretGroup[] } }>( } = await standardRequest.get<{ data: { secrets: NorthflankSecretGroup[] } }>(
`${INTEGRATION_NORTHFLANK_API_URL}/v1/projects/${appId}/secrets`, `${INTEGRATION_NORTHFLANK_API_URL}/v1/projects/${appId}/secrets`,
{ {
params, params,
headers: { headers: {
Authorization: `Bearer ${req.accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
} }
); );
@@ -570,7 +701,7 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
return res.status(200).send({ return res.status(200).send({
secretGroups secretGroups
}); });
} };
/** /**
* Return list of build configs for TeamCity project with id [appId] * Return list of build configs for TeamCity project with id [appId]
@@ -579,7 +710,23 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
* @returns * @returns
*/ */
export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: Response) => { export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: Response) => {
const appId = req.query.appId as string; const {
params: { integrationAuthId,appId },
} = await validateRequest(reqValidator.GetIntegrationAuthTeamCityBuildConfigsV1 , req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
interface TeamCityBuildConfig { interface TeamCityBuildConfig {
id: string; id: string;
@@ -630,12 +777,31 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
* @returns * @returns
*/ */
export const deleteIntegrationAuth = async (req: Request, res: Response) => { export const deleteIntegrationAuth = async (req: Request, res: Response) => {
const integrationAuth = await revokeAccess({ const {
integrationAuth: req.integrationAuth, params: { integrationAuthId }
accessToken: req.accessToken } = await validateRequest(reqValidator.DeleteIntegrationAuthV1, req);
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
}); });
if (!integrationAuth) return res.status(400).send({ const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
);
const deletedIntegrationAuth = await revokeAccess({
integrationAuth: integrationAuth,
accessToken: accessToken
});
if (!deletedIntegrationAuth)
return res.status(400).send({
message: "Failed to find integration authorization" message: "Failed to find integration authorization"
}); });
@@ -644,16 +810,15 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
{ {
type: EventType.UNAUTHORIZE_INTEGRATION, type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: { metadata: {
integration: integrationAuth.integration integration: deletedIntegrationAuth.integration
} }
}, },
{ {
workspaceId: integrationAuth.workspace workspaceId: deletedIntegrationAuth.workspace
} }
); );
return res.status(200).send({ return res.status(200).send({
integrationAuth integrationAuth: deletedIntegrationAuth
}); });
}; };
@@ -1,6 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Folder, Integration } from "../../models"; import { IWorkspace, Integration, Folder, IntegrationAuth } from "../../models";
import { EventService } from "../../services"; import { EventService } from "../../services";
import { eventStartIntegration } from "../../events"; import { eventStartIntegration } from "../../events";
import { getFolderByPath } from "../../services/FolderService"; import { getFolderByPath } from "../../services/FolderService";
@@ -8,6 +8,14 @@ import { BadRequestError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { syncSecretsToActiveIntegrationsQueue } from "../../queues/integrations/syncSecretsToThirdPartyServices"; import { syncSecretsToActiveIntegrationsQueue } from "../../queues/integrations/syncSecretsToThirdPartyServices";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/integration";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
/** /**
* Create/initialize an (empty) integration for integration authorization * Create/initialize an (empty) integration for integration authorization
@@ -17,24 +25,42 @@ import { syncSecretsToActiveIntegrationsQueue } from "../../queues/integrations/
*/ */
export const createIntegration = async (req: Request, res: Response) => { export const createIntegration = async (req: Request, res: Response) => {
const { const {
integrationAuthId, body: {
app,
appId,
isActive, isActive,
sourceEnvironment, sourceEnvironment,
targetEnvironment, secretPath,
targetEnvironmentId, app,
path,
appId,
owner,
region,
targetService, targetService,
targetServiceId, targetServiceId,
owner, integrationAuthId,
path, targetEnvironment,
region, targetEnvironmentId
secretPath, }
metadata } = await validateRequest(reqValidator.CreateIntegrationV1, req);
} = req.body;
const integrationAuth = await IntegrationAuth.findById(integrationAuthId)
.populate<{ workspace: IWorkspace }>("workspace")
.select(
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt"
);
if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const folders = await Folder.findOne({ const folders = await Folder.findOne({
workspace: req.integrationAuth.workspace._id, workspace: integrationAuth.workspace._id,
environment: sourceEnvironment environment: sourceEnvironment
}); });
@@ -42,7 +68,7 @@ export const createIntegration = async (req: Request, res: Response) => {
const folder = getFolderByPath(folders.nodes, secretPath); const folder = getFolderByPath(folders.nodes, secretPath);
if (!folder) { if (!folder) {
throw BadRequestError({ throw BadRequestError({
message: "Path for service token does not exist" message: "Folder path doesn't exist"
}); });
} }
} }
@@ -51,7 +77,7 @@ export const createIntegration = async (req: Request, res: Response) => {
// initialize new integration after saving integration access token // initialize new integration after saving integration access token
const integration = await new Integration({ const integration = await new Integration({
workspace: req.integrationAuth.workspace._id, workspace: integrationAuth.workspace._id,
environment: sourceEnvironment, environment: sourceEnvironment,
isActive, isActive,
app, app,
@@ -64,9 +90,8 @@ export const createIntegration = async (req: Request, res: Response) => {
path, path,
region, region,
secretPath, secretPath,
integration: req.integrationAuth.integration, integration: integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId), integrationAuth: new Types.ObjectId(integrationAuthId)
metadata
}).save(); }).save();
if (integration) { if (integration) {
@@ -120,6 +145,7 @@ export const updateIntegration = async (req: Request, res: Response) => {
// integration has the correct fields populated in [Integration] // integration has the correct fields populated in [Integration]
const { const {
body: {
environment, environment,
isActive, isActive,
app, app,
@@ -127,10 +153,24 @@ export const updateIntegration = async (req: Request, res: Response) => {
targetEnvironment, targetEnvironment,
owner, // github-specific integration param owner, // github-specific integration param
secretPath secretPath
} = req.body; },
params: { integrationId }
} = await validateRequest(reqValidator.UpdateIntegrationV1, req);
const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions(
req.user._id,
integration.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
);
const folders = await Folder.findOne({ const folders = await Folder.findOne({
workspace: req.integration.workspace, workspace: integration.workspace,
environment environment
}); });
@@ -143,9 +183,9 @@ export const updateIntegration = async (req: Request, res: Response) => {
} }
} }
const integration = await Integration.findOneAndUpdate( const updatedIntegration = await Integration.findOneAndUpdate(
{ {
_id: req.integration._id _id: integration._id
}, },
{ {
environment, environment,
@@ -161,18 +201,18 @@ export const updateIntegration = async (req: Request, res: Response) => {
} }
); );
if (integration) { if (updatedIntegration) {
// trigger event - push secrets // trigger event - push secrets
EventService.handleEvent({ EventService.handleEvent({
event: eventStartIntegration({ event: eventStartIntegration({
workspaceId: integration.workspace, workspaceId: updatedIntegration.workspace,
environment environment
}) })
}); });
} }
return res.status(200).send({ return res.status(200).send({
integration integration: updatedIntegration
}); });
}; };
@@ -183,13 +223,27 @@ export const updateIntegration = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteIntegration = async (req: Request, res: Response) => { export const deleteIntegration = async (req: Request, res: Response) => {
const { integrationId } = req.params; const {
params: { integrationId }
} = await validateRequest(reqValidator.DeleteIntegrationV1, req);
const integration = await Integration.findOneAndDelete({ const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions(
req.user._id,
integration.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
);
const deletedIntegration = await Integration.findOneAndDelete({
_id: integrationId _id: integrationId
}); });
if (!integration) throw new Error("Failed to find integration"); if (!deletedIntegration) throw new Error("Failed to find integration");
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
req.authData, req.authData,
@@ -223,12 +277,20 @@ export const deleteIntegration = async (req: Request, res: Response) => {
// Will trigger sync for all integrations within the given env and workspace id // Will trigger sync for all integrations within the given env and workspace id
export const manualSync = async (req: Request, res: Response) => { export const manualSync = async (req: Request, res: Response) => {
const { workspaceId, environment } = req.body; const {
body: { workspaceId, environment }
} = await validateRequest(reqValidator.ManualSyncV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
);
syncSecretsToActiveIntegrationsQueue({ syncSecretsToActiveIntegrationsQueue({
workspaceId, workspaceId,
environment environment
}) });
res.status(200).send() res.status(200).send();
}; };
+25 -7
View File
@@ -4,6 +4,14 @@ import { Key } from "../../models";
import { findMembership } from "../../helpers/membership"; import { findMembership } from "../../helpers/membership";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/key";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
/** /**
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with * Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
@@ -13,13 +21,21 @@ import { EEAuditLogService } from "../../ee/services";
* @returns * @returns
*/ */
export const uploadKey = async (req: Request, res: Response) => { export const uploadKey = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
const { key } = req.body; params: { workspaceId },
body: { key }
} = await validateRequest(reqValidator.UploadKeyV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
);
// validate membership of receiver // validate membership of receiver
const receiverMembership = await findMembership({ const receiverMembership = await findMembership({
user: key.userId, user: key.userId,
workspace: workspaceId, workspace: workspaceId
}); });
if (!receiverMembership) { if (!receiverMembership) {
@@ -31,11 +47,11 @@ export const uploadKey = async (req: Request, res: Response) => {
nonce: key.nonce, nonce: key.nonce,
sender: req.user._id, sender: req.user._id,
receiver: key.userId, receiver: key.userId,
workspace: workspaceId, workspace: workspaceId
}).save(); }).save();
return res.status(200).send({ return res.status(200).send({
message: "Successfully uploaded key to workspace", message: "Successfully uploaded key to workspace"
}); });
}; };
@@ -46,12 +62,14 @@ export const uploadKey = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getLatestKey = async (req: Request, res: Response) => { export const getLatestKey = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetLatestKeyV1, req);
// get latest key // get latest key
const latestKey = await Key.find({ const latestKey = await Key.find({
workspace: workspaceId, workspace: workspaceId,
receiver: req.user._id, receiver: req.user._id
}) })
.sort({ createdAt: -1 }) .sort({ createdAt: -1 })
.limit(1) .limit(1)
@@ -1,12 +1,23 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IUser, Key, Membership, MembershipOrg, User } from "../../models"; import { IUser, Key, Membership, MembershipOrg, User, Workspace } from "../../models";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership"; import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
import { sendMail } from "../../helpers/nodemailer"; import { sendMail } from "../../helpers/nodemailer";
import { ACCEPTED, ADMIN, MEMBER } from "../../variables"; import { ACCEPTED, ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
import { getSiteURL } from "../../config"; import { getSiteURL } from "../../config";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/membership";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import Role from "../../models/role";
import { BadRequestError } from "../../utils/errors";
import { InviteUserToWorkspaceV1 } from "../../validation/workspace";
/** /**
* Check that user is a member of workspace with id [workspaceId] * Check that user is a member of workspace with id [workspaceId]
@@ -15,7 +26,10 @@ import { EEAuditLogService } from "../../ee/services";
* @returns * @returns
*/ */
export const validateMembership = async (req: Request, res: Response) => { export const validateMembership = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.ValidateMembershipV1, req);
// validate membership // validate membership
const membership = await findMembership({ const membership = await findMembership({
user: req.user._id, user: req.user._id,
@@ -38,7 +52,9 @@ export const validateMembership = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteMembership = async (req: Request, res: Response) => { export const deleteMembership = async (req: Request, res: Response) => {
const { membershipId } = req.params; const {
params: { membershipId }
} = await validateRequest(reqValidator.DeleteMembershipV1, req);
// check if membership to delete exists // check if membership to delete exists
const membershipToDelete = await Membership.findOne({ const membershipToDelete = await Membership.findOne({
@@ -49,21 +65,14 @@ export const deleteMembership = async (req: Request, res: Response) => {
throw new Error("Failed to delete workspace membership that doesn't exist"); throw new Error("Failed to delete workspace membership that doesn't exist");
} }
// check if user is a member and admin of the workspace const { permission } = await getUserProjectPermissions(
// whose membership we wish to delete req.user._id,
const membership = await Membership.findOne({ membershipToDelete.workspace.toString()
user: req.user._id, );
workspace: membershipToDelete.workspace ForbiddenError.from(permission).throwUnlessCan(
}); ProjectPermissionActions.Delete,
ProjectPermissionSub.Member
if (!membership) { );
throw new Error("Failed to validate workspace membership");
}
if (membership.role !== ADMIN) {
// user is not an admin member of the workspace
throw new Error("Insufficient role for deleting workspace membership");
}
// delete workspace membership // delete workspace membership
const deletedMembership = await deleteMember({ const deletedMembership = await deleteMember({
@@ -80,7 +89,7 @@ export const deleteMembership = async (req: Request, res: Response) => {
} }
}, },
{ {
workspaceId: membership.workspace workspaceId: membershipToDelete.workspace
} }
); );
@@ -96,42 +105,55 @@ export const deleteMembership = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const changeMembershipRole = async (req: Request, res: Response) => { export const changeMembershipRole = async (req: Request, res: Response) => {
const { membershipId } = req.params; const {
const { role } = req.body; body: { role },
params: { membershipId }
if (![ADMIN, MEMBER].includes(role)) { } = await validateRequest(reqValidator.ChangeMembershipRoleV1, req);
throw new Error("Failed to validate role");
}
// validate target membership // validate target membership
const membershipToChangeRole = await Membership const membershipToChangeRole = await Membership.findById(membershipId).populate<{ user: IUser }>(
.findById(membershipId) "user"
.populate<{ user: IUser }>("user"); );
if (!membershipToChangeRole) { if (!membershipToChangeRole) {
throw new Error("Failed to find membership to change role"); throw new Error("Failed to find membership to change role");
} }
// check if user is a member and admin of target membership's const { permission } = await getUserProjectPermissions(
// workspace req.user._id,
const membership = await findMembership({ membershipToChangeRole.workspace.toString()
user: req.user._id, );
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
);
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
if (isCustomRole) {
const wsRole = await Role.findOne({
slug: role,
isOrgRole: false,
workspace: membershipToChangeRole.workspace workspace: membershipToChangeRole.workspace
}); });
if (!wsRole) throw BadRequestError({ message: "Role not found" });
if (!membership) { const membership = await Membership.findByIdAndUpdate(membershipId, {
throw new Error("Failed to validate membership"); role: CUSTOM,
customRole: wsRole
});
return res.status(200).send({
membership
});
} }
if (membership.role !== ADMIN) { const membership = await Membership.findByIdAndUpdate(
// user is not an admin member of the workspace membershipId,
throw new Error("Insufficient role for changing member roles"); {
role
},
{
new: true
} }
);
const oldRole = membershipToChangeRole.role;
membershipToChangeRole.role = role;
await membershipToChangeRole.save();
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
req.authData, req.authData,
@@ -140,8 +162,8 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
metadata: { metadata: {
userId: membershipToChangeRole.user._id.toString(), userId: membershipToChangeRole.user._id.toString(),
email: membershipToChangeRole.user.email, email: membershipToChangeRole.user.email,
oldRole, oldRole: membershipToChangeRole.role,
newRole: membershipToChangeRole.role newRole: role
} }
}, },
{ {
@@ -150,7 +172,7 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
); );
return res.status(200).send({ return res.status(200).send({
membership: membershipToChangeRole membership
}); });
}; };
@@ -161,8 +183,15 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const inviteUserToWorkspace = async (req: Request, res: Response) => { export const inviteUserToWorkspace = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
const { email }: { email: string } = req.body; params: { workspaceId },
body: { email }
} = await validateRequest(InviteUserToWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
const invitee = await User.findOne({ const invitee = await User.findOne({
email email
@@ -179,11 +208,13 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
if (inviteeMembership) throw new Error("Failed to add existing member of workspace"); if (inviteeMembership) throw new Error("Failed to add existing member of workspace");
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw new Error("Failed to find workspace");
// validate invitee's organization membership - ensure that only // validate invitee's organization membership - ensure that only
// (accepted) organization members can be added to the workspace // (accepted) organization members can be added to the workspace
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
user: invitee._id, user: invitee._id,
organization: req.membership.workspace.organization, organization: workspace.organization,
status: ACCEPTED status: ACCEPTED
}); });
@@ -211,7 +242,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
substitutions: { substitutions: {
inviterFirstName: req.user.firstName, inviterFirstName: req.user.firstName,
inviterEmail: req.user.email, inviterEmail: req.user.email,
workspaceName: req.membership.workspace.name, workspaceName: workspace.name,
callback_url: (await getSiteURL()) + "/login" callback_url: (await getSiteURL()) + "/login"
} }
}); });
@@ -8,14 +8,8 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import { sendMail } from "../../helpers/nodemailer"; import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELicenseService } from "../../ee/services"; import { EELicenseService } from "../../ee/services";
import { import { ACCEPTED, INVITED, MEMBER, TOKEN_EMAIL_ORG_INVITATION } from "../../variables";
ACCEPTED, import * as reqValidator from "../../validation/membershipOrg";
ADMIN,
INVITED,
MEMBER,
OWNER,
TOKEN_EMAIL_ORG_INVITATION
} from "../../variables";
import { import {
getJwtSignupLifetime, getJwtSignupLifetime,
getJwtSignupSecret, getJwtSignupSecret,
@@ -23,6 +17,13 @@ import {
getSmtpConfigured getSmtpConfigured
} from "../../config"; } from "../../config";
import { validateUserEmail } from "../../validation"; import { validateUserEmail } from "../../validation";
import { validateRequest } from "../../helpers/validation";
import {
OrgPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
/** /**
* Delete organization membership with id [membershipOrgId] from organization * Delete organization membership with id [membershipOrgId] from organization
@@ -31,7 +32,9 @@ import { validateUserEmail } from "../../validation";
* @returns * @returns
*/ */
export const deleteMembershipOrg = async (req: Request, _res: Response) => { export const deleteMembershipOrg = async (req: Request, _res: Response) => {
const { membershipOrgId } = req.params; const {
params: { membershipOrgId }
} = await validateRequest(reqValidator.DelOrgMembershipv1, req);
// check if organization membership to delete exists // check if organization membership to delete exists
const membershipOrgToDelete = await MembershipOrg.findOne({ const membershipOrgToDelete = await MembershipOrg.findOne({
@@ -42,21 +45,14 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => {
throw new Error("Failed to delete organization membership that doesn't exist"); throw new Error("Failed to delete organization membership that doesn't exist");
} }
// check if user is a member and admin of the organization const { permission, membership: membershipOrg } = await getUserOrgPermissions(
// whose membership we wish to delete req.user._id,
const membershipOrg = await MembershipOrg.findOne({ membershipOrgToDelete.organization.toString()
user: req.user._id, );
organization: membershipOrgToDelete.organization ForbiddenError.from(permission).throwUnlessCan(
}); OrgPermissionActions.Delete,
OrgPermissionSubjects.Member
if (!membershipOrg) { );
throw new Error("Failed to validate organization membership");
}
if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) {
// user is not an admin member of the organization
throw new Error("Insufficient role for deleting organization membership");
}
// delete organization membership // delete organization membership
await deleteMemberFromOrg({ await deleteMemberFromOrg({
@@ -96,20 +92,18 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
*/ */
export const inviteUserToOrganization = async (req: Request, res: Response) => { export const inviteUserToOrganization = async (req: Request, res: Response) => {
let inviteeMembershipOrg, completeInviteLink; let inviteeMembershipOrg, completeInviteLink;
const { organizationId, inviteeEmail } = req.body; const {
body: { inviteeEmail, organizationId }
} = await validateRequest(reqValidator.InviteUserToOrgv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Member
);
const host = req.headers.host; const host = req.headers.host;
const siteUrl = `${req.protocol}://${host}`; const siteUrl = `${req.protocol}://${host}`;
// validate membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: new Types.ObjectId(organizationId)
});
if (!membershipOrg) {
throw new Error("Failed to validate organization membership");
}
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
const ssoConfig = await SSOConfig.findOne({ const ssoConfig = await SSOConfig.findOne({
@@ -119,8 +113,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
if (ssoConfig && ssoConfig.isActive) { if (ssoConfig && ssoConfig.isActive) {
// case: SAML SSO is enabled for the organization // case: SAML SSO is enabled for the organization
return res.status(400).send({ return res.status(400).send({
message: message: "Failed to invite member due to SAML SSO configured for organization"
"Failed to invite member due to SAML SSO configured for organization"
}); });
} }
@@ -231,7 +224,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
*/ */
export const verifyUserToOrganization = async (req: Request, res: Response) => { export const verifyUserToOrganization = async (req: Request, res: Response) => {
let user; let user;
const { email, organizationId, code } = req.body;
const {
body: { organizationId, email, code }
} = await validateRequest(reqValidator.VerifyUserToOrgv1, req);
user = await User.findOne({ email }).select("+publicKey"); user = await User.findOne({ email }).select("+publicKey");
@@ -4,24 +4,33 @@ import {
Membership, Membership,
MembershipOrg, MembershipOrg,
Organization, Organization,
Workspace, Workspace
} from "../../models"; } from "../../models";
import { createOrganization as create } from "../../helpers/organization"; import { createOrganization as create } from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg"; import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { ACCEPTED, OWNER } from "../../variables"; import { ACCEPTED, OWNER } from "../../variables";
import { getLicenseServerUrl, getSiteURL } from "../../config"; import { getLicenseServerUrl, getSiteURL } from "../../config";
import { licenseServerKeyRequest } from "../../config/request"; import { licenseServerKeyRequest } from "../../config/request";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/organization";
import {
OrgPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../services/RoleService";
import { OrganizationNotFoundError } from "../../utils/errors";
import { ForbiddenError } from "@casl/ability";
export const getOrganizations = async (req: Request, res: Response) => { export const getOrganizations = async (req: Request, res: Response) => {
const organizations = ( const organizations = (
await MembershipOrg.find({ await MembershipOrg.find({
user: req.user._id, user: req.user._id,
status: ACCEPTED, status: ACCEPTED
}).populate("organization") }).populate("organization")
).map((m) => m.organization); ).map((m) => m.organization);
return res.status(200).send({ return res.status(200).send({
organizations, organizations
}); });
}; };
@@ -33,27 +42,25 @@ export const getOrganizations = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const createOrganization = async (req: Request, res: Response) => { export const createOrganization = async (req: Request, res: Response) => {
const { organizationName } = req.body; const {
body: { organizationName }
if (organizationName.length < 1) { } = await validateRequest(reqValidator.CreateOrgv1, req);
throw new Error("Organization names must be at least 1-character long");
}
// create organization and add user as member // create organization and add user as member
const organization = await create({ const organization = await create({
email: req.user.email, email: req.user.email,
name: organizationName, name: organizationName
}); });
await addMembershipsOrg({ await addMembershipsOrg({
userIds: [req.user._id.toString()], userIds: [req.user._id.toString()],
organizationId: organization._id.toString(), organizationId: organization._id.toString(),
roles: [OWNER], roles: [OWNER],
statuses: [ACCEPTED], statuses: [ACCEPTED]
}); });
return res.status(200).send({ return res.status(200).send({
organization, organization
}); });
}; };
@@ -64,9 +71,22 @@ export const createOrganization = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getOrganization = async (req: Request, res: Response) => { export const getOrganization = async (req: Request, res: Response) => {
const organization = req.organization const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgv1, req);
// ensure user has membership
await getUserOrgPermissions(req.user._id, organizationId);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
return res.status(200).send({ return res.status(200).send({
organization, organization
}); });
}; };
@@ -77,14 +97,22 @@ export const getOrganization = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getOrganizationMembers = async (req: Request, res: Response) => { export const getOrganizationMembers = async (req: Request, res: Response) => {
const { organizationId } = req.params; const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Member
);
const users = await MembershipOrg.find({ const users = await MembershipOrg.find({
organization: organizationId, organization: organizationId
}).populate("user", "+publicKey"); }).populate("user", "+publicKey");
return res.status(200).send({ return res.status(200).send({
users, users
}); });
}; };
@@ -94,17 +122,22 @@ export const getOrganizationMembers = async (req: Request, res: Response) => {
* @param res * @param res
* @returns * @returns
*/ */
export const getOrganizationWorkspaces = async ( export const getOrganizationWorkspaces = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { organizationId }
) => { } = await validateRequest(reqValidator.GetOrgWorkspacesv1, req);
const { organizationId } = req.params;
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Workspace
);
const workspacesSet = new Set( const workspacesSet = new Set(
( (
await Workspace.find( await Workspace.find(
{ {
organization: organizationId, organization: organizationId
}, },
"_id" "_id"
) )
@@ -113,14 +146,14 @@ export const getOrganizationWorkspaces = async (
const workspaces = ( const workspaces = (
await Membership.find({ await Membership.find({
user: req.user._id, user: req.user._id
}).populate("workspace") }).populate("workspace")
) )
.filter((m) => workspacesSet.has(m.workspace._id.toString())) .filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace); .map((m) => m.workspace);
return res.status(200).send({ return res.status(200).send({
workspaces, workspaces
}); });
}; };
@@ -131,24 +164,32 @@ export const getOrganizationWorkspaces = async (
* @returns * @returns
*/ */
export const changeOrganizationName = async (req: Request, res: Response) => { export const changeOrganizationName = async (req: Request, res: Response) => {
const { organizationId } = req.params; const {
const { name } = req.body; params: { organizationId },
body: { name }
} = await validateRequest(reqValidator.ChangeOrgNamev1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Settings
);
const organization = await Organization.findOneAndUpdate( const organization = await Organization.findOneAndUpdate(
{ {
_id: organizationId, _id: organizationId
}, },
{ {
name, name
}, },
{ {
new: true, new: true
} }
); );
return res.status(200).send({ return res.status(200).send({
message: "Successfully changed organization name", message: "Successfully changed organization name",
organization, organization
}); });
}; };
@@ -158,18 +199,23 @@ export const changeOrganizationName = async (req: Request, res: Response) => {
* @param res * @param res
* @returns * @returns
*/ */
export const getOrganizationIncidentContacts = async ( export const getOrganizationIncidentContacts = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { organizationId }
) => { } = await validateRequest(reqValidator.GetOrgIncidentContactv1, req);
const { organizationId } = req.params;
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.IncidentAccount
);
const incidentContactsOrg = await IncidentContactOrg.find({ const incidentContactsOrg = await IncidentContactOrg.find({
organization: organizationId, organization: organizationId
}); });
return res.status(200).send({ return res.status(200).send({
incidentContactsOrg, incidentContactsOrg
}); });
}; };
@@ -179,12 +225,17 @@ export const getOrganizationIncidentContacts = async (
* @param res * @param res
* @returns * @returns
*/ */
export const addOrganizationIncidentContact = async ( export const addOrganizationIncidentContact = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { organizationId },
) => { body: { email }
const { organizationId } = req.params; } = await validateRequest(reqValidator.CreateOrgIncideContact, req);
const { email } = req.body;
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.IncidentAccount
);
const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate( const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
{ email, organization: organizationId }, { email, organization: organizationId },
@@ -193,7 +244,7 @@ export const addOrganizationIncidentContact = async (
); );
return res.status(200).send({ return res.status(200).send({
incidentContactOrg, incidentContactOrg
}); });
}; };
@@ -203,21 +254,26 @@ export const addOrganizationIncidentContact = async (
* @param res * @param res
* @returns * @returns
*/ */
export const deleteOrganizationIncidentContact = async ( export const deleteOrganizationIncidentContact = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { organizationId },
) => { body: { email }
const { organizationId } = req.params; } = await validateRequest(reqValidator.DelOrgIncideContact, req);
const { email } = req.body;
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.IncidentAccount
);
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({ const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
email, email,
organization: organizationId, organization: organizationId
}); });
return res.status(200).send({ return res.status(200).send({
message: "Successfully deleted organization incident contact", message: "Successfully deleted organization incident contact",
incidentContactOrg, incidentContactOrg
}); });
}; };
@@ -228,19 +284,41 @@ export const deleteOrganizationIncidentContact = async (
* @param res * @param res
* @returns * @returns
*/ */
export const createOrganizationPortalSession = async ( export const createOrganizationPortalSession = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { organizationId }
) => { } = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`, const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { pmtMethods }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`
); );
if (pmtMethods.length < 1) { if (pmtMethods.length < 1) {
// case: organization has no payment method on file // case: organization has no payment method on file
// -> redirect to add payment method portal // -> redirect to add payment method portal
const { data: { url } } = await licenseServerKeyRequest.post( const {
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`, data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`,
{ {
success_url: (await getSiteURL()) + "/dashboard", success_url: (await getSiteURL()) + "/dashboard",
cancel_url: (await getSiteURL()) + "/dashboard" cancel_url: (await getSiteURL()) + "/dashboard"
@@ -250,8 +328,12 @@ export const createOrganizationPortalSession = async (
} else { } else {
// case: organization has payment method on file // case: organization has payment method on file
// -> redirect to billing portal // -> redirect to billing portal
const { data: { url } } = await licenseServerKeyRequest.post( const {
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/billing-portal`, data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/billing-portal`,
{ {
return_url: (await getSiteURL()) + "/dashboard" return_url: (await getSiteURL()) + "/dashboard"
} }
@@ -266,29 +348,36 @@ export const createOrganizationPortalSession = async (
* @param res * @param res
* @returns * @returns
*/ */
export const getOrganizationMembersAndTheirWorkspaces = async ( export const getOrganizationMembersAndTheirWorkspaces = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { organizationId }
) => { } = await validateRequest(reqValidator.GetOrgMembersv1, req);
const { organizationId } = req.params;
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Member
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Workspace
);
const workspacesSet = ( const workspacesSet = (
await Workspace.find( await Workspace.find(
{ {
organization: organizationId, organization: organizationId
}, },
"_id" "_id"
) )
).map((w) => w._id.toString()); ).map((w) => w._id.toString());
const memberships = ( const memberships = await Membership.find({
await Membership.find({ workspace: { $in: workspacesSet }
workspace: { $in: workspacesSet }, }).populate("workspace");
}).populate("workspace")
);
const userToWorkspaceIds: any = {}; const userToWorkspaceIds: any = {};
memberships.forEach(membership => { memberships.forEach((membership) => {
const user = membership.user.toString(); const user = membership.user.toString();
if (userToWorkspaceIds[user]) { if (userToWorkspaceIds[user]) {
userToWorkspaceIds[user].push(membership.workspace); userToWorkspaceIds[user].push(membership.workspace);
@@ -14,6 +14,8 @@ import {
getSiteURL getSiteURL
} from "../../config"; } from "../../config";
import { ActorType } from "../../ee/models"; import { ActorType } from "../../ee/models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
/** /**
* Password reset step 1: Send email verification link to email [email] * Password reset step 1: Send email verification link to email [email]
@@ -23,7 +25,9 @@ import { ActorType } from "../../ee/models";
* @returns * @returns
*/ */
export const emailPasswordReset = async (req: Request, res: Response) => { export const emailPasswordReset = async (req: Request, res: Response) => {
const email: string = req.body.email; const {
body: { email }
} = await validateRequest(reqValidator.EmailPasswordResetV1, req);
const user = await User.findOne({ email }).select("+publicKey"); const user = await User.findOne({ email }).select("+publicKey");
if (!user || !user?.publicKey) { if (!user || !user?.publicKey) {
@@ -62,7 +66,9 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const emailPasswordResetVerify = async (req: Request, res: Response) => { export const emailPasswordResetVerify = async (req: Request, res: Response) => {
const { email, code } = req.body; const {
body: { email, code }
} = await validateRequest(reqValidator.EmailPasswordResetVerifyV1, req);
const user = await User.findOne({ email }).select("+publicKey"); const user = await User.findOne({ email }).select("+publicKey");
if (!user || !user?.publicKey) { if (!user || !user?.publicKey) {
@@ -103,8 +109,10 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
*/ */
export const srp1 = async (req: Request, res: Response) => { export const srp1 = async (req: Request, res: Response) => {
// return salt, serverPublicKey as part of first step of SRP protocol // return salt, serverPublicKey as part of first step of SRP protocol
const {
body: { clientPublicKey }
} = await validateRequest(reqValidator.Srp1V1, req);
const { clientPublicKey } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email: req.user.email email: req.user.email
}).select("+salt +verifier"); }).select("+salt +verifier");
@@ -149,6 +157,7 @@ export const srp1 = async (req: Request, res: Response) => {
*/ */
export const changePassword = async (req: Request, res: Response) => { export const changePassword = async (req: Request, res: Response) => {
const { const {
body: {
clientProof, clientProof,
protectedKey, protectedKey,
protectedKeyIV, protectedKeyIV,
@@ -158,7 +167,8 @@ export const changePassword = async (req: Request, res: Response) => {
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier verifier
} = req.body; }
} = await validateRequest(reqValidator.ChangePasswordV1, req);
const user = await User.findOne({ const user = await User.findOne({
email: req.user.email email: req.user.email
@@ -208,10 +218,7 @@ export const changePassword = async (req: Request, res: Response) => {
} }
); );
if ( if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) {
req.authData.actor.type === ActorType.USER &&
req.authData.tokenVersionId
) {
await clearTokens(req.authData.tokenVersionId); await clearTokens(req.authData.tokenVersionId);
} }
@@ -246,8 +253,9 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
// create/change backup private key // create/change backup private key
// requires verifying [clientProof] as part of second step of SRP protocol // requires verifying [clientProof] as part of second step of SRP protocol
// as initiated in /srp1 // as initiated in /srp1
const {
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = req.body; body: { clientProof, encryptedPrivateKey, salt, verifier, iv, tag }
} = await validateRequest(reqValidator.CreateBackupPrivateKeyV1, req);
const user = await User.findOne({ const user = await User.findOne({
email: req.user.email email: req.user.email
}).select("+salt +verifier"); }).select("+salt +verifier");
@@ -325,15 +333,17 @@ export const getBackupPrivateKey = async (req: Request, res: Response) => {
export const resetPassword = async (req: Request, res: Response) => { export const resetPassword = async (req: Request, res: Response) => {
const { const {
body: {
encryptedPrivateKey,
protectedKeyTag,
protectedKey, protectedKey,
protectedKeyIV, protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt, salt,
verifier verifier,
} = req.body; encryptedPrivateKeyIV,
encryptedPrivateKeyTag
}
} = await validateRequest(reqValidator.ResetPasswordV1, req);
await User.findByIdAndUpdate( await User.findByIdAndUpdate(
req.user._id.toString(), req.user._id.toString(),
@@ -0,0 +1,247 @@
import { Request, Response } from "express";
import {
CreateRoleSchema,
DeleteRoleSchema,
GetRoleSchema,
GetUserPermission,
GetUserProjectPermission,
UpdateRoleSchema
} from "../../validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
adminProjectPermissions,
getUserProjectPermissions,
memberProjectPermissions,
viewerProjectPermission
} from "../../services/ProjectRoleService";
import {
OrgPermissionActions,
OrgPermissionSubjects,
adminPermissions,
getUserOrgPermissions,
memberPermissions
} from "../../services/RoleService";
import { BadRequestError } from "../../utils/errors";
import Role from "../../models/role";
import { validateRequest } from "../../helpers/validation";
import { packRules } from "@casl/ability/extra";
export const createRole = async (req: Request, res: Response) => {
const {
body: { workspaceId, name, description, slug, permissions, orgId }
} = await validateRequest(CreateRoleSchema, req);
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
if (isOrgRole) {
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
if (permission.cannot(OrgPermissionActions.Create, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "user doesn't have the permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
}
const existingRole = await Role.findOne({ organization: orgId, workspace: workspaceId, slug });
if (existingRole) {
throw BadRequestError({ message: "Role already exist" });
}
const role = new Role({
organization: orgId,
workspace: workspaceId,
isOrgRole,
name,
slug,
permissions,
description
});
await role.save();
res.status(200).json({
message: "Successfully created role",
data: {
role
}
});
};
export const updateRole = async (req: Request, res: Response) => {
const {
params: { id },
body: { name, description, slug, permissions, workspaceId, orgId }
} = await validateRequest(UpdateRoleSchema, req);
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
if (isOrgRole) {
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
if (permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the org permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." });
}
}
if (slug) {
const existingRole = await Role.findOne({
organization: orgId,
slug,
isOrgRole,
workspace: workspaceId
});
if (existingRole && existingRole.id !== id) {
throw BadRequestError({ message: "Role already exist" });
}
}
const role = await Role.findByIdAndUpdate(
id,
{ name, description, slug, permissions },
{ returnDocument: "after" }
);
if (!role) {
throw BadRequestError({ message: "Role not found" });
}
res.status(200).json({
message: "Successfully updated role",
data: {
role
}
});
};
export const deleteRole = async (req: Request, res: Response) => {
const {
params: { id }
} = await validateRequest(DeleteRoleSchema, req);
const role = await Role.findById(id);
if (!role) {
throw BadRequestError({ message: "Role not found" });
}
const isOrgRole = !role.workspace;
if (isOrgRole) {
const { permission } = await getUserOrgPermissions(req.user.id, role.organization.toString());
if (permission.cannot(OrgPermissionActions.Delete, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the org permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, role.workspace.toString());
if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." });
}
}
await Role.findByIdAndDelete(role.id);
res.status(200).json({
message: "Successfully deleted role",
data: {
role
}
});
};
export const getRoles = async (req: Request, res: Response) => {
const {
query: { workspaceId, orgId }
} = await validateRequest(GetRoleSchema, req);
const isOrgRole = !workspaceId;
if (isOrgRole) {
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
if (permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the org permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." });
}
}
const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
// as this is shared between org and workspace switch the rule set based on it
const roles = [
// owner is only in org level role
...(isOrgRole
? [
{
_id: "owner",
name: "Owner",
slug: "owner",
description: "Complete administration access over the organization.",
permissions: adminPermissions.rules
}
]
: []),
{
_id: "admin",
name: "Admin",
slug: "admin",
description: "Complete administration access over the organization",
permissions: isOrgRole ? adminPermissions.rules : adminProjectPermissions.rules
},
{
_id: "member",
name: "Member",
slug: "member",
description: "Non-administrative role in an organization",
permissions: isOrgRole ? memberPermissions.rules : memberProjectPermissions.rules
},
// viewer role only for project level
...(isOrgRole
? []
: [
{
_id: "viewer",
name: "Viewer",
slug: "viewer",
description: "Non-administrative role in an organization",
permissions: viewerProjectPermission.rules
}
]),
...customRoles
];
res.status(200).json({
message: "Successfully fetched role list",
data: {
roles
}
});
};
export const getUserPermissions = async (req: Request, res: Response) => {
const {
params: { orgId }
} = await validateRequest(GetUserPermission, req);
const { permission } = await getUserOrgPermissions(req.user._id, orgId);
res.status(200).json({
data: {
permissions: packRules(permission.rules)
}
});
};
export const getUserWorkspacePermissions = async (req: Request, res: Response) => {
const {
params: { workspaceId }
} = await validateRequest(GetUserProjectPermission, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
res.status(200).json({
data: {
permissions: packRules(permission.rules)
}
});
};
@@ -1,15 +1,28 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { isValidScope, validateMembership } from "../../helpers"; import { isValidScope } from "../../helpers";
import { Folder, SecretImport, ServiceTokenData } from "../../models"; import { Folder, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import { getFolderWithPathFromId } from "../../services/FolderService"; import { getFolderWithPathFromId } from "../../services/FolderService";
import { BadRequestError, ResourceNotFoundError,UnauthorizedRequestError } from "../../utils/errors"; import {
import { ADMIN, MEMBER } from "../../variables"; BadRequestError,
ResourceNotFoundError,
UnauthorizedRequestError
} from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/secretImports";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability";
export const createSecretImport = async (req: Request, res: Response) => { export const createSecretImport = async (req: Request, res: Response) => {
const { workspaceId, environment, folderId, secretImport } = req.body; const {
body: { workspaceId, environment, folderId, secretImport }
} = await validateRequest(reqValidator.CreateSecretImportV1, req);
const folders = await Folder.findOne({ const folders = await Folder.findOne({
workspace: workspaceId, workspace: workspaceId,
@@ -27,12 +40,19 @@ export const createSecretImport = async (req: Request, res: Response) => {
const { folderPath } = getFolderWithPathFromId(folders.nodes, folderId); const { folderPath } = getFolderWithPathFromId(folders.nodes, folderId);
secretPath = folderPath; secretPath = folderPath;
} }
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
// root check // root check
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath); const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
if (!isValidScopeAccess) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
);
} }
const importSecDoc = await SecretImport.findOne({ const importSecDoc = await SecretImport.findOne({
@@ -41,7 +61,9 @@ export const createSecretImport = async (req: Request, res: Response) => {
folderId folderId
}); });
const importToSecretPath = folders?getFolderWithPathFromId(folders.nodes, folderId).folderPath:"/"; const importToSecretPath = folders
? getFolderWithPathFromId(folders.nodes, folderId).folderPath
: "/";
if (!importSecDoc) { if (!importSecDoc) {
const doc = new SecretImport({ const doc = new SecretImport({
@@ -108,20 +130,16 @@ export const createSecretImport = async (req: Request, res: Response) => {
// to keep the ordering, you must pass all the imports in here not the only updated one // to keep the ordering, you must pass all the imports in here not the only updated one
// this is because the order decide which import gets overriden // this is because the order decide which import gets overriden
export const updateSecretImport = async (req: Request, res: Response) => { export const updateSecretImport = async (req: Request, res: Response) => {
const { id } = req.params; const {
const { secretImports } = req.body; body: { secretImports },
params: { id }
} = await validateRequest(reqValidator.UpdateSecretImportV1, req);
const importSecDoc = await SecretImport.findById(id); const importSecDoc = await SecretImport.findById(id);
if (!importSecDoc) { if (!importSecDoc) {
throw BadRequestError({ message: "Import not found" }); throw BadRequestError({ message: "Import not found" });
} }
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
await validateMembership({
userId: req.user._id.toString(),
workspaceId: importSecDoc.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
} else {
// check for service token validity // check for service token validity
const folders = await Folder.findOne({ const folders = await Folder.findOne({
workspace: importSecDoc.workspace, workspace: importSecDoc.workspace,
@@ -134,6 +152,8 @@ export const updateSecretImport = async (req: Request, res: Response) => {
secretPath = folderPath; secretPath = folderPath;
} }
if (req.authData.authPayload instanceof ServiceTokenData) {
// token permission check
const isValidScopeAccess = isValidScope( const isValidScopeAccess = isValidScope(
req.authData.authPayload, req.authData.authPayload,
importSecDoc.environment, importSecDoc.environment,
@@ -142,6 +162,19 @@ export const updateSecretImport = async (req: Request, res: Response) => {
if (!isValidScopeAccess) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else {
// non token entry check
const { permission } = await getUserProjectPermissions(
req.user._id,
importSecDoc.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.SecretImports, {
environment: importSecDoc.environment,
secretPath
})
);
} }
const orderBefore = importSecDoc.imports; const orderBefore = importSecDoc.imports;
@@ -149,24 +182,13 @@ export const updateSecretImport = async (req: Request, res: Response) => {
await importSecDoc.save(); await importSecDoc.save();
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment,
}).lean();
if (!folders) throw ResourceNotFoundError({
message: "Failed to find folder"
});
const importToSecretPath = folders?getFolderWithPathFromId(folders.nodes, importSecDoc.folderId).folderPath:"/";
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
req.authData, req.authData,
{ {
type: EventType.UPDATE_SECRET_IMPORT, type: EventType.UPDATE_SECRET_IMPORT,
metadata: { metadata: {
importToEnvironment: importSecDoc.environment, importToEnvironment: importSecDoc.environment,
importToSecretPath, importToSecretPath: secretPath,
secretImportId: importSecDoc._id.toString(), secretImportId: importSecDoc._id.toString(),
folderId: importSecDoc.folderId.toString(), folderId: importSecDoc.folderId.toString(),
orderBefore, orderBefore,
@@ -181,20 +203,16 @@ export const updateSecretImport = async (req: Request, res: Response) => {
}; };
export const deleteSecretImport = async (req: Request, res: Response) => { export const deleteSecretImport = async (req: Request, res: Response) => {
const { id } = req.params; const {
const { secretImportEnv, secretImportPath } = req.body; params: { id },
body: { secretImportEnv, secretImportPath }
} = await validateRequest(reqValidator.DeleteSecretImportV1, req);
const importSecDoc = await SecretImport.findById(id); const importSecDoc = await SecretImport.findById(id);
if (!importSecDoc) { if (!importSecDoc) {
throw BadRequestError({ message: "Import not found" }); throw BadRequestError({ message: "Import not found" });
} }
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
await validateMembership({
userId: req.user._id.toString(),
workspaceId: importSecDoc.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
} else {
// check for service token validity // check for service token validity
const folders = await Folder.findOne({ const folders = await Folder.findOne({
workspace: importSecDoc.workspace, workspace: importSecDoc.workspace,
@@ -207,6 +225,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
secretPath = folderPath; secretPath = folderPath;
} }
if (req.authData.authPayload instanceof ServiceTokenData) {
const isValidScopeAccess = isValidScope( const isValidScopeAccess = isValidScope(
req.authData.authPayload, req.authData.authPayload,
importSecDoc.environment, importSecDoc.environment,
@@ -215,6 +234,18 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
if (!isValidScopeAccess) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else {
const { permission } = await getUserProjectPermissions(
req.user._id,
importSecDoc.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.SecretImports, {
environment: importSecDoc.environment,
secretPath
})
);
} }
importSecDoc.imports = importSecDoc.imports.filter( importSecDoc.imports = importSecDoc.imports.filter(
({ environment, secretPath }) => ({ environment, secretPath }) =>
@@ -222,17 +253,6 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
); );
await importSecDoc.save(); await importSecDoc.save();
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment,
}).lean();
if (!folders) throw ResourceNotFoundError({
message: "Failed to find folder"
});
const importToSecretPath = folders?getFolderWithPathFromId(folders.nodes, importSecDoc.folderId).folderPath:"/";
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
req.authData, req.authData,
{ {
@@ -243,7 +263,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
importFromEnvironment: secretImportEnv, importFromEnvironment: secretImportEnv,
importFromSecretPath: secretImportPath, importFromSecretPath: secretImportPath,
importToEnvironment: importSecDoc.environment, importToEnvironment: importSecDoc.environment,
importToSecretPath importToSecretPath: secretPath
} }
}, },
{ {
@@ -255,7 +275,9 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
}; };
export const getSecretImports = async (req: Request, res: Response) => { export const getSecretImports = async (req: Request, res: Response) => {
const { workspaceId, environment, folderId } = req.query; const {
query: { workspaceId, environment, folderId }
} = await validateRequest(reqValidator.GetSecretImportsV1, req);
const importSecDoc = await SecretImport.findOne({ const importSecDoc = await SecretImport.findOne({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
@@ -266,7 +288,6 @@ export const getSecretImports = async (req: Request, res: Response) => {
return res.status(200).json({ secretImport: {} }); return res.status(200).json({ secretImport: {} });
} }
if (req.authData.authPayload instanceof ServiceTokenData) {
// check for service token validity // check for service token validity
const folders = await Folder.findOne({ const folders = await Folder.findOne({
workspace: importSecDoc.workspace, workspace: importSecDoc.workspace,
@@ -279,6 +300,7 @@ export const getSecretImports = async (req: Request, res: Response) => {
secretPath = folderPath; secretPath = folderPath;
} }
if (req.authData.authPayload instanceof ServiceTokenData) {
const isValidScopeAccess = isValidScope( const isValidScopeAccess = isValidScope(
req.authData.authPayload, req.authData.authPayload,
importSecDoc.environment, importSecDoc.environment,
@@ -287,17 +309,28 @@ export const getSecretImports = async (req: Request, res: Response) => {
if (!isValidScopeAccess) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else {
const { permission } = await getUserProjectPermissions(
req.user._id,
importSecDoc.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, {
environment: importSecDoc.environment,
secretPath
})
);
} }
return res.status(200).json({ secretImport: importSecDoc }); return res.status(200).json({ secretImport: importSecDoc });
}; };
export const getAllSecretsFromImport = async (req: Request, res: Response) => { export const getAllSecretsFromImport = async (req: Request, res: Response) => {
const { workspaceId, environment, folderId } = req.query as { const {
workspaceId: string; query: { workspaceId, environment, folderId }
environment: string; } = await validateRequest(reqValidator.GetAllSecretsFromImportV1, req);
folderId: string;
};
const importSecDoc = await SecretImport.findOne({ const importSecDoc = await SecretImport.findOne({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
@@ -308,8 +341,6 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
return res.status(200).json({ secrets: [] }); return res.status(200).json({ secrets: [] });
} }
if (req.authData.authPayload instanceof ServiceTokenData) {
// check for service token validity
const folders = await Folder.findOne({ const folders = await Folder.findOne({
workspace: importSecDoc.workspace, workspace: importSecDoc.workspace,
environment: importSecDoc.environment environment: importSecDoc.environment
@@ -321,6 +352,8 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
secretPath = folderPath; secretPath = folderPath;
} }
if (req.authData.authPayload instanceof ServiceTokenData) {
// check for service token validity
const isValidScopeAccess = isValidScope( const isValidScopeAccess = isValidScope(
req.authData.authPayload, req.authData.authPayload,
importSecDoc.environment, importSecDoc.environment,
@@ -329,6 +362,25 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
if (!isValidScopeAccess) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else {
const { permission } = await getUserProjectPermissions(
req.user._id,
importSecDoc.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, {
environment: importSecDoc.environment,
secretPath
})
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment: importSecDoc.environment,
secretPath
})
);
} }
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
@@ -2,20 +2,45 @@ import { Request, Response } from "express";
import GitAppInstallationSession from "../../ee/models/gitAppInstallationSession"; import GitAppInstallationSession from "../../ee/models/gitAppInstallationSession";
import crypto from "crypto"; import crypto from "crypto";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { UnauthorizedRequestError } from "../../utils/errors"; import { OrganizationNotFoundError, UnauthorizedRequestError } from "../../utils/errors";
import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation"; import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation";
import { MembershipOrg } from "../../models";
import { scanGithubFullRepoForSecretLeaks } from "../../queues/secret-scanning/githubScanFullRepository" import { scanGithubFullRepoForSecretLeaks } from "../../queues/secret-scanning/githubScanFullRepository"
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config"; import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../ee/models/gitRisks"; import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../ee/models/gitRisks";
import { ProbotOctokit } from "probot"; import { ProbotOctokit } from "probot";
import { Organization } from "../../models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/secretScanning";
import {
OrgPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
export const createInstallationSession = async (req: Request, res: Response) => { export const createInstallationSession = async (req: Request, res: Response) => {
const sessionId = crypto.randomBytes(16).toString("hex"); const sessionId = crypto.randomBytes(16).toString("hex");
const {
params: { organizationId }
} = await validateRequest(reqValidator.CreateInstalLSessionv1, req);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.SecretScanning
);
await GitAppInstallationSession.findByIdAndUpdate( await GitAppInstallationSession.findByIdAndUpdate(
req.organization, organization,
{ {
organization: new Types.ObjectId(req.organization), organization: organization.id,
sessionId: sessionId, sessionId: sessionId,
user: new Types.ObjectId(req.user._id) user: new Types.ObjectId(req.user._id)
}, },
@@ -24,31 +49,43 @@ export const createInstallationSession = async (req: Request, res: Response) =>
res.send({ res.send({
sessionId: sessionId sessionId: sessionId
}) });
} };
export const linkInstallationToOrganization = async (req: Request, res: Response) => { export const linkInstallationToOrganization = async (req: Request, res: Response) => {
const { installationId, sessionId } = req.body const {
body: { sessionId, installationId }
} = await validateRequest(reqValidator.LinkInstallationToOrgv1, req);
const installationSession = await GitAppInstallationSession.findOneAndDelete({ sessionId: sessionId }) const installationSession = await GitAppInstallationSession.findOneAndDelete({
sessionId: sessionId
});
if (!installationSession) { if (!installationSession) {
throw UnauthorizedRequestError() throw UnauthorizedRequestError();
} }
const userMembership = await MembershipOrg.find({ user: req.user._id, organization: installationSession.organization }) const { permission } = await getUserOrgPermissions(
if (!userMembership) { req.user._id,
throw UnauthorizedRequestError() installationSession.organization.toString()
} );
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.SecretScanning
);
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate({ const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate(
organizationId: installationSession.organization, {
}, { organizationId: installationSession.organization
},
{
installationId: installationId, installationId: installationId,
organizationId: installationSession.organization, organizationId: installationSession.organization,
user: installationSession.user user: installationSession.user
}, { },
{
upsert: true upsert: true
}).lean() }
).lean();
const octokit = new ProbotOctokit({ const octokit = new ProbotOctokit({
auth: { auth: {
@@ -66,41 +103,68 @@ export const linkInstallationToOrganization = async (req: Request, res: Response
} }
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => { export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
const { organizationId } = req.params const { organizationId } = req.params;
try { try {
const appInstallation = await GitAppOrganizationInstallation.findOne({ organizationId: organizationId }).lean() const appInstallation = await GitAppOrganizationInstallation.findOne({
organizationId: organizationId
}).lean();
if (!appInstallation) { if (!appInstallation) {
res.json({ res.json({
appInstallationComplete: false appInstallationComplete: false
}) });
} }
res.json({ res.json({
appInstallationComplete: true appInstallationComplete: true
}) });
} catch { } catch {
res.json({ res.json({
appInstallationComplete: false appInstallationComplete: false
}) });
}
} }
};
export const getRisksForOrganization = async (req: Request, res: Response) => { export const getRisksForOrganization = async (req: Request, res: Response) => {
const { organizationId } = req.params const {
const risks = await GitRisks.find({ organization: organizationId }).sort({ createdAt: -1 }).lean() params: { organizationId }
} = await validateRequest(reqValidator.GetOrgRisksv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.SecretScanning
);
const risks = await GitRisks.find({ organization: organizationId })
.sort({ createdAt: -1 })
.lean();
res.json({ res.json({
risks: risks risks: risks
}) });
} };
export const updateRisksStatus = async (req: Request, res: Response) => { export const updateRisksStatus = async (req: Request, res: Response) => {
const { riskId } = req.params const {
const { status } = req.body params: { organizationId, riskId },
const isRiskResolved = status == STATUS_RESOLVED_FALSE_POSITIVE || status == STATUS_RESOLVED_REVOKED || status == STATUS_RESOLVED_NOT_REVOKED ? true : false body: { status }
} = await validateRequest(reqValidator.UpdateRiskStatusv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.SecretScanning
);
const isRiskResolved =
status == STATUS_RESOLVED_FALSE_POSITIVE ||
status == STATUS_RESOLVED_REVOKED ||
status == STATUS_RESOLVED_NOT_REVOKED
? true
: false;
const risk = await GitRisks.findByIdAndUpdate(riskId, { const risk = await GitRisks.findByIdAndUpdate(riskId, {
status: status, status: status,
isResolved: isRiskResolved isResolved: isRiskResolved
}).lean() }).lean();
res.json(risk) res.json(risk);
} };
@@ -1,10 +1,12 @@
import { ForbiddenError, subject } from "@casl/ability";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { EventType, FolderVersion } from "../../ee/models"; import { EventType, FolderVersion } from "../../ee/models";
import { EEAuditLogService, EESecretService } from "../../ee/services"; import { EEAuditLogService, EESecretService } from "../../ee/services";
import { validateMembership } from "../../helpers/membership";
import { isValidScope } from "../../helpers/secrets"; import { isValidScope } from "../../helpers/secrets";
import { Folder, Secret, ServiceTokenData } from "../../models"; import { validateRequest } from "../../helpers/validation";
import { Secret, ServiceTokenData } from "../../models";
import {Folder} from "../../models/folder";
import { import {
appendFolder, appendFolder,
deleteFolderById, deleteFolderById,
@@ -15,12 +17,20 @@ import {
getParentFromFolderId, getParentFromFolderId,
validateFolderName validateFolderName
} from "../../services/FolderService"; } from "../../services/FolderService";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { ADMIN, MEMBER } from "../../variables"; import * as reqValidator from "../../validation/folders";
// verify workspace id/environment // verify workspace id/environment
export const createFolder = async (req: Request, res: Response) => { export const createFolder = async (req: Request, res: Response) => {
const { workspaceId, environment, folderName, parentFolderId } = req.body; const {
body: { workspaceId, environment, folderName, parentFolderId }
} = await validateRequest(reqValidator.CreateFolderV1, req);
if (!validateFolderName(folderName)) { if (!validateFolderName(folderName)) {
throw BadRequestError({ throw BadRequestError({
message: "Folder name cannot contain spaces. Only underscore and dashes" message: "Folder name cannot contain spaces. Only underscore and dashes"
@@ -32,6 +42,18 @@ export const createFolder = async (req: Request, res: Response) => {
environment environment
}).lean(); }).lean();
if (req.user) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const secretPath =
folders && parentFolderId
? getFolderWithPathFromId(folders.nodes, parentFolderId).folderPath
: "/";
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Folders, { environment, secretPath })
);
}
// space has no folders initialized // space has no folders initialized
if (!folders) { if (!folders) {
@@ -62,7 +84,7 @@ export const createFolder = async (req: Request, res: Response) => {
}); });
await folderVersion.save(); await folderVersion.save();
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment environment
}); });
@@ -116,7 +138,7 @@ export const createFolder = async (req: Request, res: Response) => {
await folderVersion.save(); await folderVersion.save();
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId: parentFolderId folderId: parentFolderId
}); });
@@ -143,8 +165,11 @@ export const createFolder = async (req: Request, res: Response) => {
}; };
export const updateFolderById = async (req: Request, res: Response) => { export const updateFolderById = async (req: Request, res: Response) => {
const { folderId } = req.params; const {
const { name, workspaceId, environment } = req.body; body: { workspaceId, environment, name },
params: { folderId }
} = await validateRequest(reqValidator.UpdateFolderV1, req);
if (!validateFolderName(name)) { if (!validateFolderName(name)) {
throw BadRequestError({ throw BadRequestError({
message: "Folder name cannot contain spaces. Only underscore and dashes" message: "Folder name cannot contain spaces. Only underscore and dashes"
@@ -156,21 +181,21 @@ export const updateFolderById = async (req: Request, res: Response) => {
throw BadRequestError({ message: "The folder doesn't exist" }); throw BadRequestError({ message: "The folder doesn't exist" });
} }
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId,
acceptedRoles: [ADMIN, MEMBER]
});
}
const parentFolder = getParentFromFolderId(folders.nodes, folderId); const parentFolder = getParentFromFolderId(folders.nodes, folderId);
if (!parentFolder) { if (!parentFolder) {
throw BadRequestError({ message: "The folder doesn't exist" }); throw BadRequestError({ message: "The folder doesn't exist" });
} }
const folder = parentFolder.children.find(({ id }) => id === folderId);
if (req.user) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const secretPath = getFolderWithPathFromId(folders.nodes, parentFolder.id).folderPath;
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Folders, { environment, secretPath })
);
}
const folder = parentFolder.children.find(({ id }) => id === folderId);
if (!folder) { if (!folder) {
throw BadRequestError({ message: "The folder doesn't exist" }); throw BadRequestError({ message: "The folder doesn't exist" });
} }
@@ -197,7 +222,7 @@ export const updateFolderById = async (req: Request, res: Response) => {
await folderVersion.save(); await folderVersion.save();
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId: parentFolder.id folderId: parentFolder.id
}); });
@@ -228,37 +253,35 @@ export const updateFolderById = async (req: Request, res: Response) => {
}; };
export const deleteFolder = async (req: Request, res: Response) => { export const deleteFolder = async (req: Request, res: Response) => {
const { folderId } = req.params; const {
const { workspaceId, environment } = req.body; params: { folderId },
body: { environment, workspaceId }
} = await validateRequest(reqValidator.DeleteFolderV1, req);
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (!folders) { if (!folders) {
throw BadRequestError({ message: "The folder doesn't exist" }); throw BadRequestError({ message: "The folder doesn't exist" });
} }
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId,
acceptedRoles: [ADMIN, MEMBER]
});
}
const {folderPath} = getFolderWithPathFromId(folders.nodes, folderId);
const delOp = deleteFolderById(folders.nodes, folderId); const delOp = deleteFolderById(folders.nodes, folderId);
if (!delOp) { if (!delOp) {
throw BadRequestError({ message: "The folder doesn't exist" }); throw BadRequestError({ message: "The folder doesn't exist" });
} }
const { deletedNode: delFolder, parent: parentFolder } = delOp; const { deletedNode: delFolder, parent: parentFolder } = delOp;
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath); const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
if (!isValidScopeAccess) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else {
// check that user is a member of the workspace
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Folders, { environment, secretPath })
);
} }
parentFolder.version += 1; parentFolder.version += 1;
@@ -280,7 +303,7 @@ export const deleteFolder = async (req: Request, res: Response) => {
} }
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId: parentFolder.id folderId: parentFolder.id
}); });
@@ -293,7 +316,7 @@ export const deleteFolder = async (req: Request, res: Response) => {
environment, environment,
folderId, folderId,
folderName: delFolder.name, folderName: delFolder.name,
folderPath folderPath: secretPath
} }
}, },
{ {
@@ -306,28 +329,30 @@ export const deleteFolder = async (req: Request, res: Response) => {
// TODO: validate workspace // TODO: validate workspace
export const getFolders = async (req: Request, res: Response) => { export const getFolders = async (req: Request, res: Response) => {
const { workspaceId, environment, parentFolderId, parentFolderPath } = req.query as { const {
workspaceId: string; query: { workspaceId, environment, parentFolderId, parentFolderPath }
environment: string; } = await validateRequest(reqValidator.GetFoldersV1, req);
parentFolderId?: string;
parentFolderPath?: string;
};
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (req.user) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const secretPath =
folders && parentFolderId
? getFolderWithPathFromId(folders.nodes, parentFolderId).folderPath
: parentFolderPath || "/";
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Folders, { environment, secretPath })
);
}
if (!folders) { if (!folders) {
res.send({ folders: [], dir: [] }); res.send({ folders: [], dir: [] });
return; return;
} }
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId,
acceptedRoles: [ADMIN, MEMBER]
});
}
// if instead of parentFolderId given a path like /folder1/folder2 // if instead of parentFolderId given a path like /folder1/folder2
if (parentFolderPath) { if (parentFolderPath) {
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
@@ -10,6 +10,8 @@ import {
getSmtpConfigured getSmtpConfigured
} from "../../config"; } from "../../config";
import { validateUserEmail } from "../../validation"; import { validateUserEmail } from "../../validation";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
/** /**
* Signup step 1: Initialize account for user under email [email] and send a verification code * Signup step 1: Initialize account for user under email [email] and send a verification code
@@ -19,7 +21,9 @@ import { validateUserEmail } from "../../validation";
* @returns * @returns
*/ */
export const beginEmailSignup = async (req: Request, res: Response) => { export const beginEmailSignup = async (req: Request, res: Response) => {
const email: string = req.body.email; const {
body: { email }
} = await validateRequest(reqValidator.BeginEmailSignUpV1, req);
// validate that email is not disposable // validate that email is not disposable
validateUserEmail(email); validateUserEmail(email);
@@ -50,7 +54,9 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
*/ */
export const verifyEmailSignup = async (req: Request, res: Response) => { export const verifyEmailSignup = async (req: Request, res: Response) => {
let user; let user;
const { email, code } = req.body; const {
body: { email, code }
} = await validateRequest(reqValidator.VerifyEmailSignUpV1, req);
// initialize user account // initialize user account
user = await User.findOne({ email }).select("+publicKey"); user = await User.findOne({ email }).select("+publicKey");
@@ -1,5 +1,7 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { validateRequest } from "../../helpers/validation";
import { UserAction } from "../../models"; import { UserAction } from "../../models";
import * as reqValidator from "../../validation/action";
/** /**
* Add user action [action] * Add user action [action]
@@ -9,24 +11,25 @@ import { UserAction } from "../../models";
*/ */
export const addUserAction = async (req: Request, res: Response) => { export const addUserAction = async (req: Request, res: Response) => {
// add/record new action [action] for user with id [req.user._id] // add/record new action [action] for user with id [req.user._id]
const {
const { action } = req.body; body: { action }
} = await validateRequest(reqValidator.AddUserActionV1, req);
const userAction = await UserAction.findOneAndUpdate( const userAction = await UserAction.findOneAndUpdate(
{ {
user: req.user._id, user: req.user._id,
action, action
}, },
{ user: req.user._id, action }, { user: req.user._id, action },
{ {
new: true, new: true,
upsert: true, upsert: true
} }
); );
return res.status(200).send({ return res.status(200).send({
message: "Successfully recorded user action", message: "Successfully recorded user action",
userAction, userAction
}); });
}; };
@@ -38,14 +41,16 @@ export const addUserAction = async (req: Request, res: Response) => {
*/ */
export const getUserAction = async (req: Request, res: Response) => { export const getUserAction = async (req: Request, res: Response) => {
// get user action [action] for user with id [req.user._id] // get user action [action] for user with id [req.user._id]
const action: string = req.query.action as string; const {
query: { action }
} = await validateRequest(reqValidator.GetUserActionV1, req);
const userAction = await UserAction.findOne({ const userAction = await UserAction.findOne({
user: req.user._id, user: req.user._id,
action, action
}); });
return res.status(200).send({ return res.status(200).send({
userAction, userAction
}); });
}; };
+65 -25
View File
@@ -1,16 +1,32 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config"; import { client, getRootEncryptionKey } from "../../config";
import { validateMembership } from "../../helpers";
import { Webhook } from "../../models"; import { Webhook } from "../../models";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService"; import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { ADMIN, ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, MEMBER } from "../../variables"; import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64 } from "../../variables";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/webhooks";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
export const createWebhook = async (req: Request, res: Response) => { export const createWebhook = async (req: Request, res: Response) => {
const { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath } = req.body; const {
body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.CreateWebhookV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Webhooks
);
const webhook = new Webhook({ const webhook = new Webhook({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
@@ -43,7 +59,7 @@ export const createWebhook = async (req: Request, res: Response) => {
} }
}, },
{ {
workspaceId workspaceId: new Types.ObjectId(workspaceId)
} }
); );
@@ -54,19 +70,24 @@ export const createWebhook = async (req: Request, res: Response) => {
}; };
export const updateWebhook = async (req: Request, res: Response) => { export const updateWebhook = async (req: Request, res: Response) => {
const { webhookId } = req.params; const {
const { isDisabled } = req.body; body: { isDisabled },
params: { webhookId }
} = await validateRequest(reqValidator.UpdateWebhookV1, req);
const webhook = await Webhook.findById(webhookId); const webhook = await Webhook.findById(webhookId);
if (!webhook) { if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" }); throw BadRequestError({ message: "Webhook not found!!" });
} }
// check that user is a member of the workspace const { permission } = await getUserProjectPermissions(
await validateMembership({ req.user._id,
userId: req.user._id.toString(), webhook.workspace.toString()
workspaceId: webhook.workspace, );
acceptedRoles: [ADMIN, MEMBER] ForbiddenError.from(permission).throwUnlessCan(
}); ProjectPermissionActions.Edit,
ProjectPermissionSub.Webhooks
);
if (typeof isDisabled !== undefined) { if (typeof isDisabled !== undefined) {
webhook.isDisabled = isDisabled; webhook.isDisabled = isDisabled;
@@ -97,18 +118,23 @@ export const updateWebhook = async (req: Request, res: Response) => {
}; };
export const deleteWebhook = async (req: Request, res: Response) => { export const deleteWebhook = async (req: Request, res: Response) => {
const { webhookId } = req.params; const {
params: { webhookId }
} = await validateRequest(reqValidator.DeleteWebhookV1, req);
let webhook = await Webhook.findById(webhookId); let webhook = await Webhook.findById(webhookId);
if (!webhook) { if (!webhook) {
throw ResourceNotFoundError({ message: "Webhook not found!!" }); throw ResourceNotFoundError({ message: "Webhook not found!!" });
} }
await validateMembership({ const { permission } = await getUserProjectPermissions(
userId: req.user._id.toString(), req.user._id,
workspaceId: webhook.workspace, webhook.workspace.toString()
acceptedRoles: [ADMIN, MEMBER] );
}); ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Webhooks
);
webhook = await Webhook.findByIdAndDelete(webhookId); webhook = await Webhook.findByIdAndDelete(webhookId);
@@ -139,17 +165,23 @@ export const deleteWebhook = async (req: Request, res: Response) => {
}; };
export const testWebhook = async (req: Request, res: Response) => { export const testWebhook = async (req: Request, res: Response) => {
const { webhookId } = req.params; const {
params: { webhookId }
} = await validateRequest(reqValidator.TestWebhookV1, req);
const webhook = await Webhook.findById(webhookId); const webhook = await Webhook.findById(webhookId);
if (!webhook) { if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" }); throw BadRequestError({ message: "Webhook not found!!" });
} }
await validateMembership({ const { permission } = await getUserProjectPermissions(
userId: req.user._id.toString(), req.user._id,
workspaceId: webhook.workspace, webhook.workspace.toString()
acceptedRoles: [ADMIN, MEMBER] );
}); ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks
);
try { try {
await triggerWebhookRequest( await triggerWebhookRequest(
@@ -182,7 +214,15 @@ export const testWebhook = async (req: Request, res: Response) => {
}; };
export const listWebhooks = async (req: Request, res: Response) => { export const listWebhooks = async (req: Request, res: Response) => {
const { environment, workspaceId, secretPath } = req.query; const {
query: { environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.ListWebhooksV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks
);
const optionalFilters: Record<string, string> = {}; const optionalFilters: Record<string, string> = {};
if (environment) optionalFilters.environment = environment as string; if (environment) optionalFilters.environment = environment as string;
+125 -56
View File
@@ -5,17 +5,28 @@ import {
Integration, Integration,
IntegrationAuth, IntegrationAuth,
Membership, Membership,
MembershipOrg, Organization,
ServiceToken, ServiceToken,
Workspace, Workspace
} from "../../models"; } from "../../models";
import { import { createWorkspace as create, deleteWorkspace as deleteWork } from "../../helpers/workspace";
createWorkspace as create,
deleteWorkspace as deleteWork,
} from "../../helpers/workspace";
import { EELicenseService } from "../../ee/services"; import { EELicenseService } from "../../ee/services";
import { addMemberships } from "../../helpers/membership"; import { addMemberships } from "../../helpers/membership";
import { ADMIN } from "../../variables"; import { ADMIN } from "../../variables";
import { OrganizationNotFoundError } from "../../utils/errors";
import {
OrgPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
/** /**
* Return public keys of members of workspace with id [workspaceId] * Return public keys of members of workspace with id [workspaceId]
@@ -24,21 +35,29 @@ import { ADMIN } from "../../variables";
* @returns * @returns
*/ */
export const getWorkspacePublicKeys = async (req: Request, res: Response) => { export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
);
const publicKeys = ( const publicKeys = (
await Membership.find({ await Membership.find({
workspace: workspaceId, workspace: workspaceId
}).populate<{ user: IUser }>("user", "publicKey") }).populate<{ user: IUser }>("user", "publicKey")
).map((member) => { ).map((member) => {
return { return {
publicKey: member.user.publicKey, publicKey: member.user.publicKey,
userId: member.user._id, userId: member.user._id
}; };
}); });
return res.status(200).send({ return res.status(200).send({
publicKeys, publicKeys
}); });
}; };
@@ -49,14 +68,22 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getWorkspaceMemberships = async (req: Request, res: Response) => { export const getWorkspaceMemberships = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
);
const users = await Membership.find({ const users = await Membership.find({
workspace: workspaceId, workspace: workspaceId
}).populate("user", "+publicKey"); }).populate("user", "+publicKey");
return res.status(200).send({ return res.status(200).send({
users, users
}); });
}; };
@@ -69,12 +96,12 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
export const getWorkspaces = async (req: Request, res: Response) => { export const getWorkspaces = async (req: Request, res: Response) => {
const workspaces = ( const workspaces = (
await Membership.find({ await Membership.find({
user: req.user._id, user: req.user._id
}).populate("workspace") }).populate("workspace")
).map((m) => m.workspace); ).map((m) => m.workspace);
return res.status(200).send({ return res.status(200).send({
workspaces, workspaces
}); });
}; };
@@ -85,14 +112,16 @@ export const getWorkspaces = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getWorkspace = async (req: Request, res: Response) => { export const getWorkspace = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceV1, req);
const workspace = await Workspace.findOne({ const workspace = await Workspace.findOne({
_id: workspaceId, _id: workspaceId
}); });
return res.status(200).send({ return res.status(200).send({
workspace, workspace
}); });
}; };
@@ -104,18 +133,23 @@ export const getWorkspace = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const createWorkspace = async (req: Request, res: Response) => { export const createWorkspace = async (req: Request, res: Response) => {
const { workspaceName, organizationId } = req.body; const {
body: { organizationId, workspaceName }
} = await validateRequest(reqValidator.CreateWorkspaceV1, req);
// validate organization membership const organization = await Organization.findById(organizationId);
const membershipOrg = await MembershipOrg.findOne({ if (!organization) {
user: req.user._id, throw OrganizationNotFoundError({
organization: new Types.ObjectId(organizationId), message: "Failed to find organization"
}); });
if (!membershipOrg) {
throw new Error("Failed to validate organization membership");
} }
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Workspace
);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (plan.workspaceLimit !== null) { if (plan.workspaceLimit !== null) {
@@ -123,7 +157,8 @@ export const createWorkspace = async (req: Request, res: Response) => {
if (plan.workspacesUsed >= plan.workspaceLimit) { if (plan.workspacesUsed >= plan.workspaceLimit) {
// case: number of workspaces used exceeds the number of workspaces allowed // case: number of workspaces used exceeds the number of workspaces allowed
return res.status(400).send({ return res.status(400).send({
message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces.", message:
"Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces."
}); });
} }
} }
@@ -135,17 +170,17 @@ export const createWorkspace = async (req: Request, res: Response) => {
// create workspace and add user as member // create workspace and add user as member
const workspace = await create({ const workspace = await create({
name: workspaceName, name: workspaceName,
organizationId: new Types.ObjectId(organizationId), organizationId: new Types.ObjectId(organizationId)
}); });
await addMemberships({ await addMemberships({
userIds: [req.user._id], userIds: [req.user._id],
workspaceId: workspace._id.toString(), workspaceId: workspace._id.toString(),
roles: [ADMIN], roles: [ADMIN]
}); });
return res.status(200).send({ return res.status(200).send({
workspace, workspace
}); });
}; };
@@ -156,15 +191,23 @@ export const createWorkspace = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteWorkspace = async (req: Request, res: Response) => { export const deleteWorkspace = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.DeleteWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Workspace
);
// delete workspace // delete workspace
await deleteWork({ await deleteWork({
id: workspaceId, id: workspaceId
}); });
return res.status(200).send({ return res.status(200).send({
message: "Successfully deleted workspace", message: "Successfully deleted workspace"
}); });
}; };
@@ -175,24 +218,32 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const changeWorkspaceName = async (req: Request, res: Response) => { export const changeWorkspaceName = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
const { name } = req.body; params: { workspaceId },
body: { name }
} = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Workspace
);
const workspace = await Workspace.findOneAndUpdate( const workspace = await Workspace.findOneAndUpdate(
{ {
_id: workspaceId, _id: workspaceId
}, },
{ {
name, name
}, },
{ {
new: true, new: true
} }
); );
return res.status(200).send({ return res.status(200).send({
message: "Successfully changed workspace name", message: "Successfully changed workspace name",
workspace, workspace
}); });
}; };
@@ -203,14 +254,21 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getWorkspaceIntegrations = async (req: Request, res: Response) => { export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const integrations = await Integration.find({ const integrations = await Integration.find({
workspace: workspaceId, workspace: workspaceId
}); });
return res.status(200).send({ return res.status(200).send({
integrations, integrations
}); });
}; };
@@ -220,18 +278,23 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
* @param res * @param res
* @returns * @returns
*/ */
export const getWorkspaceIntegrationAuthorizations = async ( export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { workspaceId }
) => { } = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req);
const { workspaceId } = req.params;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const authorizations = await IntegrationAuth.find({ const authorizations = await IntegrationAuth.find({
workspace: workspaceId, workspace: workspaceId
}); });
return res.status(200).send({ return res.status(200).send({
authorizations, authorizations
}); });
}; };
@@ -241,18 +304,24 @@ export const getWorkspaceIntegrationAuthorizations = async (
* @param res * @param res
* @returns * @returns
*/ */
export const getWorkspaceServiceTokens = async ( export const getWorkspaceServiceTokens = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { workspaceId }
) => { } = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req);
const { workspaceId } = req.params;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.ServiceTokens
);
// ?? FIX. // ?? FIX.
const serviceTokens = await ServiceToken.find({ const serviceTokens = await ServiceToken.find({
user: req.user._id, user: req.user._id,
workspace: workspaceId, workspace: workspaceId
}); });
return res.status(200).send({ return res.status(200).send({
serviceTokens, serviceTokens
}); });
}; };
+71 -69
View File
@@ -10,16 +10,11 @@ import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELogService } from "../../ee/services"; import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors"; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { import { ACTION_LOGIN, TOKEN_EMAIL_MFA } from "../../variables";
ACTION_LOGIN,
TOKEN_EMAIL_MFA,
} from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { import { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config";
getHttpsEnabled, import { validateRequest } from "../../helpers/validation";
getJwtMfaLifetime, import * as reqValidator from "../../validation/auth";
getJwtMfaSecret,
} from "../../config";
declare module "jsonwebtoken" { declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -34,13 +29,10 @@ declare module "jsonwebtoken" {
* @returns * @returns
*/ */
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
const { const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier"); }).select("+salt +verifier");
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
@@ -49,25 +41,28 @@ export const login1 = async (req: Request, res: Response) => {
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier
}, },
async () => { async () => {
// generate server-side public key // generate server-side public key
const serverPublicKey = server.getPublicKey(); const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, { await LoginSRPDetail.findOneAndReplace(
{ email: email },
{
email: email, email: email,
clientPublicKey: clientPublicKey, clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt), serverBInt: bigintConversion.bigintToBuf(server.bInt)
}, { upsert: true, returnNewDocument: false }); },
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
salt: user.salt, salt: user.salt
}); });
} }
); );
}; };
/** /**
@@ -78,19 +73,22 @@ export const login1 = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" }); if (!req.headers["user-agent"])
throw InternalServerError({ message: "User-Agent header is required" });
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"); }).select(
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
);
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email }) const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email });
if (!loginSRPDetail) { if (!loginSRPDetail) {
return BadRequestError(Error("Failed to find login details for SRP")) return BadRequestError(Error("Failed to find login details for SRP"));
} }
const server = new jsrp.server(); const server = new jsrp.server();
@@ -98,7 +96,7 @@ export const login2 = async (req: Request, res: Response) => {
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: loginSRPDetail.serverBInt, b: loginSRPDetail.serverBInt
}, },
async () => { async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey); server.setClientPublicKey(loginSRPDetail.clientPublicKey);
@@ -111,15 +109,15 @@ export const login2 = async (req: Request, res: Response) => {
// generate temporary MFA token // generate temporary MFA token
const token = createToken({ const token = createToken({
payload: { payload: {
userId: user._id.toString(), userId: user._id.toString()
}, },
expiresIn: await getJwtMfaLifetime(), expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret(), secret: await getJwtMfaSecret()
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
@@ -128,27 +126,27 @@ export const login2 = async (req: Request, res: Response) => {
subjectLine: "Infisical MFA code", subjectLine: "Infisical MFA code",
recipients: [email], recipients: [email],
substitutions: { substitutions: {
code, code
}, }
}); });
return res.status(200).send({ return res.status(200).send({
mfaEnabled: true, mfaEnabled: true,
token, token
}); });
} }
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
@@ -156,7 +154,7 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: await getHttpsEnabled(), secure: await getHttpsEnabled()
}); });
// case: user does not have MFA enabled // case: user does not have MFA enabled
@@ -182,36 +180,33 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey, publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey, encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag, tag: user.tag
} };
if ( if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
user?.protectedKey &&
user?.protectedKeyIV &&
user?.protectedKeyTag
) {
response.protectedKey = user.protectedKey; response.protectedKey = user.protectedKey;
response.protectedKeyIV = user.protectedKeyIV response.protectedKeyIV = user.protectedKeyIV;
response.protectedKeyTag = user.protectedKeyTag; response.protectedKeyTag = user.protectedKeyTag;
} }
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id, userId: user._id
}); });
loginAction && await EELogService.createLog({ loginAction &&
(await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]), channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.ip, ipAddress: req.ip
}); }));
return res.status(200).send(response); return res.status(200).send(response);
} }
return res.status(400).send({ return res.status(400).send({
message: "Failed to authenticate. Try again?", message: "Failed to authenticate. Try again?"
}); });
} }
); );
@@ -223,11 +218,13 @@ export const login2 = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const sendMfaToken = async (req: Request, res: Response) => { export const sendMfaToken = async (req: Request, res: Response) => {
const { email } = req.body; const {
body: { email }
} = await validateRequest(reqValidator.SendMfaTokenV2, req);
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
@@ -236,14 +233,14 @@ export const sendMfaToken = async (req: Request, res: Response) => {
subjectLine: "Infisical MFA code", subjectLine: "Infisical MFA code",
recipients: [email], recipients: [email],
substitutions: { substitutions: {
code, code
}, }
}); });
return res.status(200).send({ return res.status(200).send({
message: "Successfully sent new MFA code", message: "Successfully sent new MFA code"
}); });
} };
/** /**
* Verify MFA token [mfaToken] and issue JWT and refresh tokens if the * Verify MFA token [mfaToken] and issue JWT and refresh tokens if the
@@ -252,33 +249,37 @@ export const sendMfaToken = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const verifyMfaToken = async (req: Request, res: Response) => { export const verifyMfaToken = async (req: Request, res: Response) => {
const { email, mfaToken } = req.body; const {
body: { email, mfaToken }
} = await validateRequest(reqValidator.VerifyMfaTokenV2, req);
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email,
token: mfaToken, token: mfaToken
}); });
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"); }).select(
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
);
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
await LoginSRPDetail.deleteOne({ userId: user.id }) await LoginSRPDetail.deleteOne({ userId: user.id });
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
@@ -286,7 +287,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: await getHttpsEnabled(), secure: await getHttpsEnabled()
}); });
interface VerifyMfaTokenRes { interface VerifyMfaTokenRes {
@@ -319,8 +320,8 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
publicKey: user.publicKey as string, publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string, encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string, iv: user.iv as string,
tag: user.tag as string, tag: user.tag as string
} };
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) { if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey; resObj.protectedKey = user.protectedKey;
@@ -330,15 +331,16 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id, userId: user._id
}); });
loginAction && await EELogService.createLog({ loginAction &&
(await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]), channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP, ipAddress: req.realIP
}); }));
return res.status(200).send(resObj); return res.status(200).send(resObj);
} };
@@ -6,13 +6,21 @@ import {
Secret, Secret,
ServiceToken, ServiceToken,
ServiceTokenData, ServiceTokenData,
Workspace, Workspace
} from "../../models"; } from "../../models";
import { EventType, SecretVersion } from "../../ee/models"; import { EventType, SecretVersion } from "../../ee/models";
import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { EEAuditLogService, EELicenseService } from "../../ee/services";
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors"; import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
import _ from "lodash"; import _ from "lodash";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables"; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/environments";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
/** /**
* Create new workspace environment named [environmentName] under workspace with id * Create new workspace environment named [environmentName] under workspace with id
@@ -20,13 +28,18 @@ import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variabl
* @param res * @param res
* @returns * @returns
*/ */
export const createWorkspaceEnvironment = async ( export const createWorkspaceEnvironment = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { workspaceId },
) => { body: { environmentName, environmentSlug }
} = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Environments
);
const { workspaceId } = req.params;
const { environmentName, environmentSlug } = req.body;
const workspace = await Workspace.findById(workspaceId).exec(); const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) throw WorkspaceNotFoundError(); if (!workspace) throw WorkspaceNotFoundError();
@@ -39,7 +52,8 @@ export const createWorkspaceEnvironment = async (
// case: number of environments used exceeds the number of environments allowed // case: number of environments used exceeds the number of environments allowed
return res.status(400).send({ return res.status(400).send({
message: "Failed to create environment due to environment limit reached. Upgrade plan to create more environments.", message:
"Failed to create environment due to environment limit reached. Upgrade plan to create more environments."
}); });
} }
} }
@@ -55,7 +69,7 @@ export const createWorkspaceEnvironment = async (
workspace?.environments.push({ workspace?.environments.push({
name: environmentName, name: environmentName,
slug: environmentSlug.toLowerCase(), slug: environmentSlug.toLowerCase()
}); });
await workspace.save(); await workspace.save();
@@ -80,8 +94,8 @@ export const createWorkspaceEnvironment = async (
workspace: workspaceId, workspace: workspaceId,
environment: { environment: {
name: environmentName, name: environmentName,
slug: environmentSlug, slug: environmentSlug
}, }
}); });
}; };
@@ -91,12 +105,17 @@ export const createWorkspaceEnvironment = async (
* @param res * @param res
* @returns * @returns
*/ */
export const reorderWorkspaceEnvironments = async ( export const reorderWorkspaceEnvironments = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { workspaceId },
) => { body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName }
const { workspaceId } = req.params; } = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req);
const { environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName } = req.body;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments
);
// atomic update the env to avoid conflict // atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec(); const workspace = await Workspace.findById(workspaceId).exec();
@@ -104,21 +123,28 @@ export const reorderWorkspaceEnvironments = async (
throw BadRequestError({ message: "Couldn't load workspace" }); throw BadRequestError({ message: "Couldn't load workspace" });
} }
const environmentIndex = workspace.environments.findIndex((env) => env.name === environmentName && env.slug === environmentSlug) const environmentIndex = workspace.environments.findIndex(
const otherEnvironmentIndex = workspace.environments.findIndex((env) => env.name === otherEnvironmentName && env.slug === otherEnvironmentSlug) (env) => env.name === environmentName && env.slug === environmentSlug
);
const otherEnvironmentIndex = workspace.environments.findIndex(
(env) => env.name === otherEnvironmentName && env.slug === otherEnvironmentSlug
);
if (environmentIndex === -1 || otherEnvironmentIndex === -1) { if (environmentIndex === -1 || otherEnvironmentIndex === -1) {
throw BadRequestError({message: "environment or otherEnvironment couldn't be found"}) throw BadRequestError({ message: "environment or otherEnvironment couldn't be found" });
} }
// swap the order of the environments // swap the order of the environments
[workspace.environments[environmentIndex], workspace.environments[otherEnvironmentIndex]] = [workspace.environments[otherEnvironmentIndex], workspace.environments[environmentIndex]] [workspace.environments[environmentIndex], workspace.environments[otherEnvironmentIndex]] = [
workspace.environments[otherEnvironmentIndex],
workspace.environments[environmentIndex]
];
await workspace.save() await workspace.save();
return res.status(200).send({ return res.status(200).send({
message: "Successfully reordered environments", message: "Successfully reordered environments",
workspace: workspaceId, workspace: workspaceId
}); });
}; };
@@ -129,12 +155,18 @@ export const reorderWorkspaceEnvironments = async (
* @param res * @param res
* @returns * @returns
*/ */
export const renameWorkspaceEnvironment = async ( export const renameWorkspaceEnvironment = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { workspaceId },
) => { body: { environmentName, environmentSlug, oldEnvironmentSlug }
const { workspaceId } = req.params; } = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req);
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments
);
// user should pass both new slug and env name // user should pass both new slug and env name
if (!environmentSlug || !environmentName) { if (!environmentSlug || !environmentName) {
throw new Error("Invalid environment given."); throw new Error("Invalid environment given.");
@@ -148,16 +180,13 @@ export const renameWorkspaceEnvironment = async (
const isEnvExist = workspace.environments.some( const isEnvExist = workspace.environments.some(
({ name, slug }) => ({ name, slug }) =>
slug !== oldEnvironmentSlug && slug !== oldEnvironmentSlug && (name === environmentName || slug === environmentSlug)
(name === environmentName || slug === environmentSlug)
); );
if (isEnvExist) { if (isEnvExist) {
throw new Error("Invalid environment given"); throw new Error("Invalid environment given");
} }
const envIndex = workspace?.environments.findIndex( const envIndex = workspace?.environments.findIndex(({ slug }) => slug === oldEnvironmentSlug);
({ slug }) => slug === oldEnvironmentSlug
);
if (envIndex === -1) { if (envIndex === -1) {
throw new Error("Invalid environment given"); throw new Error("Invalid environment given");
} }
@@ -191,7 +220,7 @@ export const renameWorkspaceEnvironment = async (
await Membership.updateMany( await Membership.updateMany(
{ {
workspace: workspaceId, workspace: workspaceId,
"deniedPermissions.environmentSlug": oldEnvironmentSlug, "deniedPermissions.environmentSlug": oldEnvironmentSlug
}, },
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
@@ -218,8 +247,8 @@ export const renameWorkspaceEnvironment = async (
workspace: workspaceId, workspace: workspaceId,
environment: { environment: {
name: environmentName, name: environmentName,
slug: environmentSlug, slug: environmentSlug
}, }
}); });
}; };
@@ -229,21 +258,25 @@ export const renameWorkspaceEnvironment = async (
* @param res * @param res
* @returns * @returns
*/ */
export const deleteWorkspaceEnvironment = async ( export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { workspaceId },
) => { body: { environmentSlug }
const { workspaceId } = req.params; } = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req);
const { environmentSlug } = req.body;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Environments
);
// atomic update the env to avoid conflict // atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec(); const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) { if (!workspace) {
throw new Error("Failed to create workspace environment"); throw new Error("Failed to create workspace environment");
} }
const envIndex = workspace?.environments.findIndex( const envIndex = workspace?.environments.findIndex(({ slug }) => slug === environmentSlug);
({ slug }) => slug === environmentSlug
);
if (envIndex === -1) { if (envIndex === -1) {
throw new Error("Invalid environment given"); throw new Error("Invalid environment given");
} }
@@ -256,11 +289,11 @@ export const deleteWorkspaceEnvironment = async (
// clean up // clean up
await Secret.deleteMany({ await Secret.deleteMany({
workspace: workspaceId, workspace: workspaceId,
environment: environmentSlug, environment: environmentSlug
}); });
await SecretVersion.deleteMany({ await SecretVersion.deleteMany({
workspace: workspaceId, workspace: workspaceId,
environment: environmentSlug, environment: environmentSlug
}); });
// await ServiceToken.deleteMany({ // await ServiceToken.deleteMany({
@@ -279,7 +312,7 @@ export const deleteWorkspaceEnvironment = async (
await Integration.deleteMany({ await Integration.deleteMany({
workspace: workspaceId, workspace: workspaceId,
environment: environmentSlug, environment: environmentSlug
}); });
await Membership.updateMany( await Membership.updateMany(
{ workspace: workspaceId }, { workspace: workspaceId },
@@ -305,46 +338,48 @@ export const deleteWorkspaceEnvironment = async (
return res.status(200).send({ return res.status(200).send({
message: "Successfully deleted environment", message: "Successfully deleted environment",
workspace: workspaceId, workspace: workspaceId,
environment: environmentSlug, environment: environmentSlug
}); });
}; };
// TODO(akhilmhdh) after rbac this can be completely removed
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetAllAccessibileEnvironmentsOfWorkspaceV2, req);
export const getAllAccessibleEnvironmentsOfWorkspace = async ( const { membership: workspacesUserIsMemberOf } = await getUserProjectPermissions(
req: Request, req.user._id,
res: Response workspaceId
) => { );
const { workspaceId } = req.params;
const workspacesUserIsMemberOf = await Membership.findOne({
workspace: workspaceId,
user: req.user,
})
if (!workspacesUserIsMemberOf) { const accessibleEnvironments: any = [];
throw BadRequestError() const deniedPermission = workspacesUserIsMemberOf.deniedPermissions;
}
const accessibleEnvironments: any = [] const relatedWorkspace = await Workspace.findById(workspaceId);
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions
const relatedWorkspace = await Workspace.findById(workspaceId)
if (!relatedWorkspace) { if (!relatedWorkspace) {
throw BadRequestError() throw BadRequestError();
} }
relatedWorkspace.environments.forEach(environment => { relatedWorkspace.environments.forEach((environment) => {
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_READ_SECRETS }) const isReadBlocked = _.some(deniedPermission, {
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_WRITE_SECRETS }) environmentSlug: environment.slug,
ability: PERMISSION_READ_SECRETS
});
const isWriteBlocked = _.some(deniedPermission, {
environmentSlug: environment.slug,
ability: PERMISSION_WRITE_SECRETS
});
if (isReadBlocked && isWriteBlocked) { if (isReadBlocked && isWriteBlocked) {
return return;
} else { } else {
accessibleEnvironments.push({ accessibleEnvironments.push({
name: environment.name, name: environment.name,
slug: environment.slug, slug: environment.slug,
isWriteDenied: isWriteBlocked, isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked, isReadDenied: isReadBlocked
}) });
} }
}) });
res.json({ accessibleEnvironments }) res.json({ accessibleEnvironments });
}; };
@@ -1,13 +1,19 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import { Membership, MembershipOrg, ServiceAccount, Workspace } from "../../models";
Membership,
MembershipOrg,
ServiceAccount,
Workspace,
} from "../../models";
import { deleteMembershipOrg } from "../../helpers/membershipOrg"; import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import { updateSubscriptionOrgQuantity } from "../../helpers/organization"; import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import Role from "../../models/role";
import { BadRequestError } from "../../utils/errors";
import { CUSTOM } from "../../variables";
import * as reqValidator from "../../validation/organization";
import { validateRequest } from "../../helpers/validation";
import {
OrgPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
/** /**
* Return memberships for organization with id [organizationId] * Return memberships for organization with id [organizationId]
@@ -48,16 +54,24 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
} }
} }
*/ */
const { organizationId } = req.params; const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgMembersv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Member
);
const memberships = await MembershipOrg.find({ const memberships = await MembershipOrg.find({
organization: organizationId, organization: organizationId
}).populate("user", "+publicKey"); }).populate("user", "+publicKey");
return res.status(200).send({ return res.status(200).send({
memberships, memberships
}); });
} };
/** /**
* Update role of membership with id [membershipId] to role [role] * Update role of membership with id [membershipId] to role [role]
@@ -118,22 +132,44 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
} }
} }
*/ */
const { membershipId } = req.params; const {
const { role } = req.body; params: { organizationId, membershipId },
body: { role }
} = await validateRequest(reqValidator.UpdateOrgMemberv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Member
);
const isCustomRole = !["admin", "member", "owner"].includes(role);
if (isCustomRole) {
const orgRole = await Role.findOne({ slug: role, isOrgRole: true });
if (!orgRole) throw BadRequestError({ message: "Role not found" });
const membership = await MembershipOrg.findByIdAndUpdate(membershipId, {
role: CUSTOM,
customRole: orgRole
});
return res.status(200).send({
membership
});
}
const membership = await MembershipOrg.findByIdAndUpdate( const membership = await MembershipOrg.findByIdAndUpdate(
membershipId, membershipId,
{ {
role, role
}, { },
new: true, {
new: true
} }
); );
return res.status(200).send({ return res.status(200).send({
membership, membership
}); });
} };
/** /**
* Delete organization membership with id [membershipId] * Delete organization membership with id [membershipId]
@@ -178,21 +214,28 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
} }
} }
*/ */
const { membershipId } = req.params; const {
params: { organizationId, membershipId }
} = await validateRequest(reqValidator.DeleteOrgMemberv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.Member
);
// delete organization membership // delete organization membership
const membership = await deleteMembershipOrg({ const membership = await deleteMembershipOrg({
membershipOrgId: membershipId, membershipOrgId: membershipId
}); });
await updateSubscriptionOrgQuantity({ await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString(), organizationId: membership.organization.toString()
}); });
return res.status(200).send({ return res.status(200).send({
membership, membership
}); });
} };
/** /**
* Return workspaces for organization with id [organizationId] that user has * Return workspaces for organization with id [organizationId] that user has
@@ -234,13 +277,21 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
} }
} }
*/ */
const { organizationId } = req.params; const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgWorkspacesv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Workspace
);
const workspacesSet = new Set( const workspacesSet = new Set(
( (
await Workspace.find( await Workspace.find(
{ {
organization: organizationId, organization: organizationId
}, },
"_id" "_id"
) )
@@ -249,16 +300,16 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
const workspaces = ( const workspaces = (
await Membership.find({ await Membership.find({
user: req.user._id, user: req.user._id
}).populate("workspace") }).populate("workspace")
) )
.filter((m) => workspacesSet.has(m.workspace._id.toString())) .filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace); .map((m) => m.workspace);
return res.status(200).send({ return res.status(200).send({
workspaces, workspaces
}); });
} };
/** /**
* Return service accounts for organization with id [organizationId] * Return service accounts for organization with id [organizationId]
@@ -269,10 +320,10 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response
const { organizationId } = req.params; const { organizationId } = req.params;
const serviceAccounts = await ServiceAccount.find({ const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId), organization: new Types.ObjectId(organizationId)
}); });
return res.status(200).send({ return res.status(200).send({
serviceAccounts, serviceAccounts
}); });
} };
+94 -44
View File
@@ -25,7 +25,6 @@ import {
userHasWriteOnlyAbility userHasWriteOnlyAbility
} from "../../ee/helpers/checkMembershipPermissions"; } from "../../ee/helpers/checkMembershipPermissions";
import _ from "lodash"; import _ from "lodash";
import { BatchSecret, BatchSecretRequest } from "../../types/secret";
import { import {
getFolderByPath, getFolderByPath,
getFolderIdFromServiceToken, getFolderIdFromServiceToken,
@@ -35,6 +34,18 @@ import {
import { isValidScope } from "../../helpers/secrets"; import { isValidScope } from "../../helpers/secrets";
import path from "path"; import path from "path";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import { validateRequest } from "../../helpers/validation";
import {
BatchSecretsV2,
GetSecretsV2,
validateServiceTokenDataClientForWorkspace
} from "../../validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability";
/** /**
* Peform a batch of any specified CUD secret operations * Peform a batch of any specified CUD secret operations
@@ -46,22 +57,31 @@ export const batchSecrets = async (req: Request, res: Response) => {
const channel = getUserAgentType(req.headers["user-agent"]); const channel = getUserAgentType(req.headers["user-agent"]);
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const validatedData = await validateRequest(BatchSecretsV2, req);
const { const {
workspaceId, body: { workspaceId, environment, requests }
environment, } = validatedData;
requests let {
}: { body: { secretPath, folderId }
workspaceId: string; } = validatedData;
environment: string;
requests: BatchSecretRequest[];
} = req.body;
let secretPath = req.body.secretPath as string; const secretIds = requests
let folderId = req.body.folderId as string; .filter(({ method }) => method !== "POST")
// akhilmhdh: ts is dumb
.map((el) => new Types.ObjectId((el.secret as any)._id));
const createSecrets: BatchSecret[] = []; const oldSecrets = await Secret.find({
const updateSecrets: BatchSecret[] = []; _id: {
const deleteSecrets: { _id: Types.ObjectId, secretName: string; }[] = []; $in: secretIds
}
});
if (oldSecrets.length != secretIds.length) {
throw BadRequestError({ message: "Failed to validate non-existent secrets" });
}
const createSecrets: any[] = [];
const updateSecrets: any[] = [];
const deleteSecrets: { _id: Types.ObjectId; secretName: string }[] = [];
const actions: IAction[] = []; const actions: IAction[] = [];
// get secret blind index salt // get secret blind index salt
@@ -71,16 +91,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (req.authData.authPayload instanceof ServiceTokenData) {
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
// in service token when not giving secretpath folderid must be root
// this is to avoid giving folderid when service tokens are used
if ((!secretPath && folderId !== "root") || (secretPath && !isValidScopeAccess)) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
if (secretPath) { if (secretPath) {
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
} }
@@ -94,6 +104,16 @@ export const batchSecrets = async (req: Request, res: Response) => {
); );
} }
if (req.authData.authPayload instanceof ServiceTokenData) {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_WRITE_SECRETS]
});
}
for await (const request of requests) { for await (const request of requests) {
// do a validation // do a validation
@@ -110,7 +130,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
version: 1, version: 1,
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined, user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
environment, environment,
workspace: new Types.ObjectId(workspaceId), workspace: workspaceId,
folder: folderId, folder: folderId,
secretBlindIndex, secretBlindIndex,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
@@ -125,7 +145,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
updateSecrets.push({ updateSecrets.push({
...request.secret, ...request.secret,
_id: new Types.ObjectId(request.secret._id), _id: request.secret._id,
secretBlindIndex, secretBlindIndex,
folder: folderId, folder: folderId,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
@@ -133,15 +153,39 @@ export const batchSecrets = async (req: Request, res: Response) => {
}); });
break; break;
case "DELETE": case "DELETE":
deleteSecrets.push({ _id: new Types.ObjectId(request.secret._id), secretName: request.secret.secretName }); deleteSecrets.push({
_id: new Types.ObjectId(request.secret._id),
secretName: request.secret.secretName
});
break; break;
} }
} }
// not using service token using auth
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
if (createSecrets.length)
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
if (updateSecrets.length)
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
if (deleteSecrets.length)
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
}
// handle create secrets // handle create secrets
let createdSecrets: ISecret[] = []; let createdSecrets: ISecret[] = [];
if (createSecrets.length > 0) { if (createSecrets.length > 0) {
createdSecrets = await Secret.insertMany(createSecrets); createdSecrets = (await Secret.insertMany(createSecrets)) as any;
// (EE) add secret versions for new secrets // (EE) add secret versions for new secrets
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: createdSecrets.map((n: any) => { secretVersions: createdSecrets.map((n: any) => {
@@ -206,7 +250,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
// handle update secrets // handle update secrets
let updatedSecrets: ISecret[] = []; let updatedSecrets: ISecret[] = [];
if (updateSecrets.length > 0 && req.secrets) { if (updateSecrets.length > 0 && oldSecrets) {
// construct object containing all secrets // construct object containing all secrets
let listedSecretsObj: { let listedSecretsObj: {
[key: string]: { [key: string]: {
@@ -215,7 +259,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
}; };
} = {}; } = {};
listedSecretsObj = req.secrets.reduce( listedSecretsObj = oldSecrets.reduce(
(obj: any, secret: ISecret) => ({ (obj: any, secret: ISecret) => ({
...obj, ...obj,
[secret._id.toString()]: secret [secret._id.toString()]: secret
@@ -227,7 +271,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
updateOne: { updateOne: {
filter: { filter: {
_id: new Types.ObjectId(u._id), _id: new Types.ObjectId(u._id),
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId),
environment
}, },
update: { update: {
$inc: { $inc: {
@@ -241,7 +286,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
} }
} }
})); }));
await Secret.bulkWrite(updateOperations); await Secret.bulkWrite(updateOperations);
const secretVersions = updateSecrets.map( const secretVersions = updateSecrets.map(
@@ -332,12 +376,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
if (deleteSecrets.length > 0) { if (deleteSecrets.length > 0) {
const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id); const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id);
const deletedSecretsObj = (await Secret.find({ const deletedSecretsObj = (
await Secret.find({
_id: { _id: {
$in: deleteSecretIds $in: deleteSecretIds
} }
})) })
.reduce( ).reduce(
(obj: any, secret: ISecret) => ({ (obj: any, secret: ISecret) => ({
...obj, ...obj,
[secret._id.toString()]: secret [secret._id.toString()]: secret
@@ -348,7 +393,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
await Secret.deleteMany({ await Secret.deleteMany({
_id: { _id: {
$in: deleteSecretIds $in: deleteSecretIds
} },
workspace: new Types.ObjectId(workspaceId),
environment
}); });
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
@@ -781,10 +828,13 @@ export const getSecrets = async (req: Request, res: Response) => {
} }
*/ */
const { tagSlugs, secretPath, include_imports } = req.query; const validatedData = await validateRequest(GetSecretsV2, req);
let { folderId } = req.query; const {
const workspaceId = req.query.workspaceId as string; query: { tagSlugs, secretPath, include_imports, workspaceId, environment }
const environment = req.query.environment as string; } = validatedData;
let {
query: { folderId }
} = validatedData;
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
@@ -926,7 +976,7 @@ export const getSecrets = async (req: Request, res: Response) => {
// TODO(akhilmhdh) - secret-imp change this to org type // TODO(akhilmhdh) - secret-imp change this to org type
let importedSecrets: any[] = []; let importedSecrets: any[] = [];
if (include_imports === "true") { if (include_imports) {
importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string); importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string);
} }
@@ -970,17 +1020,17 @@ export const getSecrets = async (req: Request, res: Response) => {
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
// reduce the number of events captured // reduce the number of events captured
let shouldRecordK8Event = false let shouldRecordK8Event = false;
if (req.authData.userAgent == K8_USER_AGENT_NAME) { if (req.authData.userAgent == K8_USER_AGENT_NAME) {
const randomNumber = Math.random(); const randomNumber = Math.random();
if (randomNumber > 0.9) { if (randomNumber > 0.9) {
shouldRecordK8Event = true shouldRecordK8Event = true;
} }
} }
if (postHogClient) { if (postHogClient) {
const shouldCapture = req.authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event; const shouldCapture = req.authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
const approximateForNoneCapturedEvents = secrets.length * 10 const approximateForNoneCapturedEvents = secrets.length * 10;
if (shouldCapture) { if (shouldCapture) {
postHogClient.capture({ postHogClient.capture({
@@ -6,6 +6,15 @@ import { getSaltRounds } from "../../config";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { ActorType, EventType } from "../../ee/models"; import { ActorType, EventType } from "../../ee/models";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/serviceTokenData";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { Types } from "mongoose";
/** /**
* Return service token data associated with service token on request * Return service token data associated with service token on request
@@ -63,7 +72,14 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
export const createServiceTokenData = async (req: Request, res: Response) => { export const createServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData; let serviceTokenData;
const { name, workspaceId, encryptedKey, iv, tag, expiresIn, permissions, scopes } = req.body; const {
body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv }
} = await validateRequest(reqValidator.CreateServiceTokenV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens
);
const secret = crypto.randomBytes(16).toString("hex"); const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
@@ -111,7 +127,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
} }
}, },
{ {
workspaceId workspaceId: new Types.ObjectId(workspaceId)
} }
); );
@@ -128,11 +144,26 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteServiceTokenData = async (req: Request, res: Response) => { export const deleteServiceTokenData = async (req: Request, res: Response) => {
const { serviceTokenDataId } = req.params; const {
params: { serviceTokenDataId }
} = await validateRequest(reqValidator.DeleteServiceTokenV2, req);
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId); let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId);
if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" });
if (!serviceTokenData) return res.status(200).send({ const { permission } = await getUserProjectPermissions(
req.user._id,
serviceTokenData.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.ServiceTokens
);
serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
if (!serviceTokenData)
return res.status(200).send({
message: "Failed to delete service token" message: "Failed to delete service token"
}); });
+40 -17
View File
@@ -1,18 +1,33 @@
import { ForbiddenError } from "@casl/ability";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Membership, Secret, Tag } from "../../models"; import { Secret, Tag } from "../../models";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { validateRequest } from "../../helpers/validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import * as reqValidator from "../../validation/tags";
export const createWorkspaceTag = async (req: Request, res: Response) => { export const createWorkspaceTag = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
const { name, slug, tagColor } = req.body; body: { name, slug },
params: { workspaceId }
} = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Tags
);
const tagToCreate = { const tagToCreate = {
name, name,
tagColor,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
slug, slug,
user: new Types.ObjectId(req.user._id), user: new Types.ObjectId(req.user._id)
}; };
const createdTag = await new Tag(tagToCreate).save(); const createdTag = await new Tag(tagToCreate).save();
@@ -21,22 +36,23 @@ export const createWorkspaceTag = async (req: Request, res: Response) => {
}; };
export const deleteWorkspaceTag = async (req: Request, res: Response) => { export const deleteWorkspaceTag = async (req: Request, res: Response) => {
const { tagId } = req.params; const {
params: { tagId }
} = await validateRequest(reqValidator.DeleteWorkspaceTagsV2, req);
const tagFromDB = await Tag.findById(tagId); const tagFromDB = await Tag.findById(tagId);
if (!tagFromDB) { if (!tagFromDB) {
throw BadRequestError(); throw BadRequestError();
} }
// can only delete if the request user is one that belongs to the same workspace as the tag const { permission } = await getUserProjectPermissions(
const membership = await Membership.findOne({ req.user._id,
user: req.user, tagFromDB.workspace.toString()
workspace: tagFromDB.workspace );
}); ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
if (!membership) { ProjectPermissionSub.Tags
UnauthorizedRequestError({ message: "Failed to validate membership" }); );
}
const result = await Tag.findByIdAndDelete(tagId); const result = await Tag.findByIdAndDelete(tagId);
@@ -47,7 +63,14 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
}; };
export const getWorkspaceTags = async (req: Request, res: Response) => { export const getWorkspaceTags = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Tags
);
const workspaceTags = await Tag.find({ const workspaceTags = await Tag.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
+49 -53
View File
@@ -2,14 +2,10 @@ import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import crypto from "crypto"; import crypto from "crypto";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { import { APIKeyData, AuthMethod, MembershipOrg, TokenVersion, User } from "../../models";
APIKeyData,
AuthMethod,
MembershipOrg,
TokenVersion,
User
} from "../../models";
import { getSaltRounds } from "../../config"; import { getSaltRounds } from "../../config";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation";
/** /**
* Return the current user. * Return the current user.
@@ -43,14 +39,14 @@ export const getMe = async (req: Request, res: Response) => {
} }
} }
*/ */
const user = await User const user = await User.findById(req.user._id).select(
.findById(req.user._id) "+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag"
.select("+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag"); );
return res.status(200).send({ return res.status(200).send({
user, user
}); });
} };
/** /**
* Update the current user's MFA-enabled status [isMfaEnabled]. * Update the current user's MFA-enabled status [isMfaEnabled].
@@ -61,7 +57,10 @@ export const getMe = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const updateMyMfaEnabled = async (req: Request, res: Response) => { export const updateMyMfaEnabled = async (req: Request, res: Response) => {
const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body; const {
body: { isMfaEnabled }
} = await validateRequest(reqValidator.UpdateMyMfaEnabledV2, req);
req.user.isMfaEnabled = isMfaEnabled; req.user.isMfaEnabled = isMfaEnabled;
if (isMfaEnabled) { if (isMfaEnabled) {
@@ -77,9 +76,9 @@ export const updateMyMfaEnabled = async (req: Request, res: Response) => {
const user = req.user; const user = req.user;
return res.status(200).send({ return res.status(200).send({
user, user
}); });
} };
/** /**
* Update name of the current user to [firstName, lastName]. * Update name of the current user to [firstName, lastName].
@@ -89,12 +88,8 @@ export const updateMyMfaEnabled = async (req: Request, res: Response) => {
*/ */
export const updateName = async (req: Request, res: Response) => { export const updateName = async (req: Request, res: Response) => {
const { const {
firstName, body: { lastName, firstName }
lastName } = await validateRequest(reqValidator.UpdateNameV2, req);
}: {
firstName: string;
lastName: string;
} = req.body;
const user = await User.findByIdAndUpdate( const user = await User.findByIdAndUpdate(
req.user._id.toString(), req.user._id.toString(),
@@ -108,9 +103,9 @@ export const updateName = async (req: Request, res: Response) => {
); );
return res.status(200).send({ return res.status(200).send({
user, user
}); });
} };
/** /**
* Update auth method of the current user to [authMethods] * Update auth method of the current user to [authMethods]
@@ -120,16 +115,11 @@ export const updateName = async (req: Request, res: Response) => {
*/ */
export const updateAuthMethods = async (req: Request, res: Response) => { export const updateAuthMethods = async (req: Request, res: Response) => {
const { const {
authMethods body: { authMethods }
} = req.body; } = await validateRequest(reqValidator.UpdateAuthMethodsV2, req);
const hasSamlEnabled = req.user.authMethods const hasSamlEnabled = req.user.authMethods.some((authMethod: AuthMethod) =>
.some( [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod)
(authMethod: AuthMethod) => [
AuthMethod.OKTA_SAML,
AuthMethod.AZURE_SAML,
AuthMethod.JUMPCLOUD_SAML
].includes(authMethod)
); );
if (hasSamlEnabled) { if (hasSamlEnabled) {
@@ -151,8 +141,7 @@ export const updateName = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
user user
}); });
} };
/** /**
* Return organizations that the current user is part of. * Return organizations that the current user is part of.
@@ -189,14 +178,14 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
*/ */
const organizations = ( const organizations = (
await MembershipOrg.find({ await MembershipOrg.find({
user: req.user._id, user: req.user._id
}).populate("organization") }).populate("organization")
).map((m) => m.organization); ).map((m) => m.organization);
return res.status(200).send({ return res.status(200).send({
organizations, organizations
}); });
} };
/** /**
* Return API keys belonging to current user. * Return API keys belonging to current user.
@@ -206,11 +195,11 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
*/ */
export const getMyAPIKeys = async (req: Request, res: Response) => { export const getMyAPIKeys = async (req: Request, res: Response) => {
const apiKeyData = await APIKeyData.find({ const apiKeyData = await APIKeyData.find({
user: req.user._id, user: req.user._id
}); });
return res.status(200).send(apiKeyData); return res.status(200).send(apiKeyData);
} };
/** /**
* Create new API key for current user. * Create new API key for current user.
@@ -219,7 +208,9 @@ export const getMyAPIKeys = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const createAPIKey = async (req: Request, res: Response) => { export const createAPIKey = async (req: Request, res: Response) => {
const { name, expiresIn } = req.body; const {
body: { name, expiresIn }
} = await validateRequest(reqValidator.CreateApiKeyV2, req);
const secret = crypto.randomBytes(16).toString("hex"); const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
@@ -232,7 +223,7 @@ export const createAPIKey = async (req: Request, res: Response) => {
lastUsed: new Date(), lastUsed: new Date(),
expiresAt, expiresAt,
user: req.user._id, user: req.user._id,
secretHash, secretHash
}).save(); }).save();
// return api key data without sensitive data // return api key data without sensitive data
@@ -244,9 +235,9 @@ export const createAPIKey = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
apiKey, apiKey,
apiKeyData, apiKeyData
}); });
} };
/** /**
* Delete API key with id [apiKeyDataId] belonging to current user * Delete API key with id [apiKeyDataId] belonging to current user
@@ -254,7 +245,9 @@ export const createAPIKey = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const deleteAPIKey = async (req: Request, res: Response) => { export const deleteAPIKey = async (req: Request, res: Response) => {
const { apiKeyDataId } = req.params; const {
params: { apiKeyDataId }
} = await validateRequest(reqValidator.DeleteApiKeyV2, req);
const apiKeyData = await APIKeyData.findOneAndDelete({ const apiKeyData = await APIKeyData.findOneAndDelete({
_id: new Types.ObjectId(apiKeyDataId), _id: new Types.ObjectId(apiKeyDataId),
@@ -264,7 +257,7 @@ export const deleteAPIKey = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
apiKeyData apiKeyData
}); });
} };
/** /**
* Return active sessions (TokenVersion) belonging to user * Return active sessions (TokenVersion) belonging to user
@@ -278,7 +271,7 @@ export const getMySessions = async (req: Request, res: Response) => {
}); });
return res.status(200).send(tokenVersions); return res.status(200).send(tokenVersions);
} };
/** /**
* Revoke all active sessions belong to user * Revoke all active sessions belong to user
@@ -287,16 +280,19 @@ export const getMySessions = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteMySessions = async (req: Request, res: Response) => { export const deleteMySessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({ await TokenVersion.updateMany(
user: req.user._id, {
}, { user: req.user._id
},
{
$inc: { $inc: {
refreshVersion: 1, refreshVersion: 1,
accessVersion: 1, accessVersion: 1
}, }
}); }
);
return res.status(200).send({ return res.status(200).send({
message: "Successfully revoked all sessions" message: "Successfully revoked all sessions"
}); });
} };
@@ -11,6 +11,14 @@ import { EventService, TelemetryService } from "../../services";
import { eventPushSecrets } from "../../events"; import { eventPushSecrets } from "../../events";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
interface V2PushSecret { interface V2PushSecret {
type: string; // personal or shared type: string; // personal or shared
@@ -181,7 +189,9 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
} }
} }
*/ */
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceKeyV2, req);
const key = await Key.findOne({ const key = await Key.findOne({
workspace: workspaceId, workspace: workspaceId,
@@ -258,7 +268,15 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
} }
} }
*/ */
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
);
const memberships = await Membership.find({ const memberships = await Membership.find({
workspace: workspaceId workspace: workspaceId
@@ -329,8 +347,16 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
} }
} }
*/ */
const { membershipId } = req.params; const {
const { role } = req.body; params: { workspaceId, membershipId },
body: { role }
} = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
);
const membership = await Membership.findByIdAndUpdate( const membership = await Membership.findByIdAndUpdate(
membershipId, membershipId,
@@ -390,7 +416,15 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
} }
} }
*/ */
const { membershipId } = req.params; const {
params: { workspaceId, membershipId }
} = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Member
);
const membership = await Membership.findByIdAndDelete(membershipId); const membership = await Membership.findByIdAndDelete(membershipId);
@@ -413,8 +447,16 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
* @returns * @returns
*/ */
export const toggleAutoCapitalization = async (req: Request, res: Response) => { export const toggleAutoCapitalization = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
const { autoCapitalization } = req.body; params: { workspaceId },
body: { autoCapitalization }
} = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Settings
);
const workspace = await Workspace.findOneAndUpdate( const workspace = await Workspace.findOneAndUpdate(
{ {
+53 -59
View File
@@ -10,24 +10,19 @@ import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELogService } from "../../ee/services"; import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors"; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { import { ACTION_LOGIN, TOKEN_EMAIL_MFA } from "../../variables";
ACTION_LOGIN,
TOKEN_EMAIL_MFA,
} from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { import { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config";
getHttpsEnabled,
getJwtMfaLifetime,
getJwtMfaSecret,
} from "../../config";
import { AuthMethod } from "../../models/user"; import { AuthMethod } from "../../models/user";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
declare module "jsonwebtoken" { declare module "jsonwebtoken" {
export interface ProviderAuthJwtPayload extends jwt.JwtPayload { export interface ProviderAuthJwtPayload extends jwt.JwtPayload {
userId: string; userId: string;
email: string; email: string;
authProvider: AuthMethod; authProvider: AuthMethod;
isUserCompleted: boolean, isUserCompleted: boolean;
} }
} }
@@ -39,17 +34,11 @@ declare module "jsonwebtoken" {
*/ */
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
const { const {
email, body: { email, clientPublicKey, providerAuthToken }
providerAuthToken, } = await validateRequest(reqValidator.Login1V3, req);
clientPublicKey,
}: {
email: string;
clientPublicKey: string,
providerAuthToken?: string;
} = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier"); }).select("+salt +verifier");
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
@@ -57,7 +46,7 @@ export const login1 = async (req: Request, res: Response) => {
if (!user.authMethods.includes(AuthMethod.EMAIL)) { if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({ await validateProviderAuthToken({
email, email,
providerAuthToken, providerAuthToken
}); });
} }
@@ -65,23 +54,27 @@ export const login1 = async (req: Request, res: Response) => {
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier
}, },
async () => { async () => {
// generate server-side public key // generate server-side public key
const serverPublicKey = server.getPublicKey(); const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ await LoginSRPDetail.findOneAndReplace(
email: email, {
}, { email: email
},
{
email, email,
userId: user.id, userId: user.id,
clientPublicKey: clientPublicKey, clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt), serverBInt: bigintConversion.bigintToBuf(server.bInt)
}, { upsert: true, returnNewDocument: false }); },
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
salt: user.salt, salt: user.salt
}); });
} }
); );
@@ -95,27 +88,32 @@ export const login1 = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" }); if (!req.headers["user-agent"])
throw InternalServerError({ message: "User-Agent header is required" });
const { email, clientProof, providerAuthToken } = req.body; const {
body: { email, providerAuthToken, clientProof }
} = await validateRequest(reqValidator.Login2V3, req);
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"); }).select(
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
);
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
if (!user.authMethods.includes(AuthMethod.EMAIL)) { if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({ await validateProviderAuthToken({
email, email,
providerAuthToken, providerAuthToken
}) });
} }
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email }) const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email });
if (!loginSRPDetail) { if (!loginSRPDetail) {
return BadRequestError(Error("Failed to find login details for SRP")) return BadRequestError(Error("Failed to find login details for SRP"));
} }
const server = new jsrp.server(); const server = new jsrp.server();
@@ -123,29 +121,28 @@ export const login2 = async (req: Request, res: Response) => {
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: loginSRPDetail.serverBInt, b: loginSRPDetail.serverBInt
}, },
async () => { async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey); server.setClientPublicKey(loginSRPDetail.clientPublicKey);
// compare server and client shared keys // compare server and client shared keys
if (server.checkClientProof(clientProof)) { if (server.checkClientProof(clientProof)) {
if (user.isMfaEnabled) { if (user.isMfaEnabled) {
// case: user has MFA enabled // case: user has MFA enabled
// generate temporary MFA token // generate temporary MFA token
const token = createToken({ const token = createToken({
payload: { payload: {
userId: user._id.toString(), userId: user._id.toString()
}, },
expiresIn: await getJwtMfaLifetime(), expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret(), secret: await getJwtMfaSecret()
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
@@ -154,27 +151,27 @@ export const login2 = async (req: Request, res: Response) => {
subjectLine: "Infisical MFA code", subjectLine: "Infisical MFA code",
recipients: [user.email], recipients: [user.email],
substitutions: { substitutions: {
code, code
}, }
}); });
return res.status(200).send({ return res.status(200).send({
mfaEnabled: true, mfaEnabled: true,
token, token
}); });
} }
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
@@ -182,7 +179,7 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: await getHttpsEnabled(), secure: await getHttpsEnabled()
}); });
// case: user does not have MFA enablgged // case: user does not have MFA enablgged
@@ -208,36 +205,33 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey, publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey, encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag, tag: user.tag
} };
if ( if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
user?.protectedKey &&
user?.protectedKeyIV &&
user?.protectedKeyTag
) {
response.protectedKey = user.protectedKey; response.protectedKey = user.protectedKey;
response.protectedKeyIV = user.protectedKeyIV response.protectedKeyIV = user.protectedKeyIV;
response.protectedKeyTag = user.protectedKeyTag; response.protectedKeyTag = user.protectedKeyTag;
} }
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id, userId: user._id
}); });
loginAction && await EELogService.createLog({ loginAction &&
(await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]), channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP, ipAddress: req.realIP
}); }));
return res.status(200).send(response); return res.status(200).send(response);
} }
return res.status(400).send({ return res.status(400).send({
message: "Failed to authenticate. Try again?", message: "Failed to authenticate. Try again?"
}); });
} }
); );
+228 -60
View File
@@ -9,8 +9,16 @@ import { getAllImportedSecrets } from "../../services/SecretImportService";
import { Folder, IServiceTokenData } from "../../models"; import { Folder, IServiceTokenData } from "../../models";
import { getFolderByPath } from "../../services/FolderService"; import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { requireWorkspaceAuth } from "../../middleware"; import { validateRequest } from "../../helpers/validation";
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables"; import * as reqValidator from "../../validation/secrets";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability";
import { validateServiceTokenDataClientForWorkspace } from "../../validation";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
/** /**
* Return secrets for workspace with id [workspaceId] and environment * Return secrets for workspace with id [workspaceId] and environment
@@ -19,30 +27,38 @@ import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
* @param res * @param res
*/ */
export const getSecretsRaw = async (req: Request, res: Response) => { export const getSecretsRaw = async (req: Request, res: Response) => {
let workspaceId = req.query.workspaceId as string; let {
let environment = req.query.environment as string; query: { secretPath, environment, workspaceId, include_imports: includeImports, folderId }
let secretPath = req.query.secretPath as string; } = await validateRequest(reqValidator.GetSecretsRawV3, req);
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string;
// if the service token has single scope, it will get all secrets for that scope by default // if the service token has single scope, it will get all secrets for that scope by default
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData; const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
if (serviceTokenDetails && serviceTokenDetails.scopes.length == 1 && !containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)) { if (
serviceTokenDetails &&
serviceTokenDetails.scopes.length == 1 &&
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
) {
const scope = serviceTokenDetails.scopes[0]; const scope = serviceTokenDetails.scopes[0];
secretPath = scope.secretPath; secretPath = scope.secretPath;
environment = scope.environment; environment = scope.environment;
workspaceId = serviceTokenDetails.workspace.toString(); workspaceId = serviceTokenDetails.workspace.toString();
} else {
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
});
} }
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_READ_SECRETS]
});
}
const secrets = await SecretService.getSecrets({ const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
@@ -56,7 +72,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
}); });
if (includeImports === "true") { if (includeImports) {
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
let folderId = "root"; let folderId = "root";
// if folder exist get it and replace folderid with new one // if folder exist get it and replace folderid with new one
@@ -99,11 +115,26 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const getSecretByNameRaw = async (req: Request, res: Response) => { export const getSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params; const {
const workspaceId = req.query.workspaceId as string; query: { secretPath, environment, workspaceId, type },
const environment = req.query.environment as string; params: { secretName }
const secretPath = req.query.secretPath as string; } = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
const type = req.query.type as "shared" | "personal" | undefined;
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_READ_SECRETS]
});
}
const secret = await SecretService.getSecret({ const secret = await SecretService.getSecret({
secretName, secretName,
@@ -132,8 +163,26 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const createSecretRaw = async (req: Request, res: Response) => { export const createSecretRaw = async (req: Request, res: Response) => {
const { secretName } = req.params; const {
const { workspaceId, environment, type, secretValue, secretComment, secretPath = "/" } = req.body; params: { secretName },
body: { secretPath, environment, workspaceId, type, secretValue, secretComment }
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_WRITE_SECRETS]
});
}
const key = await BotService.getWorkspaceKeyWithBot({ const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
@@ -197,8 +246,26 @@ export const createSecretRaw = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const updateSecretByNameRaw = async (req: Request, res: Response) => { export const updateSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params; const {
const { workspaceId, environment, type, secretValue, secretPath = "/" } = req.body; params: { secretName },
body: { secretValue, environment, secretPath, type, workspaceId }
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_WRITE_SECRETS]
});
}
const key = await BotService.getWorkspaceKeyWithBot({ const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
@@ -211,7 +278,7 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
const secret = await SecretService.updateSecret({ const secret = await SecretService.updateSecret({
secretName, secretName,
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
authData: req.authData, authData: req.authData,
@@ -243,12 +310,30 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const deleteSecretByNameRaw = async (req: Request, res: Response) => { export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params; const {
const { workspaceId, environment, type, secretPath = "/" } = req.body; params: { secretName },
body: { environment, secretPath, type, workspaceId }
} = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req);
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_WRITE_SECRETS]
});
}
const { secret } = await SecretService.deleteSecret({ const { secret } = await SecretService.deleteSecret({
secretName, secretName,
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
authData: req.authData, authData: req.authData,
@@ -282,11 +367,25 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const getSecrets = async (req: Request, res: Response) => { export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string; const {
const environment = req.query.environment as string; query: { secretPath, environment, workspaceId, include_imports: includeImports, folderId }
const secretPath = req.query.secretPath as string; } = await validateRequest(reqValidator.GetSecretsV3, req);
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string; if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_READ_SECRETS]
});
}
const secrets = await SecretService.getSecrets({ const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
@@ -296,7 +395,7 @@ export const getSecrets = async (req: Request, res: Response) => {
authData: req.authData authData: req.authData
}); });
if (includeImports === "true") { if (includeImports) {
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
let folderId = "root"; let folderId = "root";
// if folder exist get it and replace folderid with new one // if folder exist get it and replace folderid with new one
@@ -325,11 +424,26 @@ export const getSecrets = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const getSecretByName = async (req: Request, res: Response) => { export const getSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params; const {
const workspaceId = req.query.workspaceId as string; query: { secretPath, environment, workspaceId, type },
const environment = req.query.environment as string; params: { secretName }
const secretPath = req.query.secretPath as string; } = await validateRequest(reqValidator.GetSecretByNameV3, req);
const type = req.query.type as "shared" | "personal" | undefined;
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_READ_SECRETS]
});
}
const secret = await SecretService.getSecret({ const secret = await SecretService.getSecret({
secretName, secretName,
@@ -351,23 +465,41 @@ export const getSecretByName = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const createSecret = async (req: Request, res: Response) => { export const createSecret = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { const {
body: {
workspaceId, workspaceId,
secretPath,
environment, environment,
metadata,
type, type,
secretKeyCiphertext,
secretKeyIV, secretKeyIV,
secretKeyTag, secretKeyTag,
secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag, secretCommentTag,
secretPath = "/", secretKeyCiphertext,
metadata secretValueCiphertext,
} = req.body; secretCommentCiphertext
},
params: { secretName }
} = await validateRequest(reqValidator.CreateSecretV3, req);
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_WRITE_SECRETS]
});
}
const secret = await SecretService.createSecret({ const secret = await SecretService.createSecret({
secretName, secretName,
@@ -410,20 +542,38 @@ export const createSecret = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const updateSecretByName = async (req: Request, res: Response) => { export const updateSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { const {
workspaceId, body: {
environment,
type,
secretValueCiphertext, secretValueCiphertext,
secretValueIV,
secretValueTag, secretValueTag,
secretPath = "/" secretValueIV,
} = req.body; type,
environment,
secretPath,
workspaceId
},
params: { secretName }
} = await validateRequest(reqValidator.UpdateSecretByNameV3, req);
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_WRITE_SECRETS]
});
}
const secret = await SecretService.updateSecret({ const secret = await SecretService.updateSecret({
secretName, secretName,
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
authData: req.authData, authData: req.authData,
@@ -452,12 +602,30 @@ export const updateSecretByName = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const deleteSecretByName = async (req: Request, res: Response) => { export const deleteSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params; const {
const { workspaceId, environment, type, secretPath = "/" } = req.body; body: { type, environment, secretPath, workspaceId },
params: { secretName }
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
} else {
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: [PERMISSION_WRITE_SECRETS]
});
}
const { secret } = await SecretService.deleteSecret({ const { secret } = await SecretService.deleteSecret({
secretName, secretName,
workspaceId, workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
authData: req.authData, authData: req.authData,
+52 -56
View File
@@ -3,9 +3,7 @@ import { Request, Response } from "express";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { MembershipOrg, User } from "../../models"; import { MembershipOrg, User } from "../../models";
import { completeAccount } from "../../helpers/user"; import { completeAccount } from "../../helpers/user";
import { import { initializeDefaultOrg } from "../../helpers/signup";
initializeDefaultOrg,
} from "../../helpers/signup";
import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth"; import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
import { ACCEPTED, INVITED } from "../../variables"; import { ACCEPTED, INVITED } from "../../variables";
import { standardRequest } from "../../config/request"; import { standardRequest } from "../../config/request";
@@ -13,6 +11,8 @@ import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../confi
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { TelemetryService } from "../../services"; import { TelemetryService } from "../../services";
import { AuthMethod } from "../../models"; import { AuthMethod } from "../../models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
/** /**
* Complete setting up user by adding their personal and auth information as part of the * Complete setting up user by adding their personal and auth information as part of the
@@ -22,41 +22,27 @@ import { AuthMethod } from "../../models";
* @returns * @returns
*/ */
export const completeAccountSignup = async (req: Request, res: Response) => { export const completeAccountSignup = async (req: Request, res: Response) => {
let user, token, refreshToken; let user, token;
try { try {
const { const {
body: {
email, email,
firstName, publicKey,
salt,
lastName, lastName,
verifier,
firstName,
protectedKey, protectedKey,
protectedKeyIV, protectedKeyIV,
protectedKeyTag, protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName, organizationName,
providerAuthToken, providerAuthToken,
attributionSource, attributionSource,
}: { encryptedPrivateKey,
email: string; encryptedPrivateKeyIV,
firstName: string; encryptedPrivateKeyTag
lastName: string; }
protectedKey: string; } = await validateRequest(reqValidator.CompletedAccountSignupV3, req);
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
organizationName: string;
providerAuthToken?: string;
attributionSource?: string;
} = req.body;
user = await User.findOne({ email }); user = await User.findOne({ email });
@@ -64,7 +50,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// case 1: user doesn't exist. // case 1: user doesn't exist.
// case 2: user has already completed account // case 2: user has already completed account
return res.status(403).send({ return res.status(403).send({
error: "Failed to complete account for complete user", error: "Failed to complete account for complete user"
}); });
} }
@@ -74,17 +60,21 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
providerAuthToken providerAuthToken
}); });
} else { } else {
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers["authorization"]?.split(" ", 2) ?? [null, null] const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>(
req.headers["authorization"]?.split(" ", 2)
) ?? [null, null];
if (AUTH_TOKEN_TYPE === null) { if (AUTH_TOKEN_TYPE === null) {
throw BadRequestError({ message: "Missing Authorization Header in the request header." }); throw BadRequestError({ message: "Missing Authorization Header in the request header." });
} }
if (AUTH_TOKEN_TYPE.toLowerCase() !== "bearer") { if (AUTH_TOKEN_TYPE.toLowerCase() !== "bearer") {
throw BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` }) throw BadRequestError({
message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`
});
} }
if (AUTH_TOKEN_VALUE === null) { if (AUTH_TOKEN_VALUE === null) {
throw BadRequestError({ throw BadRequestError({
message: "Missing Authorization Body in the request header", message: "Missing Authorization Body in the request header"
}) });
} }
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
@@ -110,19 +100,21 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier, verifier
}); });
if (!user) if (!user) throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) => [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod)); const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) =>
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod)
);
if (!hasSamlEnabled) { // TODO: modify this part if (!hasSamlEnabled) {
// TODO: modify this part
// initialize default organization and workspace // initialize default organization and workspace
await initializeDefaultOrg({ await initializeDefaultOrg({
organizationName, organizationName,
user, user
}); });
} }
@@ -131,11 +123,11 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
await MembershipOrg.updateMany( await MembershipOrg.updateMany(
{ {
inviteEmail: email, inviteEmail: email,
status: INVITED, status: INVITED
}, },
{ {
user, user,
status: ACCEPTED, status: ACCEPTED
} }
); );
@@ -143,24 +135,28 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
token = tokens.token; token = tokens.token;
// sending a welcome email to new users // sending a welcome email to new users
if (await getLoopsApiKey()) { if (await getLoopsApiKey()) {
await standardRequest.post("https://app.loops.so/api/v1/events/send", { await standardRequest.post(
"email": email, "https://app.loops.so/api/v1/events/send",
"eventName": "Sign Up", {
"firstName": firstName, email: email,
"lastName": lastName, eventName: "Sign Up",
}, { firstName: firstName,
headers: { lastName: lastName
"Accept": "application/json",
"Authorization": "Bearer " + (await getLoopsApiKey()),
}, },
}); {
headers: {
Accept: "application/json",
Authorization: "Bearer " + (await getLoopsApiKey())
}
}
);
} }
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
@@ -168,7 +164,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: await getHttpsEnabled(), secure: await getHttpsEnabled()
}); });
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -179,20 +175,20 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
properties: { properties: {
email, email,
...(attributionSource ? { attributionSource } : {}) ...(attributionSource ? { attributionSource } : {})
}, }
}); });
} }
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
message: "Failed to complete account setup", message: "Failed to complete account setup"
}); });
} }
return res.status(200).send({ return res.status(200).send({
message: "Successfully set up account", message: "Successfully set up account",
user, user,
token, token
}); });
}; };
@@ -1,7 +1,11 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { validateRequest } from "../../helpers/validation";
import { Secret } from "../../models"; import { Secret } from "../../models";
import { SecretService } from "../../services"; import { SecretService } from "../../services";
import { getUserProjectPermissions } from "../../services/ProjectRoleService";
import { UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/workspace";
/** /**
* Return whether or not all secrets in workspace with id [workspaceId] * Return whether or not all secrets in workspace with id [workspaceId]
@@ -11,32 +15,44 @@ import { SecretService } from"../../services";
* @returns * @returns
*/ */
export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response) => { export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
const secretsWithoutBlindIndex = await Secret.countDocuments({ const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
secretBlindIndex: { secretBlindIndex: {
$exists: false, $exists: false
}, }
}); });
return res.status(200).send(secretsWithoutBlindIndex === 0); return res.status(200).send(secretsWithoutBlindIndex === 0);
} };
/** /**
* Get all secrets for workspace with id [workspaceId] * Get all secrets for workspace with id [workspaceId]
*/ */
export const getWorkspaceSecrets = async (req: Request, res: Response) => { export const getWorkspaceSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
const secrets = await Secret.find({ const secrets = await Secret.find({
workspace: new Types.ObjectId (workspaceId), workspace: new Types.ObjectId(workspaceId)
}); });
return res.status(200).send({ return res.status(200).send({
secrets, secrets
}); });
} };
/** /**
* Update blind indices for secrets in workspace with id [workspaceId] * Update blind indices for secrets in workspace with id [workspaceId]
@@ -44,47 +60,44 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const nameWorkspaceSecrets = async (req: Request, res: Response) => { export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
interface SecretToUpdate {
secretName: string;
_id: string;
}
const { workspaceId } = req.params;
const { const {
secretsToUpdate, params: { workspaceId },
}: { body: { secretsToUpdate }
secretsToUpdate: SecretToUpdate[]; } = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req);
} = req.body;
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
// update secret blind indices // update secret blind indices
const operations = await Promise.all( const operations = await Promise.all(
secretsToUpdate.map(async (secretToUpdate: SecretToUpdate) => { secretsToUpdate.map(async (secretToUpdate) => {
const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({ const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
secretName: secretToUpdate.secretName, secretName: secretToUpdate.secretName,
salt, salt
}); });
return ({ return {
updateOne: { updateOne: {
filter: { filter: {
_id: new Types.ObjectId(secretToUpdate._id), _id: new Types.ObjectId(secretToUpdate._id)
}, },
update: { update: {
secretBlindIndex, secretBlindIndex
}, }
}, }
}); };
}) })
); );
await Secret.bulkWrite(operations); await Secret.bulkWrite(operations);
return res.status(200).send({ return res.status(200).send({
message: "Successfully named workspace secrets", message: "Successfully named workspace secrets"
}); });
} };
@@ -1,30 +1,32 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Action } from "../../models"; import { Action } from "../../models";
import { ActionNotFoundError } from "../../../utils/errors"; import { ActionNotFoundError } from "../../../utils/errors";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/action";
export const getAction = async (req: Request, res: Response) => { export const getAction = async (req: Request, res: Response) => {
let action; let action;
try { try {
const { actionId } = req.params; const {
params: { actionId }
} = await validateRequest(reqValidator.GetActionV1, req);
action = await Action action = await Action.findById(actionId).populate([
.findById(actionId)
.populate([
"payload.secretVersions.oldSecretVersion", "payload.secretVersions.oldSecretVersion",
"payload.secretVersions.newSecretVersion", "payload.secretVersions.newSecretVersion"
]); ]);
if (!action) throw ActionNotFoundError({ if (!action)
message: "Failed to find action", throw ActionNotFoundError({
message: "Failed to find action"
}); });
} catch (err) { } catch (err) {
throw ActionNotFoundError({ throw ActionNotFoundError({
message: "Failed to find action", message: "Failed to find action"
}); });
} }
return res.status(200).send({ return res.status(200).send({
action, action
}); });
} };
@@ -2,6 +2,8 @@ import { Request, Response } from "express";
import { EELicenseService } from "../../services"; import { EELicenseService } from "../../services";
import { getLicenseServerUrl } from "../../../config"; import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from "../../../config/request"; import { licenseServerKeyRequest } from "../../../config/request";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/cloudProducts";
/** /**
* Return available cloud product information. * Return available cloud product information.
@@ -11,7 +13,9 @@ import { licenseServerKeyRequest } from "../../../config/request";
* @returns * @returns
*/ */
export const getCloudProducts = async (req: Request, res: Response) => { export const getCloudProducts = async (req: Request, res: Response) => {
const billingCycle = req.query["billing-cycle"] as string; const {
query: { "billing-cycle": billingCycle }
} = await validateRequest(reqValidator.GetCloudProductsV1, req);
if (EELicenseService.instanceType === "cloud") { if (EELicenseService.instanceType === "cloud") {
const { data } = await licenseServerKeyRequest.get( const { data } = await licenseServerKeyRequest.get(
@@ -23,6 +27,6 @@ export const getCloudProducts = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
head: [], head: [],
rows: [], rows: []
}); });
} };
@@ -3,30 +3,60 @@ import { Request, Response } from "express";
import { getLicenseServerUrl } from "../../../config"; import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from "../../../config/request"; import { licenseServerKeyRequest } from "../../../config/request";
import { EELicenseService } from "../../services"; import { EELicenseService } from "../../services";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/organization";
import {
OrgPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
import { Organization } from "../../../models";
import { OrganizationNotFoundError } from "../../../utils/errors";
export const getOrganizationPlansTable = async (req: Request, res: Response) => { export const getOrganizationPlansTable = async (req: Request, res: Response) => {
const billingCycle = req.query.billingCycle as string; const {
query: { billingCycle },
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlansTablev1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const { data } = await licenseServerKeyRequest.get( const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` `${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
/** /**
* Return the organization current plan's feature set * Return the organization current plan's feature set
*/ */
export const getOrganizationPlan = async (req: Request, res: Response) => { export const getOrganizationPlan = async (req: Request, res: Response) => {
const { organizationId } = req.params; const {
const workspaceId = req.query.workspaceId as string; query: { workspaceId },
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlanv1, req);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId), new Types.ObjectId(workspaceId)); const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const plan = await EELicenseService.getPlan(
new Types.ObjectId(organizationId),
new Types.ObjectId(workspaceId)
);
return res.status(200).send({ return res.status(200).send({
plan, plan
}); });
} };
/** /**
* Return checkout url for pro trial * Return checkout url for pro trial
@@ -35,11 +65,34 @@ export const getOrganizationPlan = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const startOrganizationTrial = async (req: Request, res: Response) => { export const startOrganizationTrial = async (req: Request, res: Response) => {
const { organizationId } = req.params; const {
const { success_url } = req.body; params: { organizationId },
body: { success_url }
} = await validateRequest(reqValidator.StartOrgTrailv1, req);
const { data: { url } } = await licenseServerKeyRequest.post( const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/session/trial`, ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Billing
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/session/trial`,
{ {
success_url success_url
} }
@@ -50,7 +103,7 @@ export const startOrganizationTrial = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
url url
}); });
} };
/** /**
* Return the organization's current plan's billing info * Return the organization's current plan's billing info
@@ -59,12 +112,31 @@ export const startOrganizationTrial = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getOrganizationPlanBillingInfo = async (req: Request, res: Response) => { export const getOrganizationPlanBillingInfo = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.get( const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/billing` `${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/cloud-plan/billing`
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
/** /**
* Return the organization's current plan's feature table * Return the organization's current plan's feature table
@@ -73,29 +145,82 @@ export const getOrganizationPlanBillingInfo = async (req: Request, res: Response
* @returns * @returns
*/ */
export const getOrganizationPlanTable = async (req: Request, res: Response) => { export const getOrganizationPlanTable = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlanTablev1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.get( const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/table` `${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/cloud-plan/table`
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
export const getOrganizationBillingDetails = async (req: Request, res: Response) => { export const getOrganizationBillingDetails = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgBillingDetailsv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.get( const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details` `${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details`
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
export const updateOrganizationBillingDetails = async (req: Request, res: Response) => { export const updateOrganizationBillingDetails = async (req: Request, res: Response) => {
const { const {
name, params: { organizationId },
email body: { name, email }
} = req.body; } = await validateRequest(reqValidator.UpdateOrgBillingDetailsv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.patch( const { data } = await licenseServerKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details`, `${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details`,
{ {
...(name ? { name } : {}), ...(name ? { name } : {}),
...(email ? { email } : {}) ...(email ? { email } : {})
@@ -103,40 +228,78 @@ export const updateOrganizationBillingDetails = async (req: Request, res: Respon
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
/** /**
* Return the organization's payment methods on file * Return the organization's payment methods on file
*/ */
export const getOrganizationPmtMethods = async (req: Request, res: Response) => { export const getOrganizationPmtMethods = async (req: Request, res: Response) => {
const { data: { pmtMethods } } = await licenseServerKeyRequest.get( const {
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods` params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPmtMethodsv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { pmtMethods }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`
); );
return res.status(200).send(pmtMethods); return res.status(200).send(pmtMethods);
} };
/** /**
* Return URL to add payment method for organization * Return URL to add payment method for organization
*/ */
export const addOrganizationPmtMethod = async (req: Request, res: Response) => { export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
const { const {
success_url, params: { organizationId },
cancel_url, body: { success_url, cancel_url }
} = req.body; } = await validateRequest(reqValidator.CreateOrgPmtMethodv1, req);
const { data: { url } } = await licenseServerKeyRequest.post( const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`, ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`,
{ {
success_url, success_url,
cancel_url, cancel_url
} }
); );
return res.status(200).send({ return res.status(200).send({
url, url
}); });
} };
/** /**
* Delete payment method with id [pmtMethodId] for organization * Delete payment method with id [pmtMethodId] for organization
@@ -145,37 +308,90 @@ export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => { export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
const { pmtMethodId } = req.params; const {
params: { organizationId, pmtMethodId }
} = await validateRequest(reqValidator.DelOrgPmtMethodv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.delete( const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods/${pmtMethodId}`, `${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods/${pmtMethodId}`
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
/** /**
* Return the organization's tax ids on file * Return the organization's tax ids on file
*/ */
export const getOrganizationTaxIds = async (req: Request, res: Response) => { export const getOrganizationTaxIds = async (req: Request, res: Response) => {
const { data: { tax_ids } } = await licenseServerKeyRequest.get( const {
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids` params: { organizationId }
} = await validateRequest(reqValidator.GetOrgTaxIdsv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { tax_ids }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/tax-ids`
); );
return res.status(200).send(tax_ids); return res.status(200).send(tax_ids);
} };
/** /**
* Add tax id to organization * Add tax id to organization
*/ */
export const addOrganizationTaxId = async (req: Request, res: Response) => { export const addOrganizationTaxId = async (req: Request, res: Response) => {
const { const {
type, params: { organizationId },
value body: { type, value }
} = req.body; } = await validateRequest(reqValidator.CreateOrgTaxId, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.post( const { data } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids`, `${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/tax-ids`,
{ {
type, type,
value value
@@ -183,7 +399,7 @@ export const addOrganizationTaxId = async (req: Request, res: Response) => {
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
/** /**
* Delete tax id with id [taxId] from organization tax ids on file * Delete tax id with id [taxId] from organization tax ids on file
@@ -192,14 +408,31 @@ export const addOrganizationTaxId = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteOrganizationTaxId = async (req: Request, res: Response) => { export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
const { taxId } = req.params; const {
params: { organizationId, taxId }
} = await validateRequest(reqValidator.DelOrgTaxIdv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.delete( const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids/${taxId}`, `${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/tax-ids/${taxId}`
); );
return res.status(200).send(data); return res.status(200).send(data);
} };
/** /**
* Return organization's invoices on file * Return organization's invoices on file
@@ -208,12 +441,33 @@ export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getOrganizationInvoices = async (req: Request, res: Response) => { export const getOrganizationInvoices = async (req: Request, res: Response) => {
const { data: { invoices } } = await licenseServerKeyRequest.get( const {
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/invoices` params: { organizationId }
} = await validateRequest(reqValidator.GetOrgInvoicesv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { invoices }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/invoices`
); );
return res.status(200).send(invoices); return res.status(200).send(invoices);
} };
/** /**
* Return organization's licenses on file * Return organization's licenses on file
@@ -222,9 +476,30 @@ export const getOrganizationInvoices = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getOrganizationLicenses = async (req: Request, res: Response) => { export const getOrganizationLicenses = async (req: Request, res: Response) => {
const { data: { licenses } } = await licenseServerKeyRequest.get( const {
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/licenses` params: { organizationId }
} = await validateRequest(reqValidator.GetOrgLicencesv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { licenses }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/licenses`
); );
return res.status(200).send(licenses); return res.status(200).send(licenses);
} };
@@ -1,7 +1,17 @@
import { ForbiddenError, subject } from "@casl/ability";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Secret } from "../../../models"; import { validateRequest } from "../../../helpers/validation";
import { Folder, Secret } from "../../../models";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../../services/ProjectRoleService";
import { BadRequestError } from "../../../utils/errors";
import * as reqValidator from "../../../validation";
import { SecretVersion } from "../../models"; import { SecretVersion } from "../../models";
import { EESecretService } from "../../services"; import { EESecretService } from "../../services";
import { getFolderWithPathFromId } from "../../../services/FolderService";
/** /**
* Return secret versions for secret with id [secretId] * Return secret versions for secret with id [secretId]
@@ -54,10 +64,21 @@ export const getSecretVersions = async (req: Request, res: Response) => {
} }
} }
*/ */
const { secretId } = req.params; const {
params: { secretId },
query: { offset, limit }
} = await validateRequest(reqValidator.GetSecretVersionsV1, req);
const offset: number = parseInt(req.query.offset as string); const secret = await Secret.findById(secretId);
const limit: number = parseInt(req.query.limit as string); if (!secret) {
throw BadRequestError({ message: "Failed to find secret" });
}
const { permission } = await getUserProjectPermissions(req.user._id, secret.workspace.toString());
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const secretVersions = await SecretVersion.find({ const secretVersions = await SecretVersion.find({
secret: secretId secret: secretId
@@ -126,8 +147,24 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
} }
} }
*/ */
const { secretId } = req.params;
const { version } = req.body; const {
params: { secretId },
body: { version }
} = await validateRequest(reqValidator.RollbackSecretVersionV1, req);
const toBeUpdatedSec = await Secret.findById(secretId);
if (!toBeUpdatedSec) {
throw BadRequestError({ message: "Failed to find secret" });
}
const { permission } = await getUserProjectPermissions(
req.user._id,
toBeUpdatedSec.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback
);
// validate secret version // validate secret version
const oldSecretVersion = await SecretVersion.findOne({ const oldSecretVersion = await SecretVersion.findOne({
@@ -154,6 +191,15 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
keyEncoding keyEncoding
} = oldSecretVersion; } = oldSecretVersion;
let secretPath = "/";
const folders = await Folder.findOne({ workspace, environment });
if (folders)
secretPath = getFolderWithPathFromId(folders.nodes, folder || "root")?.folderPath || "/";
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment: toBeUpdatedSec.environment, secretPath })
);
// update secret // update secret
const secret = await Secret.findByIdAndUpdate( const secret = await Secret.findByIdAndUpdate(
secretId, secretId,
@@ -1,9 +1,13 @@
import { ForbiddenError } from "@casl/ability";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { validateRequest } from "../../../helpers/validation";
import { import {
ISecretVersion, ProjectPermissionActions,
SecretSnapshot, ProjectPermissionSub,
TFolderRootVersionSchema, getUserProjectPermissions
} from "../../models"; } from "../../../services/ProjectRoleService";
import * as reqValidator from "../../../validation/secretSnapshot";
import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models";
/** /**
* Return secret snapshot with id [secretSnapshotId] * Return secret snapshot with id [secretSnapshotId]
@@ -12,7 +16,9 @@ import {
* @returns * @returns
*/ */
export const getSecretSnapshot = async (req: Request, res: Response) => { export const getSecretSnapshot = async (req: Request, res: Response) => {
const { secretSnapshotId } = req.params; const {
params: { secretSnapshotId }
} = await validateRequest(reqValidator.GetSecretSnapshotV1, req);
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId) const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId)
.lean() .lean()
@@ -20,26 +26,36 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
path: "secretVersions", path: "secretVersions",
populate: { populate: {
path: "tags", path: "tags",
model: "Tag", model: "Tag"
}, }
}) })
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion"); .populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
const { permission } = await getUserProjectPermissions(
req.user._id,
secretSnapshot.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const folderId = secretSnapshot.folderId; const folderId = secretSnapshot.folderId;
// to show only the folder required secrets // to show only the folder required secrets
secretSnapshot.secretVersions = secretSnapshot.secretVersions.filter( secretSnapshot.secretVersions = secretSnapshot.secretVersions.filter(
({ folder }) => folder === folderId ({ folder }) => folder === folderId
); );
secretSnapshot.folderVersion = secretSnapshot.folderVersion = secretSnapshot?.folderVersion?.nodes?.children?.map(
secretSnapshot?.folderVersion?.nodes?.children?.map(({ id, name }) => ({ ({ id, name }) => ({
id, id,
name, name
})) as any; })
) as any;
return res.status(200).send({ return res.status(200).send({
secretSnapshot, secretSnapshot
}); });
}; };
+58 -39
View File
@@ -2,16 +2,20 @@ import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { BotOrgService } from "../../../services"; import { BotOrgService } from "../../../services";
import { SSOConfig } from "../../models"; import { SSOConfig } from "../../models";
import { import { AuthMethod, MembershipOrg, User } from "../../../models";
AuthMethod,
MembershipOrg,
User
} from "../../../models";
import { getSSOConfigHelper } from "../../helpers/organizations"; import { getSSOConfigHelper } from "../../helpers/organizations";
import { client } from "../../../config"; import { client } from "../../../config";
import { ResourceNotFoundError } from "../../../utils/errors"; import { ResourceNotFoundError } from "../../../utils/errors";
import { getSiteURL } from "../../../config"; import { getSiteURL } from "../../../config";
import { EELicenseService } from "../../services"; import { EELicenseService } from "../../services";
import * as reqValidator from "../../../validation/sso";
import { validateRequest } from "../../../helpers/validation";
import {
OrgPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
/** /**
* Redirect user to appropriate SSO endpoint after successful authentication * Redirect user to appropriate SSO endpoint after successful authentication
@@ -22,11 +26,15 @@ import { EELicenseService } from "../../services";
*/ */
export const redirectSSO = async (req: Request, res: Response) => { export const redirectSSO = async (req: Request, res: Response) => {
if (req.isUserCompleted) { if (req.isUserCompleted) {
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`); return res.redirect(
`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`
);
} }
return res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`); return res.redirect(
} `${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`
);
};
/** /**
* Return organization SAML SSO configuration * Return organization SAML SSO configuration
@@ -35,14 +43,22 @@ export const redirectSSO = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getSSOConfig = async (req: Request, res: Response) => { export const getSSOConfig = async (req: Request, res: Response) => {
const organizationId = req.query.organizationId as string; const {
query: { organizationId }
} = await validateRequest(reqValidator.GetSsoConfigv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Sso
);
const data = await getSSOConfigHelper({ const data = await getSSOConfigHelper({
organizationId: new Types.ObjectId(organizationId) organizationId: new Types.ObjectId(organizationId)
}); });
return res.status(200).send(data); return res.status(200).send(data);
} };
/** /**
* Update organization SAML SSO configuration * Update organization SAML SSO configuration
@@ -52,18 +68,21 @@ export const getSSOConfig = async (req: Request, res: Response) => {
*/ */
export const updateSSOConfig = async (req: Request, res: Response) => { export const updateSSOConfig = async (req: Request, res: Response) => {
const { const {
organizationId, body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
authProvider, } = await validateRequest(reqValidator.UpdateSsoConfigv1, req);
isActive,
entryPoint, const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
issuer, ForbiddenError.from(permission).throwUnlessCan(
cert, OrgPermissionActions.Edit,
} = req.body; OrgPermissionSubjects.Sso
);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (!plan.samlSSO) return res.status(400).send({ if (!plan.samlSSO)
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." return res.status(400).send({
message:
"Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
}); });
interface PatchUpdate { interface PatchUpdate {
@@ -90,9 +109,7 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
update.isActive = isActive; update.isActive = isActive;
} }
const key = await BotOrgService.getSymmetricKey( const key = await BotOrgService.getSymmetricKey(new Types.ObjectId(organizationId));
new Types.ObjectId(organizationId)
);
if (entryPoint) { if (entryPoint) {
const { const {
@@ -140,7 +157,8 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
} }
); );
if (!ssoConfig) throw ResourceNotFoundError({ if (!ssoConfig)
throw ResourceNotFoundError({
message: "Failed to find SSO config to update" message: "Failed to find SSO config to update"
}); });
@@ -157,7 +175,7 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
} }
}, },
{ {
authMethods: [ssoConfig.authProvider], authMethods: [ssoConfig.authProvider]
} }
); );
} else { } else {
@@ -168,14 +186,14 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
} }
}, },
{ {
authMethods: [AuthMethod.EMAIL], authMethods: [AuthMethod.EMAIL]
} }
); );
} }
} }
return res.status(200).send(ssoConfig); return res.status(200).send(ssoConfig);
} };
/** /**
* Create organization SAML SSO configuration * Create organization SAML SSO configuration
@@ -185,23 +203,24 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
*/ */
export const createSSOConfig = async (req: Request, res: Response) => { export const createSSOConfig = async (req: Request, res: Response) => {
const { const {
organizationId, body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
authProvider, } = await validateRequest(reqValidator.CreateSsoConfigv1, req);
isActive,
entryPoint, const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
issuer, ForbiddenError.from(permission).throwUnlessCan(
cert OrgPermissionActions.Create,
} = req.body; OrgPermissionSubjects.Sso
);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (!plan.samlSSO) return res.status(400).send({ if (!plan.samlSSO)
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration." return res.status(400).send({
message:
"Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
}); });
const key = await BotOrgService.getSymmetricKey( const key = await BotOrgService.getSymmetricKey(new Types.ObjectId(organizationId));
new Types.ObjectId(organizationId)
);
const { const {
ciphertext: encryptedEntryPoint, ciphertext: encryptedEntryPoint,
@@ -237,4 +256,4 @@ export const createSSOConfig = async (req: Request, res: Response) => {
}).save(); }).save();
return res.status(200).send(ssoConfig); return res.status(200).send(ssoConfig);
} };
@@ -22,16 +22,32 @@ import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
import { searchByFolderId } from "../../../services/FolderService"; import { searchByFolderId } from "../../../services/FolderService";
import { EEAuditLogService, EELicenseService } from "../../services"; import { EEAuditLogService, EELicenseService } from "../../services";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
import { validateRequest } from "../../../helpers/validation";
import {
AddWorkspaceTrustedIpV1,
DeleteWorkspaceTrustedIpV1,
GetWorkspaceAuditLogActorFilterOptsV1,
GetWorkspaceAuditLogsV1,
GetWorkspaceLogsV1,
GetWorkspaceSecretSnapshotsCountV1,
GetWorkspaceSecretSnapshotsV1,
GetWorkspaceTrustedIpsV1,
RollbackWorkspaceSecretSnapshotV1,
UpdateWorkspaceTrustedIpV1
} from "../../../validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
/** /**
* Return secret snapshots for workspace with id [workspaceId] * Return secret snapshots for workspace with id [workspaceId]
* @param req * @param req
* @param res * @param res
*/ */
export const getWorkspaceSecretSnapshots = async ( export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) => {
req: Request,
res: Response
) => {
/* /*
#swagger.summary = 'Return project secret snapshot ids' #swagger.summary = 'Return project secret snapshot ids'
#swagger.description = 'Return project secret snapshots ids' #swagger.description = 'Return project secret snapshots ids'
@@ -77,23 +93,28 @@ export const getWorkspaceSecretSnapshots = async (
} }
} }
*/ */
const { workspaceId } = req.params; const {
const { environment, folderId } = req.query; params: { workspaceId },
query: { environment, folderId, offset, limit }
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
const offset: number = parseInt(req.query.offset as string); const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const limit: number = parseInt(req.query.limit as string); ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const secretSnapshots = await SecretSnapshot.find({ const secretSnapshots = await SecretSnapshot.find({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
folderId: folderId || "root", folderId: folderId || "root"
}) })
.sort({ createdAt: -1 }) .sort({ createdAt: -1 })
.skip(offset) .skip(offset)
.limit(limit); .limit(limit);
return res.status(200).send({ return res.status(200).send({
secretSnapshots, secretSnapshots
}); });
}; };
@@ -102,21 +123,26 @@ export const getWorkspaceSecretSnapshots = async (
* @param req * @param req
* @param res * @param res
*/ */
export const getWorkspaceSecretSnapshotsCount = async ( export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Response) => {
req: Request, const {
res: Response params: { workspaceId },
) => { query: { environment, folderId }
const { workspaceId } = req.params; } = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
const { environment, folderId } = req.query;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const count = await SecretSnapshot.countDocuments({ const count = await SecretSnapshot.countDocuments({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
folderId: folderId || "root", folderId: folderId || "root"
}); });
return res.status(200).send({ return res.status(200).send({
count, count
}); });
}; };
@@ -126,10 +152,7 @@ export const getWorkspaceSecretSnapshotsCount = async (
* @param res * @param res
* @returns * @returns
*/ */
export const rollbackWorkspaceSecretSnapshot = async ( export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => {
req: Request,
res: Response
) => {
/* /*
#swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version.' #swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version.'
#swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.' #swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.'
@@ -181,19 +204,27 @@ export const rollbackWorkspaceSecretSnapshot = async (
} }
*/ */
const { workspaceId } = req.params; const {
const { version, environment, folderId = "root" } = req.body; params: { workspaceId },
body: { folderId, environment, version }
} = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback
);
// validate secret snapshot // validate secret snapshot
const secretSnapshot = await SecretSnapshot.findOne({ const secretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId, workspace: workspaceId,
version, version,
environment, environment,
folderId: folderId, folderId: folderId
}) })
.populate<{ secretVersions: ISecretVersion[] }>({ .populate<{ secretVersions: ISecretVersion[] }>({
path: "secretVersions", path: "secretVersions",
select: "+secretBlindIndex", select: "+secretBlindIndex"
}) })
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion"); .populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
@@ -202,13 +233,13 @@ export const rollbackWorkspaceSecretSnapshot = async (
const snapshotFolderTree = secretSnapshot.folderVersion; const snapshotFolderTree = secretSnapshot.folderVersion;
const latestFolderTree = await Folder.findOne({ const latestFolderTree = await Folder.findOne({
workspace: workspaceId, workspace: workspaceId,
environment, environment
}); });
const latestFolderVersion = await FolderVersion.findOne({ const latestFolderVersion = await FolderVersion.findOne({
environment, environment,
workspace: workspaceId, workspace: workspaceId,
"nodes.id": folderId, "nodes.id": folderId
}).sort({ "nodes.version": -1 }); }).sort({ "nodes.version": -1 });
const oldSecretVersionsObj: Record<string, ISecretVersion> = {}; const oldSecretVersionsObj: Record<string, ISecretVersion> = {};
@@ -222,8 +253,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
// the parent node from current latest one // the parent node from current latest one
// this will be modified according to the snapshot and latest snapshots // this will be modified according to the snapshot and latest snapshots
const newFolderTree = const newFolderTree = latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId);
latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId);
if (newFolderTree) { if (newFolderTree) {
newFolderTree.children = snapshotFolderTree?.nodes?.children || []; newFolderTree.children = snapshotFolderTree?.nodes?.children || [];
@@ -252,43 +282,43 @@ export const rollbackWorkspaceSecretSnapshot = async (
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
folderId: { folderId: {
$in: Object.keys(groupByFolderId), $in: Object.keys(groupByFolderId)
}, }
}, }
}; };
const sortByFolderIdAndVersion: PipelineStage = { const sortByFolderIdAndVersion: PipelineStage = {
$sort: { folderId: 1, version: -1 }, $sort: { folderId: 1, version: -1 }
}; };
const pickLatestVersionOfEachFolder = { const pickLatestVersionOfEachFolder = {
$group: { $group: {
_id: "$folderId", _id: "$folderId",
latestVersion: { $first: "$version" }, latestVersion: { $first: "$version" },
doc: { doc: {
$first: "$$ROOT", $first: "$$ROOT"
}, }
}, }
}; };
const populateSecVersion = { const populateSecVersion = {
$lookup: { $lookup: {
from: SecretVersion.collection.name, from: SecretVersion.collection.name,
localField: "doc.secretVersions", localField: "doc.secretVersions",
foreignField: "_id", foreignField: "_id",
as: "doc.secretVersions", as: "doc.secretVersions"
}, }
}; };
const populateFolderVersion = { const populateFolderVersion = {
$lookup: { $lookup: {
from: FolderVersion.collection.name, from: FolderVersion.collection.name,
localField: "doc.folderVersion", localField: "doc.folderVersion",
foreignField: "_id", foreignField: "_id",
as: "doc.folderVersion", as: "doc.folderVersion"
}, }
}; };
const unwindFolderVerField = { const unwindFolderVerField = {
$unwind: { $unwind: {
path: "$doc.folderVersion", path: "$doc.folderVersion",
preserveNullAndEmptyArrays: true, preserveNullAndEmptyArrays: true
}, }
}; };
const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> = const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> =
await SecretSnapshot.aggregate([ await SecretSnapshot.aggregate([
@@ -297,7 +327,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
pickLatestVersionOfEachFolder, pickLatestVersionOfEachFolder,
populateSecVersion, populateSecVersion,
populateFolderVersion, populateFolderVersion,
unwindFolderVerField, unwindFolderVerField
]); ]);
// recursive snapshotting each level // recursive snapshotting each level
@@ -327,7 +357,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
// TODO: fix any // TODO: fix any
const latestSecretVersionIds = await getLatestSecretVersionIds({ const latestSecretVersionIds = await getLatestSecretVersionIds({
secretIds, secretIds
}); });
// TODO: fix any // TODO: fix any
@@ -335,32 +365,31 @@ export const rollbackWorkspaceSecretSnapshot = async (
await SecretVersion.find( await SecretVersion.find(
{ {
_id: { _id: {
$in: latestSecretVersionIds.map((s) => s.versionId), $in: latestSecretVersionIds.map((s) => s.versionId)
}, }
}, },
"secret version" "secret version"
) )
).reduce( ).reduce(
(accumulator, s) => ({ (accumulator, s) => ({
...accumulator, ...accumulator,
[`${s.secret.toString()}`]: s, [`${s.secret.toString()}`]: s
}), }),
{} {}
); );
const secDelQuery: Record<string, unknown> = { const secDelQuery: Record<string, unknown> = {
workspace: workspaceId, workspace: workspaceId,
environment, environment
// undefined means root thus collect all secrets // undefined means root thus collect all secrets
}; };
if (folderId !== "root" && folderIds.length) if (folderId !== "root" && folderIds.length) secDelQuery.folder = { $in: folderIds };
secDelQuery.folder = { $in: folderIds };
// delete existing secrets // delete existing secrets
await Secret.deleteMany(secDelQuery); await Secret.deleteMany(secDelQuery);
await Folder.deleteOne({ await Folder.deleteOne({
workspace: workspaceId, workspace: workspaceId,
environment, environment
}); });
// add secrets // add secrets
@@ -382,7 +411,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
createdAt, createdAt,
algorithm, algorithm,
keyEncoding, keyEncoding,
folder: secFolderId, folder: secFolderId
} = oldSecretVersionsObj[sv]; } = oldSecretVersionsObj[sv];
return { return {
@@ -405,7 +434,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
createdAt, createdAt,
algorithm, algorithm,
keyEncoding, keyEncoding,
folder: secFolderId, folder: secFolderId
}; };
}) })
); );
@@ -429,7 +458,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
secretValueTag, secretValueTag,
algorithm, algorithm,
keyEncoding, keyEncoding,
folder: secFolderId, folder: secFolderId
}) => ({ }) => ({
_id: new Types.ObjectId(), _id: new Types.ObjectId(),
secret: _id, secret: _id,
@@ -448,7 +477,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
secretValueTag, secretValueTag,
algorithm, algorithm,
keyEncoding, keyEncoding,
folder: secFolderId, folder: secFolderId
}) })
) )
); );
@@ -464,7 +493,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
const newFolderVersion = new FolderVersion({ const newFolderVersion = new FolderVersion({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
nodes: newFolderTree, nodes: newFolderTree
}); });
await newFolderVersion.save(); await newFolderVersion.save();
} }
@@ -473,13 +502,11 @@ export const rollbackWorkspaceSecretSnapshot = async (
await SecretVersion.updateMany( await SecretVersion.updateMany(
{ {
secret: { secret: {
$in: Object.keys(oldSecretVersionsObj).map( $in: Object.keys(oldSecretVersionsObj).map((sv) => oldSecretVersionsObj[sv].secret)
(sv) => oldSecretVersionsObj[sv].secret }
),
},
}, },
{ {
isDeleted: false, isDeleted: false
} }
); );
@@ -487,11 +514,11 @@ export const rollbackWorkspaceSecretSnapshot = async (
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId, folderId
}); });
return res.status(200).send({ return res.status(200).send({
secrets, secrets
}); });
}; };
@@ -568,13 +595,16 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
} }
} }
*/ */
const { workspaceId } = req.params; const {
query: { limit, offset, userId, sortBy, actionNames },
params: { workspaceId }
} = await validateRequest(GetWorkspaceLogsV1, req);
const offset: number = parseInt(req.query.offset as string); const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const limit: number = parseInt(req.query.limit as string); ForbiddenError.from(permission).throwUnlessCan(
const sortBy: string = req.query.sortBy as string; ProjectPermissionActions.Read,
const userId: string = req.query.userId as string; ProjectPermissionSub.AuditLogs
const actionNames: string = req.query.actionNames as string; );
const logs = await Log.find({ const logs = await Log.find({
workspace: workspaceId, workspace: workspaceId,
@@ -582,10 +612,10 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
...(actionNames ...(actionNames
? { ? {
actionNames: { actionNames: {
$in: actionNames.split(","), $in: actionNames.split(",")
},
} }
: {}), }
: {})
}) })
.sort({ createdAt: sortBy === "recent" ? -1 : 1 }) .sort({ createdAt: sortBy === "recent" ? -1 : 1 })
.skip(offset) .skip(offset)
@@ -594,7 +624,7 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
.populate("user serviceAccount serviceTokenData"); .populate("user serviceAccount serviceTokenData");
return res.status(200).send({ return res.status(200).send({
logs, logs
}); });
}; };
@@ -604,44 +634,52 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
const eventType = req.query.eventType; query: { limit, offset, endDate, eventType, startDate, userAgentType, actor },
const userAgentType = req.query.userAgentType; params: { workspaceId }
const actor = req.query.actor as string | undefined; } = await validateRequest(GetWorkspaceAuditLogsV1, req);
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
const startDate = req.query.startDate as string; const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const endDate = req.query.endDate as string; ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
);
const query = { const query = {
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
...(eventType ? { ...(eventType
? {
"event.type": eventType "event.type": eventType
} : {}), }
...(userAgentType ? { : {}),
...(userAgentType
? {
userAgentType userAgentType
} : {}), }
...(actor ? { : {}),
...(actor
? {
"actor.type": actor.split("-", 2)[0], "actor.type": actor.split("-", 2)[0],
...(actor.split("-", 2)[0] === ActorType.USER ? { ...(actor.split("-", 2)[0] === ActorType.USER
? {
"actor.metadata.userId": actor.split("-", 2)[1] "actor.metadata.userId": actor.split("-", 2)[1]
} : { }
: {
"actor.metadata.serviceId": actor.split("-", 2)[1] "actor.metadata.serviceId": actor.split("-", 2)[1]
}) })
} : {}), }
...(startDate || endDate ? { : {}),
...(startDate || endDate
? {
createdAt: { createdAt: {
...(startDate && { $gte: new Date(startDate) }), ...(startDate && { $gte: new Date(startDate) }),
...(endDate && { $lte: new Date(endDate) }) ...(endDate && { $lte: new Date(endDate) })
} }
} : {})
} }
: {})
};
const auditLogs = await AuditLog.find(query) const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
const totalCount = await AuditLog.countDocuments(query); const totalCount = await AuditLog.countDocuments(query);
@@ -649,7 +687,7 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
auditLogs, auditLogs,
totalCount totalCount
}); });
} };
/** /**
* Return audit log actor filter options for workspace with id [workspaceId] * Return audit log actor filter options for workspace with id [workspaceId]
@@ -657,18 +695,26 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Response) => { export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
);
const userIds = await Membership.distinct("user", { const userIds = await Membership.distinct("user", {
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
}); });
const userActors: UserActor[] = (await User.find({ const userActors: UserActor[] = (
await User.find({
_id: { _id: {
$in: userIds $in: userIds
} }
}) }).select("email")
.select("email")) ).map((user) => ({
.map((user) => ({
type: ActorType.USER, type: ActorType.USER,
metadata: { metadata: {
userId: user._id.toString(), userId: user._id.toString(),
@@ -676,11 +722,11 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
} }
})); }));
const serviceActors: ServiceActor[] = (await ServiceTokenData.find({ const serviceActors: ServiceActor[] = (
await ServiceTokenData.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
}) }).select("name")
.select("name")) ).map((serviceTokenData) => ({
.map((serviceTokenData) => ({
type: ActorType.SERVICE, type: ActorType.SERVICE,
metadata: { metadata: {
serviceId: serviceTokenData._id.toString(), serviceId: serviceTokenData._id.toString(),
@@ -691,7 +737,7 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
return res.status(200).send({ return res.status(200).send({
actors: [...userActors, ...serviceActors] actors: [...userActors, ...serviceActors]
}); });
} };
/** /**
* Return trusted ips for workspace with id [workspaceId] * Return trusted ips for workspace with id [workspaceId]
@@ -699,7 +745,15 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
* @param res * @param res
*/ */
export const getWorkspaceTrustedIps = async (req: Request, res: Response) => { export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
params: { workspaceId }
} = await validateRequest(GetWorkspaceTrustedIpsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.IpAllowList
);
const trustedIps = await TrustedIP.find({ const trustedIps = await TrustedIP.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
@@ -708,7 +762,7 @@ export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
trustedIps trustedIps
}); });
} };
/** /**
* Add a trusted ip to workspace with id [workspaceId] * Add a trusted ip to workspace with id [workspaceId]
@@ -716,22 +770,29 @@ export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const addWorkspaceTrustedIp = async (req: Request, res: Response) => { export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const { const {
ipAddress: ip, params: { workspaceId },
comment, body: { comment, isActive, ipAddress: ip }
isActive } = await validateRequest(AddWorkspaceTrustedIpV1, req);
} = req.body;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.IpAllowList
);
const plan = await EELicenseService.getPlan(req.workspace.organization); const plan = await EELicenseService.getPlan(req.workspace.organization);
if (!plan.ipAllowlisting) return res.status(400).send({ if (!plan.ipAllowlisting)
message: "Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range." return res.status(400).send({
message:
"Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
}); });
const isValidIPOrCidr = isValidIpOrCidr(ip); const isValidIPOrCidr = isValidIpOrCidr(ip);
if (!isValidIPOrCidr) return res.status(400).send({ if (!isValidIPOrCidr)
return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block" message: "The IP is not a valid IPv4, IPv6, or CIDR block"
}); });
@@ -743,7 +804,7 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
type, type,
prefix, prefix,
isActive, isActive,
comment, comment
}).save(); }).save();
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
@@ -764,7 +825,7 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
trustedIp trustedIp
}); });
} };
/** /**
* Update trusted ip with id [trustedIpId] workspace with id [workspaceId] * Update trusted ip with id [trustedIpId] workspace with id [workspaceId]
@@ -772,21 +833,29 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => { export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId, trustedIpId } = req.params;
const { const {
ipAddress: ip, params: { workspaceId, trustedIpId },
comment body: { ipAddress: ip, comment }
} = req.body; } = await validateRequest(UpdateWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.IpAllowList
);
const plan = await EELicenseService.getPlan(req.workspace.organization); const plan = await EELicenseService.getPlan(req.workspace.organization);
if (!plan.ipAllowlisting) return res.status(400).send({ if (!plan.ipAllowlisting)
message: "Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range." return res.status(400).send({
message:
"Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range."
}); });
const isValidIPOrCidr = isValidIpOrCidr(ip); const isValidIPOrCidr = isValidIpOrCidr(ip);
if (!isValidIPOrCidr) return res.status(400).send({ if (!isValidIPOrCidr)
return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block" message: "The IP is not a valid IPv4, IPv6, or CIDR block"
}); });
@@ -799,7 +868,7 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
prefix?: number; prefix?: number;
$unset?: { $unset?: {
prefix: number; prefix: number;
} };
} = { } = {
ipAddress, ipAddress,
type, type,
@@ -815,7 +884,7 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
const trustedIp = await TrustedIP.findOneAndUpdate( const trustedIp = await TrustedIP.findOneAndUpdate(
{ {
_id: new Types.ObjectId(trustedIpId), _id: new Types.ObjectId(trustedIpId),
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId)
}, },
updateObject, updateObject,
{ {
@@ -823,7 +892,8 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
} }
); );
if (!trustedIp) return res.status(400).send({ if (!trustedIp)
return res.status(400).send({
message: "Failed to update trusted IP" message: "Failed to update trusted IP"
}); });
@@ -845,7 +915,7 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
trustedIp trustedIp
}); });
} };
/** /**
* Delete IP access range from workspace with id [workspaceId] * Delete IP access range from workspace with id [workspaceId]
@@ -853,12 +923,22 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => { export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId, trustedIpId } = req.params; const {
params: { workspaceId, trustedIpId }
} = await validateRequest(DeleteWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.IpAllowList
);
const plan = await EELicenseService.getPlan(req.workspace.organization); const plan = await EELicenseService.getPlan(req.workspace.organization);
if (!plan.ipAllowlisting) return res.status(400).send({ if (!plan.ipAllowlisting)
message: "Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range." return res.status(400).send({
message:
"Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range."
}); });
const trustedIp = await TrustedIP.findOneAndDelete({ const trustedIp = await TrustedIP.findOneAndDelete({
@@ -866,7 +946,8 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
}); });
if (!trustedIp) return res.status(400).send({ if (!trustedIp)
return res.status(400).send({
message: "Failed to delete trusted IP" message: "Failed to delete trusted IP"
}); });
@@ -888,4 +969,4 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
trustedIp trustedIp
}); });
} };
+1 -10
View File
@@ -1,17 +1,8 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import {
validateRequest,
} from "../../../middleware";
import { param } from "express-validator";
import { actionController } from "../../controllers/v1"; import { actionController } from "../../controllers/v1";
// TODO: put into action controller // TODO: put into action controller
router.get( router.get("/:actionId", actionController.getAction);
"/:actionId",
param("actionId").exists().trim(),
validateRequest,
actionController.getAction
);
export default router; export default router;
+2 -7
View File
@@ -1,19 +1,14 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth, validateRequest } from "../../../middleware";
requireAuth,
validateRequest,
} from "../../../middleware";
import { query } from "express-validator";
import { cloudProductsController } from "../../controllers/v1"; import { cloudProductsController } from "../../controllers/v1";
import { AuthMode } from "../../../variables"; import { AuthMode } from "../../../variables";
router.get( router.get(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
query("billing-cycle").exists().isIn(["monthly", "yearly"]),
validateRequest, validateRequest,
cloudProductsController.getCloudProducts cloudProductsController.getCloudProducts
); );
+17 -127
View File
@@ -1,236 +1,126 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../../middleware";
requireAuth,
requireOrganizationAuth,
validateRequest,
} from "../../../middleware";
import { body, param, query } from "express-validator";
import { organizationsController } from "../../controllers/v1"; import { organizationsController } from "../../controllers/v1";
import { import { AuthMode } from "../../../variables";
ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER
} from "../../../variables";
router.get( router.get(
"/:organizationId/plans/table", "/:organizationId/plans/table",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("billingCycle").exists().isString().isIn(["monthly", "yearly"]),
validateRequest,
organizationsController.getOrganizationPlansTable organizationsController.getOrganizationPlansTable
); );
router.get( router.get(
"/:organizationId/plan", "/:organizationId/plan",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("workspaceId").optional().isString(),
validateRequest,
organizationsController.getOrganizationPlan organizationsController.getOrganizationPlan
); );
router.post( router.post(
"/:organizationId/session/trial", "/:organizationId/session/trial",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("success_url").exists().trim(),
validateRequest,
organizationsController.startOrganizationTrial organizationsController.startOrganizationTrial
); );
router.get( router.get(
"/:organizationId/plan/billing", "/:organizationId/plan/billing",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("workspaceId").optional().isString(),
validateRequest,
organizationsController.getOrganizationPlanBillingInfo organizationsController.getOrganizationPlanBillingInfo
); );
router.get( router.get(
"/:organizationId/plan/table", "/:organizationId/plan/table",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("workspaceId").optional().isString(),
validateRequest,
organizationsController.getOrganizationPlanTable organizationsController.getOrganizationPlanTable
); );
router.get( router.get(
"/:organizationId/billing-details", "/:organizationId/billing-details",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationBillingDetails organizationsController.getOrganizationBillingDetails
); );
router.patch( router.patch(
"/:organizationId/billing-details", "/:organizationId/billing-details",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("email").optional().isString().trim(),
body("name").optional().isString().trim(),
validateRequest,
organizationsController.updateOrganizationBillingDetails organizationsController.updateOrganizationBillingDetails
); );
router.get( router.get(
"/:organizationId/billing-details/payment-methods", "/:organizationId/billing-details/payment-methods",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationPmtMethods organizationsController.getOrganizationPmtMethods
); );
router.post( router.post(
"/:organizationId/billing-details/payment-methods", "/:organizationId/billing-details/payment-methods",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("success_url").exists().isString(),
body("cancel_url").exists().isString(),
validateRequest,
organizationsController.addOrganizationPmtMethod organizationsController.addOrganizationPmtMethod
); );
router.delete( router.delete(
"/:organizationId/billing-details/payment-methods/:pmtMethodId", "/:organizationId/billing-details/payment-methods/:pmtMethodId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
param("pmtMethodId").exists().trim(),
validateRequest,
organizationsController.deleteOrganizationPmtMethod organizationsController.deleteOrganizationPmtMethod
); );
router.get( router.get(
"/:organizationId/billing-details/tax-ids", "/:organizationId/billing-details/tax-ids",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationTaxIds organizationsController.getOrganizationTaxIds
); );
router.post( router.post(
"/:organizationId/billing-details/tax-ids", "/:organizationId/billing-details/tax-ids",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("type").exists().isString(),
body("value").exists().isString(),
validateRequest,
organizationsController.addOrganizationTaxId organizationsController.addOrganizationTaxId
); );
router.delete( router.delete(
"/:organizationId/billing-details/tax-ids/:taxId", "/:organizationId/billing-details/tax-ids/:taxId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
param("taxId").exists().trim(),
validateRequest,
organizationsController.deleteOrganizationTaxId organizationsController.deleteOrganizationTaxId
); );
router.get( router.get(
"/:organizationId/invoices", "/:organizationId/invoices",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationInvoices organizationsController.getOrganizationInvoices
); );
router.get( router.get(
"/:organizationId/licenses", "/:organizationId/licenses",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationLicenses organizationsController.getOrganizationLicenses
); );
+4 -27
View File
@@ -1,47 +1,24 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../../middleware";
requireAuth,
requireSecretAuth,
validateRequest,
} from "../../../middleware";
import { body, param, query } from "express-validator";
import { secretController } from "../../controllers/v1"; import { secretController } from "../../controllers/v1";
import { import {
ADMIN, AuthMode
AuthMode,
MEMBER,
PERMISSION_READ_SECRETS,
PERMISSION_WRITE_SECRETS
} from "../../../variables"; } from "../../../variables";
router.get( router.get(
"/:secretId/secret-versions", "/:secretId/secret-versions",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireSecretAuth({
acceptedRoles: [ADMIN, MEMBER],
requiredPermissions: [PERMISSION_READ_SECRETS],
}),
param("secretId").exists().trim(),
query("offset").exists().isInt(),
query("limit").exists().isInt(),
validateRequest,
secretController.getSecretVersions secretController.getSecretVersions
); );
router.post( router.post(
"/:secretId/secret-versions/rollback", "/:secretId/secret-versions/rollback",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireSecretAuth({
acceptedRoles: [ADMIN, MEMBER],
requiredPermissions: [PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS],
}),
param("secretId").exists().trim(),
body("version").exists().isInt(),
secretController.rollbackSecretVersion secretController.rollbackSecretVersion
); );
+13 -41
View File
@@ -1,80 +1,52 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { requireAuth } from "../../../middleware";
import { import {
requireAuth, createInstallationSession,
requireOrganizationAuth, getCurrentOrganizationInstallationStatus,
validateRequest, getRisksForOrganization,
} from "../../../middleware"; linkInstallationToOrganization,
import { body, param } from "express-validator"; updateRisksStatus
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../../controllers/v1/secretScanningController"; } from "../../../controllers/v1/secretScanningController";
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../../variables"; import { AuthMode } from "../../../variables";
router.post( router.post(
"/create-installation-session/organization/:organizationId", "/create-installation-session/organization/:organizationId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("organizationId").exists().trim(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
createInstallationSession createInstallationSession
); );
router.post( router.post(
"/link-installation", "/link-installation",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("installationId").exists().trim(),
body("sessionId").exists().trim(),
validateRequest,
linkInstallationToOrganization linkInstallationToOrganization
); );
router.get( router.get(
"/installation-status/organization/:organizationId", "/installation-status/organization/:organizationId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("organizationId").exists().trim(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
getCurrentOrganizationInstallationStatus getCurrentOrganizationInstallationStatus
); );
router.get( router.get(
"/organization/:organizationId/risks", "/organization/:organizationId/risks",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("organizationId").exists().trim(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
getRisksForOrganization getRisksForOrganization
); );
router.post( router.post(
"/organization/:organizationId/risks/:riskId/status", "/organization/:organizationId/risks/:riskId/status",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("organizationId").exists().trim(),
param("riskId").exists().trim(),
body("status").exists(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
updateRisksStatus updateRisksStatus
); );
+3 -15
View File
@@ -1,26 +1,14 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../../middleware";
requireSecretSnapshotAuth, import { AuthMode } from "../../../variables";
} from "../../middleware";
import {
requireAuth,
validateRequest,
} from "../../../middleware";
import { param } from "express-validator";
import { ADMIN, AuthMode, MEMBER } from "../../../variables";
import { secretSnapshotController } from "../../controllers/v1"; import { secretSnapshotController } from "../../controllers/v1";
router.get( router.get(
"/:secretSnapshotId", "/:secretSnapshotId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireSecretSnapshotAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("secretSnapshotId").exists().trim(),
validateRequest,
secretSnapshotController.getSecretSnapshot secretSnapshotController.getSecretSnapshot
); );
+22 -73
View File
@@ -1,37 +1,22 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import passport from "passport"; import passport from "passport";
import { import { requireAuth } from "../../../middleware";
AuthProvider
} from "../../models";
import {
requireAuth,
requireOrganizationAuth,
validateRequest,
} from "../../../middleware";
import { body, query } from "express-validator";
import { ssoController } from "../../controllers/v1"; import { ssoController } from "../../controllers/v1";
import { authLimiter } from "../../../helpers/rateLimiter"; import { authLimiter } from "../../../helpers/rateLimiter";
import { import { AuthMode } from "../../../variables";
ACCEPTED,
ADMIN,
AuthMode,
OWNER
} from "../../../variables";
router.get( router.get("/redirect/google", authLimiter, (req, res, next) => {
"/redirect/google",
authLimiter,
(req, res, next) => {
passport.authenticate("google", { passport.authenticate("google", {
scope: ["profile", "email"], scope: ["profile", "email"],
session: false, session: false,
...(req.query.callback_port ? { ...(req.query.callback_port
? {
state: req.query.callback_port as string state: req.query.callback_port as string
} : {})
})(req, res, next);
} }
); : {})
})(req, res, next);
});
router.get( router.get(
"/google", "/google",
@@ -42,18 +27,16 @@ router.get(
ssoController.redirectSSO ssoController.redirectSSO
); );
router.get( router.get("/redirect/github", authLimiter, (req, res, next) => {
"/redirect/github",
authLimiter,
(req, res, next) => {
passport.authenticate("github", { passport.authenticate("github", {
session: false, session: false,
...(req.query.callback_port ? { ...(req.query.callback_port
? {
state: req.query.callback_port as string state: req.query.callback_port as string
} : {})
})(req, res, next);
} }
); : {})
})(req, res, next);
});
router.get( router.get(
"/github", "/github",
@@ -65,21 +48,18 @@ router.get(
ssoController.redirectSSO ssoController.redirectSSO
); );
router.get( router.get("/redirect/saml2/:ssoIdentifier", authLimiter, (req, res, next) => {
"/redirect/saml2/:ssoIdentifier",
authLimiter,
(req, res, next) => {
const options = { const options = {
failureRedirect: "/", failureRedirect: "/",
additionalParams: { additionalParams: {
RelayState: req.query.callback_port ?? "" RelayState: req.query.callback_port ?? ""
}, }
}; };
passport.authenticate("saml", options)(req, res, next); passport.authenticate("saml", options)(req, res, next);
} });
);
router.post("/saml2/:ssoIdentifier", router.post(
"/saml2/:ssoIdentifier",
passport.authenticate("saml", { passport.authenticate("saml", {
failureRedirect: "/login/provider/error", failureRedirect: "/login/provider/error",
failureFlash: true, failureFlash: true,
@@ -91,55 +71,24 @@ router.post("/saml2/:ssoIdentifier",
router.get( router.get(
"/config", "/config",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
locationOrganizationId: "query"
}),
query("organizationId").exists().trim(),
validateRequest,
ssoController.getSSOConfig ssoController.getSSOConfig
); );
router.post( router.post(
"/config", "/config",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
locationOrganizationId: "body"
}),
body("organizationId").exists().trim(),
body("authProvider").exists().isString().isIn([AuthProvider.OKTA_SAML]),
body("isActive").exists().isBoolean(),
body("entryPoint").exists().isString(),
body("issuer").exists().isString(),
body("cert").exists().isString(),
validateRequest,
ssoController.createSSOConfig ssoController.createSSOConfig
); );
router.patch( router.patch(
"/config", "/config",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
locationOrganizationId: "body"
}),
body("organizationId").exists().trim(),
body("authProvider").optional().isString(),
body("isActive").optional().isBoolean(),
body("entryPoint").optional().isString(),
body("issuer").optional().isString(),
body("cert").optional().isString(),
validateRequest,
ssoController.updateSSOConfig ssoController.updateSSOConfig
); );
+12 -109
View File
@@ -1,182 +1,85 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../../middleware";
requireAuth, import { AuthMode } from "../../../variables";
requireWorkspaceAuth,
validateRequest,
} from "../../../middleware";
import { body, param, query } from "express-validator";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../../variables";
import { workspaceController } from "../../controllers/v1"; import { workspaceController } from "../../controllers/v1";
import { EventType, UserAgentType } from "../../models";
router.get( router.get(
"/:workspaceId/secret-snapshots", "/:workspaceId/secret-snapshots",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
query("environment").isString().exists().trim(),
query("folderId").default("root").isString().trim(),
query("offset").exists().isInt(),
query("limit").exists().isInt(),
validateRequest,
workspaceController.getWorkspaceSecretSnapshots workspaceController.getWorkspaceSecretSnapshots
); );
router.get( router.get(
"/:workspaceId/secret-snapshots/count", "/:workspaceId/secret-snapshots/count",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
query("environment").isString().exists().trim(),
query("folderId").default("root").isString().trim(),
validateRequest,
workspaceController.getWorkspaceSecretSnapshotsCount workspaceController.getWorkspaceSecretSnapshotsCount
); );
router.post( router.post(
"/:workspaceId/secret-snapshots/rollback", "/:workspaceId/secret-snapshots/rollback",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("environment").isString().exists().trim(),
query("folderId").default("root").isString().exists().trim(),
body("version").exists().isInt(),
validateRequest,
workspaceController.rollbackWorkspaceSecretSnapshot workspaceController.rollbackWorkspaceSecretSnapshot
); );
router.get( router.get(
"/:workspaceId/logs", "/:workspaceId/logs",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
query("offset").exists().isInt(),
query("limit").exists().isInt(),
query("sortBy"),
query("userId"),
query("actionNames"),
validateRequest,
workspaceController.getWorkspaceLogs workspaceController.getWorkspaceLogs
); );
router.get( router.get(
"/:workspaceId/audit-logs", "/:workspaceId/audit-logs",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }),
query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }),
query("actor").optional({ nullable: true }),
query("startDate").isISO8601().withMessage("Invalid start date format").optional({ nullable: true }),
query("endDate").isISO8601().withMessage("Invalid end date format").optional({ nullable: true }),
query("offset"),
query("limit"),
validateRequest,
workspaceController.getWorkspaceAuditLogs workspaceController.getWorkspaceAuditLogs
); );
router.get( router.get(
"/:workspaceId/audit-logs/filters/actors", "/:workspaceId/audit-logs/filters/actors",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceAuditLogActorFilterOpts workspaceController.getWorkspaceAuditLogActorFilterOpts
); );
router.get( router.get(
"/:workspaceId/trusted-ips", "/:workspaceId/trusted-ips",
param("workspaceId").exists().isString().trim(),
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}), }),
workspaceController.getWorkspaceTrustedIps workspaceController.getWorkspaceTrustedIps
); );
router.post( router.post(
"/:workspaceId/trusted-ips", "/:workspaceId/trusted-ips",
param("workspaceId").exists().isString().trim(),
body("ipAddress").exists().isString().trim(),
body("comment").default("").isString().trim(),
body("isActive").exists().isBoolean(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}), }),
workspaceController.addWorkspaceTrustedIp workspaceController.addWorkspaceTrustedIp
); );
router.patch( router.patch(
"/:workspaceId/trusted-ips/:trustedIpId", "/:workspaceId/trusted-ips/:trustedIpId",
param("workspaceId").exists().isString().trim(),
param("trustedIpId").exists().isString().trim(),
body("ipAddress").isString().trim().default(""),
body("comment").default("").isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}), }),
workspaceController.updateWorkspaceTrustedIp workspaceController.updateWorkspaceTrustedIp
); );
router.delete( router.delete(
"/:workspaceId/trusted-ips/:trustedIpId", "/:workspaceId/trusted-ips/:trustedIpId",
param("workspaceId").exists().isString().trim(),
param("trustedIpId").exists().isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}), }),
workspaceController.deleteWorkspaceTrustedIp workspaceController.deleteWorkspaceTrustedIp
); );
+2 -1
View File
@@ -160,7 +160,7 @@ export const getIntegrationAuthAccessHelper = async ({
let accessId; let accessId;
let accessToken; let accessToken;
const integrationAuth = await IntegrationAuth.findById(integrationAuthId).select( const integrationAuth = await IntegrationAuth.findById(integrationAuthId).select(
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag" "workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt +refreshCiphertext +refreshIV +refreshTag +accessIdCiphertext +accessIdIV +accessIdTag"
); );
if (!integrationAuth) if (!integrationAuth)
@@ -209,6 +209,7 @@ export const getIntegrationAuthAccessHelper = async ({
if (!accessToken) throw InternalServerError(); if (!accessToken) throw InternalServerError();
return { return {
integrationAuth,
accessId, accessId,
accessToken accessToken
}; };
+12 -12
View File
@@ -13,27 +13,27 @@ import { BadRequestError, MembershipNotFoundError } from "../utils/errors";
export const validateMembership = async ({ export const validateMembership = async ({
userId, userId,
workspaceId, workspaceId,
acceptedRoles, acceptedRoles
}: { }: {
userId: Types.ObjectId | string; userId: Types.ObjectId | string;
workspaceId: Types.ObjectId | string; workspaceId: Types.ObjectId | string;
acceptedRoles?: Array<"admin" | "member">; acceptedRoles?: Array<"admin" | "member" | "custom" | "viewer">;
}) => { }) => {
const membership = await Membership.findOne({ const membership = await Membership.findOne({
user: userId, user: userId,
workspace: workspaceId, workspace: workspaceId
}).populate("workspace"); }).populate("workspace");
if (!membership) { if (!membership) {
throw MembershipNotFoundError({ throw MembershipNotFoundError({
message: "Failed to find workspace membership", message: "Failed to find workspace membership"
}); });
} }
if (acceptedRoles) { if (acceptedRoles) {
if (!acceptedRoles.includes(membership.role)) { if (!acceptedRoles.includes(membership.role)) {
throw BadRequestError({ throw BadRequestError({
message: "Failed authorization for membership role", message: "Failed authorization for membership role"
}); });
} }
} }
@@ -62,7 +62,7 @@ export const findMembership = async (queryObj: any) => {
export const addMemberships = async ({ export const addMemberships = async ({
userIds, userIds,
workspaceId, workspaceId,
roles, roles
}: { }: {
userIds: string[]; userIds: string[];
workspaceId: string; workspaceId: string;
@@ -74,15 +74,15 @@ export const addMemberships = async ({
filter: { filter: {
user: userId, user: userId,
workspace: workspaceId, workspace: workspaceId,
role: roles[idx], role: roles[idx]
}, },
update: { update: {
user: userId, user: userId,
workspace: workspaceId, workspace: workspaceId,
role: roles[idx], role: roles[idx]
},
upsert: true,
}, },
upsert: true
}
}; };
}); });
await Membership.bulkWrite(operations as any); await Membership.bulkWrite(operations as any);
@@ -95,7 +95,7 @@ export const addMemberships = async ({
*/ */
export const deleteMembership = async ({ membershipId }: { membershipId: string }) => { export const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
const deletedMembership = await Membership.findOneAndDelete({ const deletedMembership = await Membership.findOneAndDelete({
_id: membershipId, _id: membershipId
}); });
// delete keys associated with the membership // delete keys associated with the membership
@@ -103,7 +103,7 @@ export const deleteMembership = async ({ membershipId }: { membershipId: string
// case: membership had a registered user // case: membership had a registered user
await Key.deleteMany({ await Key.deleteMany({
receiver: deletedMembership.user, receiver: deletedMembership.user,
workspace: deletedMembership.workspace, workspace: deletedMembership.workspace
}); });
} }
+24 -32
View File
@@ -1,14 +1,6 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import { Key, Membership, MembershipOrg, Workspace } from "../models";
Key, import { MembershipOrgNotFoundError, UnauthorizedRequestError } from "../utils/errors";
Membership,
MembershipOrg,
Workspace,
} from "../models";
import {
MembershipOrgNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
/** /**
* Validate that user with id [userId] is a member of organization with id [organizationId] * Validate that user with id [userId] is a member of organization with id [organizationId]
@@ -22,16 +14,16 @@ export const validateMembershipOrg = async ({
userId, userId,
organizationId, organizationId,
acceptedRoles, acceptedRoles,
acceptedStatuses, acceptedStatuses
}: { }: {
userId: Types.ObjectId; userId: Types.ObjectId;
organizationId: Types.ObjectId; organizationId: Types.ObjectId;
acceptedRoles?: Array<"owner" | "admin" | "member">; acceptedRoles?: Array<"owner" | "admin" | "member" | "custom">;
acceptedStatuses?: Array<"invited" | "accepted">; acceptedStatuses?: Array<"invited" | "accepted">;
}) => { }) => {
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
user: userId, user: userId,
organization: organizationId, organization: organizationId
}); });
if (!membershipOrg) { if (!membershipOrg) {
@@ -40,18 +32,22 @@ export const validateMembershipOrg = async ({
if (acceptedRoles) { if (acceptedRoles) {
if (!acceptedRoles.includes(membershipOrg.role)) { if (!acceptedRoles.includes(membershipOrg.role)) {
throw UnauthorizedRequestError({ message: "Failed to validate organization membership role" }); throw UnauthorizedRequestError({
message: "Failed to validate organization membership role"
});
} }
} }
if (acceptedStatuses) { if (acceptedStatuses) {
if (!acceptedStatuses.includes(membershipOrg.status)) { if (!acceptedStatuses.includes(membershipOrg.status)) {
throw UnauthorizedRequestError({ message: "Failed to validate organization membership status" }); throw UnauthorizedRequestError({
message: "Failed to validate organization membership status"
});
} }
} }
return membershipOrg; return membershipOrg;
} };
/** /**
* Return organization membership matching criteria specified in * Return organization membership matching criteria specified in
@@ -76,7 +72,7 @@ export const addMembershipsOrg = async ({
userIds, userIds,
organizationId, organizationId,
roles, roles,
statuses, statuses
}: { }: {
userIds: string[]; userIds: string[];
organizationId: string; organizationId: string;
@@ -90,16 +86,16 @@ export const addMembershipsOrg = async ({
user: userId, user: userId,
organization: organizationId, organization: organizationId,
role: roles[idx], role: roles[idx],
status: statuses[idx], status: statuses[idx]
}, },
update: { update: {
user: userId, user: userId,
organization: organizationId, organization: organizationId,
role: roles[idx], role: roles[idx],
status: statuses[idx], status: statuses[idx]
},
upsert: true,
}, },
upsert: true
}
}; };
}); });
@@ -111,13 +107,9 @@ export const addMembershipsOrg = async ({
* @param {Object} obj * @param {Object} obj
* @param {String} obj.membershipOrgId - id of organization membership to delete * @param {String} obj.membershipOrgId - id of organization membership to delete
*/ */
export const deleteMembershipOrg = async ({ export const deleteMembershipOrg = async ({ membershipOrgId }: { membershipOrgId: string }) => {
membershipOrgId,
}: {
membershipOrgId: string;
}) => {
const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({ const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
_id: membershipOrgId, _id: membershipOrgId
}); });
if (!deletedMembershipOrg) throw new Error("Failed to delete organization membership"); if (!deletedMembershipOrg) throw new Error("Failed to delete organization membership");
@@ -128,22 +120,22 @@ export const deleteMembershipOrg = async ({
const workspaces = ( const workspaces = (
await Workspace.find({ await Workspace.find({
organization: deletedMembershipOrg.organization, organization: deletedMembershipOrg.organization
}) })
).map((w) => w._id.toString()); ).map((w) => w._id.toString());
await Membership.deleteMany({ await Membership.deleteMany({
user: deletedMembershipOrg.user, user: deletedMembershipOrg.user,
workspace: { workspace: {
$in: workspaces, $in: workspaces
}, }
}); });
await Key.deleteMany({ await Key.deleteMany({
receiver: deletedMembershipOrg.user, receiver: deletedMembershipOrg.user,
workspace: { workspace: {
$in: workspaces, $in: workspaces
}, }
}); });
} }
+4 -25
View File
@@ -504,11 +504,6 @@ export const getSecretsHelper = async ({
}: GetSecretsParams) => { }: GetSecretsParams) => {
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
if (!isValidScope(authData.authPayload, environment, secretPath)) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
if (!folderId) { if (!folderId) {
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
@@ -575,11 +570,11 @@ export const getSecretsHelper = async ({
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
// reduce the number of events captured // reduce the number of events captured
let shouldRecordK8Event = false let shouldRecordK8Event = false;
if (authData.userAgent == K8_USER_AGENT_NAME) { if (authData.userAgent == K8_USER_AGENT_NAME) {
const randomNumber = Math.random(); const randomNumber = Math.random();
if (randomNumber > 0.9) { if (randomNumber > 0.9) {
shouldRecordK8Event = true shouldRecordK8Event = true;
} }
} }
@@ -588,7 +583,7 @@ export const getSecretsHelper = async ({
if (postHogClient && atLeastOneNonSignUpSecret) { if (postHogClient && atLeastOneNonSignUpSecret) {
const shouldCapture = authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event; const shouldCapture = authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
const approximateForNoneCapturedEvents = secrets.length * 10 const approximateForNoneCapturedEvents = secrets.length * 10;
if (shouldCapture) { if (shouldCapture) {
postHogClient.capture({ postHogClient.capture({
@@ -633,11 +628,7 @@ export const getSecretHelper = async ({
}); });
let secret: ISecret | null = null; let secret: ISecret | null = null;
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
if (!isValidScope(authData.authPayload, environment, secretPath)) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
// try getting personal secret first (if exists) // try getting personal secret first (if exists)
@@ -751,12 +742,6 @@ export const updateSecretHelper = async ({
}); });
let secret: ISecret | null = null; let secret: ISecret | null = null;
// if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
if (!isValidScope(authData.authPayload, environment, secretPath)) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
if (type === SECRET_SHARED) { if (type === SECRET_SHARED) {
@@ -916,12 +901,6 @@ export const deleteSecretHelper = async ({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
}); });
// if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
if (!isValidScope(authData.authPayload, environment, secretPath)) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
+18 -17
View File
@@ -1,7 +1,4 @@
import { import { IUser, User } from "../models";
IUser,
User,
} from "../models";
import { sendMail } from "./nodemailer"; import { sendMail } from "./nodemailer";
/** /**
@@ -12,7 +9,7 @@ import { sendMail } from "./nodemailer";
*/ */
export const setupAccount = async ({ email }: { email: string }) => { export const setupAccount = async ({ email }: { email: string }) => {
const user = await new User({ const user = await new User({
email, email
}).save(); }).save();
return user; return user;
@@ -49,11 +46,11 @@ export const completeAccount = async ({
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier, verifier
}: { }: {
userId: string; userId: string;
firstName: string; firstName: string;
lastName: string; lastName?: string;
encryptionVersion: number; encryptionVersion: number;
protectedKey: string; protectedKey: string;
protectedKeyIV: string; protectedKeyIV: string;
@@ -66,7 +63,7 @@ export const completeAccount = async ({
verifier: string; verifier: string;
}) => { }) => {
const options = { const options = {
new: true, new: true
}; };
const user = await User.findByIdAndUpdate( const user = await User.findByIdAndUpdate(
userId, userId,
@@ -82,7 +79,7 @@ export const completeAccount = async ({
iv: encryptedPrivateKeyIV, iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag, tag: encryptedPrivateKeyTag,
salt, salt,
verifier, verifier
}, },
options options
); );
@@ -100,22 +97,26 @@ export const completeAccount = async ({
export const checkUserDevice = async ({ export const checkUserDevice = async ({
user, user,
ip, ip,
userAgent, userAgent
}: { }: {
user: IUser; user: IUser;
ip: string; ip: string;
userAgent: string; userAgent: string;
}) => { }) => {
const isDeviceSeen = user.devices.some((device) => device.ip === ip && device.userAgent === userAgent); const isDeviceSeen = user.devices.some(
(device) => device.ip === ip && device.userAgent === userAgent
);
if (!isDeviceSeen) { if (!isDeviceSeen) {
// case: unseen login ip detected for user // case: unseen login ip detected for user
// -> notify user about the sign-in from new ip // -> notify user about the sign-in from new ip
user.devices = user.devices.concat([{ user.devices = user.devices.concat([
{
ip: String(ip), ip: String(ip),
userAgent, userAgent
}]); }
]);
await user.save(); await user.save();
@@ -128,8 +129,8 @@ export const checkUserDevice = async ({
email: user.email, email: user.email,
timestamp: new Date().toString(), timestamp: new Date().toString(),
ip, ip,
userAgent, userAgent
}, }
}); });
} }
} };
+17
View File
@@ -0,0 +1,17 @@
import type { Request } from "express";
import { AnyZodObject, ZodError, z } from "zod";
import { BadRequestError } from "../utils/errors";
export async function validateRequest<T extends AnyZodObject>(
schema: T,
req: Request
): Promise<z.infer<T>> {
try {
return schema.parseAsync(req);
} catch (error) {
if (error instanceof ZodError) {
throw BadRequestError({ message: error.message });
}
return BadRequestError({ message: JSON.stringify(error) });
}
}
+2
View File
@@ -37,6 +37,7 @@ import {
membership as v1MembershipRouter, membership as v1MembershipRouter,
organization as v1OrganizationRouter, organization as v1OrganizationRouter,
password as v1PasswordRouter, password as v1PasswordRouter,
role as v1RoleRouter,
secretImport as v1SecretImportRouter, secretImport as v1SecretImportRouter,
secret as v1SecretRouter, secret as v1SecretRouter,
secretsFolder as v1SecretsFolder, secretsFolder as v1SecretsFolder,
@@ -173,6 +174,7 @@ const main = async () => {
app.use("/api/v1/secret-scanning", v1SecretScanningRouter); app.use("/api/v1/secret-scanning", v1SecretScanningRouter);
app.use("/api/v1/webhooks", v1WebhooksRouter); app.use("/api/v1/webhooks", v1WebhooksRouter);
app.use("/api/v1/secret-imports", v1SecretImportRouter); app.use("/api/v1/secret-imports", v1SecretImportRouter);
app.use("/api/v1/roles", v1RoleRouter);
// v2 routes (improvements) // v2 routes (improvements)
app.use("/api/v2/signup", v2SignupRouter); app.use("/api/v2/signup", v2SignupRouter);
+6
View File
@@ -14,6 +14,9 @@ import requireServiceAccountAuth from "./requireServiceAccountAuth";
import requireServiceAccountWorkspacePermissionAuth from "./requireServiceAccountWorkspacePermissionAuth"; import requireServiceAccountWorkspacePermissionAuth from "./requireServiceAccountWorkspacePermissionAuth";
import requireSecretAuth from "./requireSecretAuth"; import requireSecretAuth from "./requireSecretAuth";
import requireSecretsAuth from "./requireSecretsAuth"; import requireSecretsAuth from "./requireSecretsAuth";
import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled";
import requireE2EEOff from "./requireE2EEOff";
import requireIPAllowlistCheck from "./requireIPAllowlistCheck";
import validateRequest from "./validateRequest"; import validateRequest from "./validateRequest";
export { export {
@@ -33,5 +36,8 @@ export {
requireServiceAccountWorkspacePermissionAuth, requireServiceAccountWorkspacePermissionAuth,
requireSecretAuth, requireSecretAuth,
requireSecretsAuth, requireSecretsAuth,
requireBlindIndicesEnabled,
requireE2EEOff,
requireIPAllowlistCheck,
validateRequest, validateRequest,
}; };
@@ -0,0 +1,34 @@
import { NextFunction, Request, Response } from "express";
import { Types } from "mongoose";
import { SecretBlindIndexData } from "../models";
import { UnauthorizedRequestError } from "../utils/errors";
type req = "params" | "body" | "query";
/**
* Validate if workspace with [workspaceId] has blind indices enabled
* @param {Object} obj
* @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
* @returns
*/
const requireBlindIndicesEnabled = ({
locationWorkspaceId
}: {
locationWorkspaceId: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
const secretBlindIndexData = await SecretBlindIndexData.exists({
workspace: new Types.ObjectId(workspaceId)
});
if (!secretBlindIndexData) throw UnauthorizedRequestError({
message: "Failed workspace authorization due to blind indices not being enabled"
});
return next();
}
}
export default requireBlindIndicesEnabled;
+31
View File
@@ -0,0 +1,31 @@
import { NextFunction, Request, Response } from "express";
import { BadRequestError } from "../utils/errors";
import { BotService } from "../services";
type req = "params" | "body" | "query";
/**
* Validate if workspace with [workspaceId] has E2EE off/disabled
* @param {Object} obj
* @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
* @returns
*/
const requireE2EEOff = ({
locationWorkspaceId
}: {
locationWorkspaceId: req;
}) => {
return async (req: Request, _: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId);
if (isWorkspaceE2EE) throw BadRequestError({
message: "Failed workspace authorization due to end-to-end encryption not being disabled"
});
return next();
}
}
export default requireE2EEOff;
@@ -0,0 +1,55 @@
import net from "net";
import { NextFunction, Request, Response } from "express";
import { UnauthorizedRequestError } from "../utils/errors";
import { extractIPDetails } from "../utils/ip";
import { ActorType, TrustedIP } from "../ee/models";
type req = "params" | "body" | "query";
/**
* Validate if workspace with [workspaceId] has E2EE off/disabled
* @param {Object} obj
* @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
* @returns
*/
const requireIPAllowlistCheck = ({
locationWorkspaceId
}: {
locationWorkspaceId: req;
}) => {
return async (req: Request, _: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
if (req.authData.actor.type === ActorType.SERVICE) {
const trustedIps = await TrustedIP.find({
workspace: workspaceId
});
if (trustedIps.length > 0) {
// case: check the IP address of the inbound request against trusted IPs
const blockList = new net.BlockList();
for (const trustedIp of trustedIps) {
if (trustedIp.prefix !== undefined) {
blockList.addSubnet(trustedIp.ipAddress, trustedIp.prefix, trustedIp.type);
} else {
blockList.addAddress(trustedIp.ipAddress, trustedIp.type);
}
}
const { type } = extractIPDetails(req.authData.ipAddress);
const check = blockList.check(req.authData.ipAddress, type);
if (!check)
throw UnauthorizedRequestError({
message: "Failed workspace authorization"
});
}
}
return next();
}
}
export default requireIPAllowlistCheck;
+2 -11
View File
@@ -9,24 +9,18 @@ type req = "params" | "body" | "query";
* on request params. * on request params.
* @param {Object} obj * @param {Object} obj
* @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth * @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing * @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
*/ */
const requireWorkspaceAuth = ({ const requireWorkspaceAuth = ({
acceptedRoles, acceptedRoles,
locationWorkspaceId, locationWorkspaceId,
locationEnvironment = undefined, locationEnvironment = undefined,
requiredPermissions = [], requiredPermissions = [],
requireBlindIndicesEnabled = false,
requireE2EEOff = false,
checkIPAllowlist = false
}: { }: {
acceptedRoles: Array<"admin" | "member">; acceptedRoles: Array<"admin" | "member">;
locationWorkspaceId: req; locationWorkspaceId: req;
locationEnvironment?: req | undefined; locationEnvironment?: req | undefined;
requiredPermissions?: string[]; requiredPermissions?: string[];
requireBlindIndicesEnabled?: boolean;
requireE2EEOff?: boolean;
checkIPAllowlist?: boolean;
}) => { }) => {
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId; const workspaceId = req[locationWorkspaceId]?.workspaceId;
@@ -38,10 +32,7 @@ const requireWorkspaceAuth = ({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
acceptedRoles, acceptedRoles,
requiredPermissions, requiredPermissions
requireBlindIndicesEnabled,
requireE2EEOff,
checkIPAllowlist
}); });
if (membership) { if (membership) {
+20 -15
View File
@@ -1,9 +1,9 @@
import { Schema, Types, model } from "mongoose"; import { Schema, Types, model } from "mongoose";
import { ADMIN, MEMBER } from "../variables"; import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
export interface IMembershipPermission { export interface IMembershipPermission {
environmentSlug: string, environmentSlug: string;
ability: string ability: string;
} }
export interface IMembership { export interface IMembership {
@@ -11,23 +11,24 @@ export interface IMembership {
user: Types.ObjectId; user: Types.ObjectId;
inviteEmail?: string; inviteEmail?: string;
workspace: Types.ObjectId; workspace: Types.ObjectId;
role: "admin" | "member"; role: "admin" | "member" | "viewer" | "custom";
deniedPermissions: IMembershipPermission[] customRole: Types.ObjectId;
deniedPermissions: IMembershipPermission[];
} }
const membershipSchema = new Schema<IMembership>( const membershipSchema = new Schema<IMembership>(
{ {
user: { user: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "User", ref: "User"
}, },
inviteEmail: { inviteEmail: {
type: String, type: String
}, },
workspace: { workspace: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "Workspace", ref: "Workspace",
required: true, required: true
}, },
deniedPermissions: { deniedPermissions: {
type: [ type: [
@@ -35,20 +36,24 @@ const membershipSchema = new Schema<IMembership>(
environmentSlug: String, environmentSlug: String,
ability: { ability: {
type: String, type: String,
enum: ["read", "write"], enum: ["read", "write"]
}, }
}, }
], ],
default: [], default: []
}, },
role: { role: {
type: String, type: String,
enum: [ADMIN, MEMBER], enum: [ADMIN, MEMBER, VIEWER, CUSTOM],
required: true, required: true
}, },
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
}, },
{ {
timestamps: true, timestamps: true
} }
); );
+15 -13
View File
@@ -1,12 +1,13 @@
import { Document, Schema, Types, model } from "mongoose"; import { Document, Schema, Types, model } from "mongoose";
import { ACCEPTED, ADMIN, INVITED, MEMBER, OWNER } from "../variables"; import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER, OWNER } from "../variables";
export interface IMembershipOrg extends Document { export interface IMembershipOrg extends Document {
_id: Types.ObjectId; _id: Types.ObjectId;
user: Types.ObjectId; user: Types.ObjectId;
inviteEmail: string; inviteEmail: string;
organization: Types.ObjectId; organization: Types.ObjectId;
role: "owner" | "admin" | "member"; role: "owner" | "admin" | "member" | "custom";
customRole: Types.ObjectId;
status: "invited" | "accepted"; status: "invited" | "accepted";
} }
@@ -14,32 +15,33 @@ const membershipOrgSchema = new Schema(
{ {
user: { user: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "User", ref: "User"
}, },
inviteEmail: { inviteEmail: {
type: String, type: String
}, },
organization: { organization: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "Organization", ref: "Organization"
}, },
role: { role: {
type: String, type: String,
enum: [OWNER, ADMIN, MEMBER], enum: [OWNER, ADMIN, MEMBER, CUSTOM],
required: true, required: true
}, },
status: { status: {
type: String, type: String,
enum: [INVITED, ACCEPTED], enum: [INVITED, ACCEPTED],
required: true, required: true
}, },
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
}, },
{ {
timestamps: true, timestamps: true
} }
); );
export const MembershipOrg = model<IMembershipOrg>( export const MembershipOrg = model<IMembershipOrg>("MembershipOrg", membershipOrgSchema);
"MembershipOrg",
membershipOrgSchema
);
+55
View File
@@ -0,0 +1,55 @@
import { Schema, Types, model } from "mongoose";
export interface IRole {
_id: Types.ObjectId;
name: string;
description: string;
slug: string;
permissions: Array<unknown>;
workspace: Types.ObjectId;
organization: Types.ObjectId;
isOrgRole: boolean;
}
const roleSchema = new Schema<IRole>(
{
name: {
type: String,
required: true
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace"
},
isOrgRole: {
type: Boolean,
required: true,
select: false
},
description: {
type: String
},
slug: {
type: String,
required: true
},
permissions: {
type: Array,
required: true
}
},
{
timestamps: true
}
);
roleSchema.index({ organization: 1, workspace: 1 });
const Role = model<IRole>("Role", roleSchema);
export default Role;
-7
View File
@@ -1,6 +1,5 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body } from "express-validator";
import { requireAuth, validateRequest } from "../../middleware"; import { requireAuth, validateRequest } from "../../middleware";
import { authController } from "../../controllers/v1"; import { authController } from "../../controllers/v1";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
@@ -12,9 +11,6 @@ router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login1) // TODO endpoint: deprecate (moved to api/v3/auth/login1)
"/login1", "/login1",
authLimiter, authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(),
body("clientPublicKey").exists().trim().notEmpty(),
validateRequest,
authController.login1 authController.login1
); );
@@ -22,9 +18,6 @@ router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login2) // TODO endpoint: deprecate (moved to api/v3/auth/login2)
"/login2", "/login2",
authLimiter, authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(),
body("clientProof").exists().trim().notEmpty(),
validateRequest,
authController.login2 authController.login2
); );
+4 -20
View File
@@ -1,40 +1,24 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param } from "express-validator";
import { import {
requireAuth, requireAuth
requireBotAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware"; } from "../../middleware";
import { botController } from "../../controllers/v1"; import { botController } from "../../controllers/v1";
import { ADMIN, AuthMode, MEMBER } from "../../variables"; import { AuthMode } from "../../variables";
router.get( router.get(
"/:workspaceId", "/:workspaceId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim().notEmpty(),
validateRequest,
botController.getBotByWorkspaceId botController.getBotByWorkspaceId
); );
router.patch( router.patch(
"/:botId/active", "/:botId/active",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireBotAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
body("isActive").exists().isBoolean(),
body("botKey"),
validateRequest,
botController.setBotActiveState botController.setBotActiveState
); );
+3 -1
View File
@@ -17,6 +17,7 @@ import integrationAuth from "./integrationAuth";
import secretsFolder from "./secretsFolder"; import secretsFolder from "./secretsFolder";
import webhooks from "./webhook"; import webhooks from "./webhook";
import secretImport from "./secretImport"; import secretImport from "./secretImport";
import role from "./role";
export { export {
signup, signup,
@@ -37,5 +38,6 @@ export {
integrationAuth, integrationAuth,
secretsFolder, secretsFolder,
webhooks, webhooks,
secretImport secretImport,
role
}; };
+3 -58
View File
@@ -1,45 +1,14 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../middleware";
requireAuth, import { AuthMode } from "../../variables";
requireIntegrationAuth,
requireIntegrationAuthorizationAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { body, param } from "express-validator";
import { integrationController } from "../../controllers/v1"; import { integrationController } from "../../controllers/v1";
router.post( router.post(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
location: "body",
}),
body("integrationAuthId").exists().isString().trim(),
body("isActive").exists().isBoolean(),
body("app").optional().isString().trim(),
body("appId").optional().isString().trim(),
body("secretPath").default("/").isString().trim(),
body("sourceEnvironment").trim(),
body("targetEnvironment").trim(),
body("targetEnvironmentId").trim(),
body("targetService").trim(),
body("targetServiceId").trim(),
body("owner").trim(),
body("path").trim(),
body("region").trim(),
body("metadata").optional().isObject().withMessage("Metadata should be an object"),
body("metadata.secretSuffix").optional().isString().withMessage("Suffix should be a string"),
validateRequest,
integrationController.createIntegration integrationController.createIntegration
); );
@@ -48,18 +17,6 @@ router.patch(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationId").exists().trim(),
body("isActive").exists().isBoolean(),
body("app").exists().trim(),
body("secretPath").default("/").isString().trim(),
body("environment").exists().trim(),
body("appId").exists(),
body("targetEnvironment").exists(),
body("owner").exists(),
validateRequest,
integrationController.updateIntegration integrationController.updateIntegration
); );
@@ -68,11 +25,6 @@ router.delete(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationId").exists().trim(),
validateRequest,
integrationController.deleteIntegration integrationController.deleteIntegration
); );
@@ -81,13 +33,6 @@ router.post(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
}),
body("environment").isString().exists().trim(),
body("workspaceId").exists().trim(),
validateRequest,
integrationController.manualSync integrationController.manualSync
); );
+15 -106
View File
@@ -1,23 +1,13 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param, query } from "express-validator"; import { requireAuth } from "../../middleware";
import { import { AuthMode } from "../../variables";
requireAuth,
requireIntegrationAuthorizationAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { integrationAuthController } from "../../controllers/v1"; import { integrationAuthController } from "../../controllers/v1";
router.get( router.get(
"/integration-options", "/integration-options",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
integrationAuthController.getIntegrationOptions integrationAuthController.getIntegrationOptions
); );
@@ -25,50 +15,23 @@ router.get(
router.get( router.get(
"/:integrationAuthId", "/:integrationAuthId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
attachAccessToken: false
}),
param("integrationAuthId"),
validateRequest,
integrationAuthController.getIntegrationAuth integrationAuthController.getIntegrationAuth
); );
router.post( router.post(
"/oauth-token", "/oauth-token",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
}),
body("workspaceId").exists().trim().notEmpty(),
body("code").exists().trim().notEmpty(),
body("integration").exists().trim().notEmpty(),
body("url").optional().isString().trim(),
validateRequest,
integrationAuthController.oAuthExchange integrationAuthController.oAuthExchange
); );
router.post( router.post(
"/access-token", "/access-token",
body("workspaceId").exists().trim().notEmpty(),
body("refreshToken").optional().isString().trim().notEmpty(),
body("accessId").optional().isString().trim(),
body("accessToken").optional().isString().trim().notEmpty(),
body("url").trim(),
body("namespace").trim(),
body("integration").exists().trim().notEmpty(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
}), }),
integrationAuthController.saveIntegrationToken integrationAuthController.saveIntegrationToken
); );
@@ -76,126 +39,72 @@ router.post(
router.get( router.get(
"/:integrationAuthId/apps", "/:integrationAuthId/apps",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId"),
query("teamId"),
query("workspaceSlug"),
validateRequest,
integrationAuthController.getIntegrationAuthApps integrationAuthController.getIntegrationAuthApps
); );
router.get( router.get(
"/:integrationAuthId/teams", "/:integrationAuthId/teams",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId"),
validateRequest,
integrationAuthController.getIntegrationAuthTeams integrationAuthController.getIntegrationAuthTeams
); );
router.get( router.get(
"/:integrationAuthId/vercel/branches", "/:integrationAuthId/vercel/branches",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
query("teamId").optional().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthVercelBranches integrationAuthController.getIntegrationAuthVercelBranches
); );
router.get( router.get(
"/:integrationAuthId/railway/environments", "/:integrationAuthId/railway/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthRailwayEnvironments integrationAuthController.getIntegrationAuthRailwayEnvironments
); );
router.get( router.get(
"/:integrationAuthId/railway/services", "/:integrationAuthId/railway/services",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthRailwayServices integrationAuthController.getIntegrationAuthRailwayServices
); );
router.get( router.get(
"/:integrationAuthId/bitbucket/workspaces", "/:integrationAuthId/bitbucket/workspaces",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthBitBucketWorkspaces integrationAuthController.getIntegrationAuthBitBucketWorkspaces
); );
router.get( router.get(
"/:integrationAuthId/northflank/secret-groups", "/:integrationAuthId/northflank/secret-groups",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthNorthflankSecretGroups integrationAuthController.getIntegrationAuthNorthflankSecretGroups
); );
router.get( router.get(
"/:integrationAuthId/teamcity/build-configs", "/:integrationAuthId/teamcity/build-configs",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
); );
router.delete( router.delete(
"/:integrationAuthId", "/:integrationAuthId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
attachAccessToken: false,
}),
param("integrationAuthId"),
validateRequest,
integrationAuthController.deleteIntegrationAuth integrationAuthController.deleteIntegrationAuth
); );
+1 -4
View File
@@ -10,11 +10,8 @@ import { AuthMode } from "../../variables";
router.post( router.post(
"/signup", "/signup",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("inviteeEmail").exists().trim().notEmpty().isEmail(),
body("organizationId").exists().trim().notEmpty(),
validateRequest,
membershipOrgController.inviteUserToOrganization membershipOrgController.inviteUserToOrganization
); );
+6 -23
View File
@@ -1,12 +1,7 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../middleware";
requireAuth, import { AuthMode } from "../../variables";
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import { body, param } from "express-validator";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { keyController } from "../../controllers/v1"; import { keyController } from "../../controllers/v1";
// TODO endpoint: consider moving these endpoints to be under /workspaces to be more RESTful // TODO endpoint: consider moving these endpoints to be under /workspaces to be more RESTful
@@ -14,29 +9,17 @@ import { keyController } from "../../controllers/v1";
router.post( router.post(
"/:workspaceId", "/:workspaceId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("key").exists(),
validateRequest,
keyController.uploadKey keyController.uploadKey
); );
router.get( // TODO endpoint: deprecate (note: move frontend to v2/workspace/key or something) router.get(
// TODO endpoint: deprecate (note: move frontend to v2/workspace/key or something)
"/:workspaceId/latest", "/:workspaceId/latest",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId"),
validateRequest,
keyController.getLatestKey keyController.getLatestKey
); );
+10 -26
View File
@@ -1,53 +1,37 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param } from "express-validator"; import { requireAuth } from "../../middleware";
import { requireAuth, validateRequest } from "../../middleware";
import { membershipController } from "../../controllers/v1"; import { membershipController } from "../../controllers/v1";
import { membershipController as EEMembershipControllers } from "../../ee/controllers/v1";
import { AuthMode } from "../../variables"; import { AuthMode } from "../../variables";
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId) // note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
// TODO endpoint: consider moving these endpoints to be under /workspace to be more RESTful // TODO endpoint: consider moving these endpoints to be under /workspace to be more RESTful
router.get( // TODO endpoint: deprecate - used for old CLI (deprecate) router.get(
// TODO endpoint: deprecate - used for old CLI (deprecate)
"/:workspaceId/connect", "/:workspaceId/connect",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("workspaceId").exists().trim(),
validateRequest,
membershipController.validateMembership membershipController.validateMembership
); );
router.delete( // TODO endpoint: check dashboard router.delete(
// TODO endpoint: check dashboard
"/:membershipId", "/:membershipId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("membershipId").exists().trim(),
validateRequest,
membershipController.deleteMembership membershipController.deleteMembership
); );
router.post( // TODO endpoint: check dashboard router.post(
// TODO endpoint: check dashboard
"/:membershipId/change-role", "/:membershipId/change-role",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("role").exists().trim(),
validateRequest,
membershipController.changeMembershipRole membershipController.changeMembershipRole
); );
router.post( // TODO endpoint: check dashboard
"/:membershipId/deny-permissions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
param("membershipId").isMongoId().exists().trim(),
body("permissions").isArray().exists(),
validateRequest,
EEMembershipControllers.denyMembershipPermissions
);
export default router; export default router;
+4 -5
View File
@@ -5,10 +5,11 @@ import { requireAuth, validateRequest } from "../../middleware";
import { membershipOrgController } from "../../controllers/v1"; import { membershipOrgController } from "../../controllers/v1";
import { AuthMode } from "../../variables"; import { AuthMode } from "../../variables";
router.post( // TODO endpoint: check dashboard router.post(
// TODO endpoint: check dashboard
"/membershipOrg/:membershipOrgId/change-role", "/membershipOrg/:membershipOrgId/change-role",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("membershipOrgId"), param("membershipOrgId"),
validateRequest, validateRequest,
@@ -18,10 +19,8 @@ router.post( // TODO endpoint: check dashboard
router.delete( router.delete(
"/:membershipOrgId", // TODO endpoint: check dashboard "/:membershipOrgId", // TODO endpoint: check dashboard
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("membershipOrgId").exists().trim(),
validateRequest,
membershipOrgController.deleteMembershipOrg membershipOrgController.deleteMembershipOrg
); );
+20 -87
View File
@@ -1,150 +1,90 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param } from "express-validator"; import { requireAuth } from "../../middleware";
import { import { AuthMode } from "../../variables";
requireAuth,
requireOrganizationAuth,
validateRequest,
} from "../../middleware";
import {
ACCEPTED,
ADMIN,
AuthMode,
MEMBER,
OWNER
} from "../../variables";
import { organizationController } from "../../controllers/v1"; import { organizationController } from "../../controllers/v1";
router.get( // TODO endpoint: deprecate (moved to api/v2/users/me/organizations) router.get(
// TODO endpoint: deprecate (moved to api/v2/users/me/organizations)
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
organizationController.getOrganizations organizationController.getOrganizations
); );
router.post( // not used on frontend router.post(
// not used on frontend
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("organizationName").exists().trim().notEmpty(),
validateRequest,
organizationController.createOrganization organizationController.createOrganization
); );
router.get( router.get(
"/:organizationId", "/:organizationId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganization organizationController.getOrganization
); );
router.get( // TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships) router.get(
// TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships)
"/:organizationId/users", "/:organizationId/users",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationMembers organizationController.getOrganizationMembers
); );
router.get( // TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces router.get(
// TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces) "/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationWorkspaces organizationController.getOrganizationWorkspaces
); );
router.patch( router.patch(
"/:organizationId/name", "/:organizationId/name",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("name").exists().trim().notEmpty(),
validateRequest,
organizationController.changeOrganizationName organizationController.changeOrganizationName
); );
router.get( router.get(
"/:organizationId/incidentContactOrg", "/:organizationId/incidentContactOrg",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationIncidentContacts organizationController.getOrganizationIncidentContacts
); );
router.post( router.post(
"/:organizationId/incidentContactOrg", "/:organizationId/incidentContactOrg",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("email").exists().trim().notEmpty(),
validateRequest,
organizationController.addOrganizationIncidentContact organizationController.addOrganizationIncidentContact
); );
router.delete( router.delete(
"/:organizationId/incidentContactOrg", "/:organizationId/incidentContactOrg",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("email").exists().trim().notEmpty(),
validateRequest,
organizationController.deleteOrganizationIncidentContact organizationController.deleteOrganizationIncidentContact
); );
router.post( router.post(
"/:organizationId/customer-portal-session", // TODO endpoint: move to EE "/:organizationId/customer-portal-session", // TODO endpoint: move to EE
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.createOrganizationPortalSession organizationController.createOrganizationPortalSession
); );
@@ -153,14 +93,7 @@ router.get(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationMembersAndTheirWorkspaces organizationController.getOrganizationMembersAndTheirWorkspaces
); );
export default router; export default router;
+6 -48
View File
@@ -1,7 +1,6 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body } from "express-validator"; import { requireAuth, requireSignupAuth } from "../../middleware";
import { requireAuth, requireSignupAuth, validateRequest } from "../../middleware";
import { passwordController } from "../../controllers/v1"; import { passwordController } from "../../controllers/v1";
import { passwordLimiter } from "../../helpers/rateLimiter"; import { passwordLimiter } from "../../helpers/rateLimiter";
import { AuthMode } from "../../variables"; import { AuthMode } from "../../variables";
@@ -9,10 +8,8 @@ import { AuthMode } from "../../variables";
router.post( router.post(
"/srp1", "/srp1",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("clientPublicKey").exists().isString().trim().notEmpty(),
validateRequest,
passwordController.srp1 passwordController.srp1
); );
@@ -20,35 +17,16 @@ router.post(
"/change-password", "/change-password",
passwordLimiter, passwordLimiter,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("clientProof").exists().trim().notEmpty(),
body("protectedKey").exists().isString().trim().notEmpty(),
body("protectedKeyIV").exists().isString().trim().notEmpty(),
body("protectedKeyTag").exists().isString().trim().notEmpty(),
body("encryptedPrivateKey").exists().isString().trim().notEmpty(), // private key encrypted under new pwd
body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), // new iv for private key
body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), // new tag for private key
body("salt").exists().isString().trim().notEmpty(), // part of new pwd
body("verifier").exists().isString().trim().notEmpty(), // part of new pwd
validateRequest,
passwordController.changePassword passwordController.changePassword
); );
router.post( router.post("/email/password-reset", passwordLimiter, passwordController.emailPasswordReset);
"/email/password-reset",
passwordLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(),
validateRequest,
passwordController.emailPasswordReset
);
router.post( router.post(
"/email/password-reset-verify", "/email/password-reset-verify",
passwordLimiter, passwordLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(),
body("code").exists().isString().trim().notEmpty(),
validateRequest,
passwordController.emailPasswordResetVerify passwordController.emailPasswordResetVerify
); );
@@ -63,31 +41,11 @@ router.post(
"/backup-private-key", "/backup-private-key",
passwordLimiter, passwordLimiter,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("clientProof").exists().isString().trim().notEmpty(),
body("encryptedPrivateKey").exists().isString().trim().notEmpty(), // (backup) private key encrypted under a strong key
body("iv").exists().isString().trim().notEmpty(), // new iv for (backup) private key
body("tag").exists().isString().trim().notEmpty(), // new tag for (backup) private key
body("salt").exists().isString().trim().notEmpty(), // salt generated from strong key
body("verifier").exists().isString().trim().notEmpty(), // salt generated from strong key
validateRequest,
passwordController.createBackupPrivateKey passwordController.createBackupPrivateKey
); );
router.post( router.post("/password-reset", requireSignupAuth, passwordController.resetPassword);
"/password-reset",
requireSignupAuth,
body("protectedKey").exists().isString().trim().notEmpty(),
body("protectedKeyIV").exists().isString().trim().notEmpty(),
body("protectedKeyTag").exists().isString().trim().notEmpty(),
body("encryptedPrivateKey").exists().isString().trim().notEmpty(), // private key encrypted under new pwd
body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), // new iv for private key
body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), // new tag for private key
body("salt").exists().isString().trim().notEmpty(), // part of new pwd
body("verifier").exists().isString().trim().notEmpty(), // part of new pwd
validateRequest,
passwordController.resetPassword
);
export default router; export default router;
+33
View File
@@ -0,0 +1,33 @@
import express from "express";
import { roleController } from "../../controllers/v1";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
const router = express.Router();
router.post("/", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleController.createRole);
router.patch("/:id", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleController.updateRole);
router.delete(
"/:id",
requireAuth({ acceptedAuthModes: [AuthMode.JWT] }),
roleController.deleteRole
);
router.get("/", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleController.getRoles);
// get a user permissions in an org
router.get(
"/organization/:orgId/permissions",
requireAuth({ acceptedAuthModes: [AuthMode.JWT] }),
roleController.getUserPermissions
);
router.get(
"/workspace/:workspaceId/permissions",
requireAuth({ acceptedAuthModes: [AuthMode.JWT] }),
roleController.getUserWorkspacePermissions
);
export default router;
+2 -39
View File
@@ -1,8 +1,7 @@
import express from "express"; import express from "express";
import { body, param, query } from "express-validator";
import { secretImportController } from "../../controllers/v1"; import { secretImportController } from "../../controllers/v1";
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import { requireAuth } from "../../middleware";
import { ADMIN, AuthMode, MEMBER } from "../../variables"; import { AuthMode } from "../../variables";
const router = express.Router(); const router = express.Router();
router.post( router.post(
@@ -10,17 +9,6 @@ router.post(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body"
}),
body("workspaceId").exists().isString().trim().notEmpty(),
body("environment").exists().isString().trim().notEmpty(),
body("folderId").default("root").isString().trim(),
body("secretImport").exists().isObject(),
body("secretImport.environment").isString().exists().trim(),
body("secretImport.secretPath").isString().exists().trim(),
validateRequest,
secretImportController.createSecretImport secretImportController.createSecretImport
); );
@@ -29,11 +17,6 @@ router.put(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
param("id").exists().isString().trim(),
body("secretImports").exists().isArray(),
body("secretImports.*.environment").isString().exists().trim(),
body("secretImports.*.secretPath").isString().exists().trim(),
validateRequest,
secretImportController.updateSecretImport secretImportController.updateSecretImport
); );
@@ -42,10 +25,6 @@ router.delete(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
param("id").exists().isString().trim(),
body("secretImportPath").isString().exists().trim(),
body("secretImportEnv").isString().exists().trim(),
validateRequest,
secretImportController.deleteSecretImport secretImportController.deleteSecretImport
); );
@@ -54,14 +33,6 @@ router.get(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query"
}),
query("workspaceId").exists().isString().trim().notEmpty(),
query("environment").exists().isString().trim().notEmpty(),
query("folderId").default("root").isString().trim(),
validateRequest,
secretImportController.getSecretImports secretImportController.getSecretImports
); );
@@ -70,14 +41,6 @@ router.get(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query"
}),
query("workspaceId").exists().isString().trim().notEmpty(),
query("environment").exists().isString().trim().notEmpty(),
query("folderId").default("root").isString().trim(),
validateRequest,
secretImportController.getAllSecretsFromImport secretImportController.getAllSecretsFromImport
); );
+2 -25
View File
@@ -1,13 +1,12 @@
import express from "express"; import express from "express";
import { body, param, query } from "express-validator";
import { import {
createFolder, createFolder,
deleteFolder, deleteFolder,
getFolders, getFolders,
updateFolderById updateFolderById
} from "../../controllers/v1/secretsFolderController"; } from "../../controllers/v1/secretsFolderController";
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import { requireAuth } from "../../middleware";
import { ADMIN, AuthMode, MEMBER } from "../../variables"; import { AuthMode } from "../../variables";
const router = express.Router(); const router = express.Router();
router.post( router.post(
@@ -15,15 +14,6 @@ router.post(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body"
}),
body("workspaceId").exists(),
body("environment").exists(),
body("folderName").exists(),
body("parentFolderId"),
validateRequest,
createFolder createFolder
); );
@@ -32,10 +22,6 @@ router.patch(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
body("workspaceId").exists(),
body("environment").exists(),
param("folderId").not().isIn(["root"]).exists(),
validateRequest,
updateFolderById updateFolderById
); );
@@ -44,10 +30,6 @@ router.delete(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
body("workspaceId").exists(),
body("environment").exists(),
param("folderId").not().isIn(["root"]).exists(),
validateRequest,
deleteFolder deleteFolder
); );
@@ -56,11 +38,6 @@ router.get(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("parentFolderId").optional().isString().trim(),
query("parentFolderPath").optional().isString().trim(),
validateRequest,
getFolders getFolders
); );
+2 -8
View File
@@ -1,26 +1,20 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body } from "express-validator";
import { validateRequest } from "../../middleware";
import { signupController } from "../../controllers/v1"; import { signupController } from "../../controllers/v1";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
// TODO: consider moving to users/v3/signup // TODO: consider moving to users/v3/signup
router.post( // TODO endpoint: consider moving to v3/users/signup/mail router.post(
// TODO endpoint: consider moving to v3/users/signup/mail
"/email/signup", "/email/signup",
authLimiter, authLimiter,
body("email").exists().trim().notEmpty().isEmail(),
validateRequest,
signupController.beginEmailSignup signupController.beginEmailSignup
); );
router.post( router.post(
"/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify "/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify
authLimiter, authLimiter,
body("email").exists().trim().notEmpty().isEmail(),
body("code").exists().trim().notEmpty(),
validateRequest,
signupController.verifyEmailSignup signupController.verifyEmailSignup
); );
+5 -9
View File
@@ -1,28 +1,24 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { requireAuth, validateRequest } from "../../middleware"; import { requireAuth } from "../../middleware";
import { body, query } from "express-validator";
import { userActionController } from "../../controllers/v1"; import { userActionController } from "../../controllers/v1";
import { AuthMode } from "../../variables"; import { AuthMode } from "../../variables";
// note: [userAction] will be deprecated in /v2 in favor of [action] // note: [userAction] will be deprecated in /v2 in favor of [action]
router.post( // TODO endpoint: move this into /users/me router.post(
// TODO endpoint: move this into /users/me
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("action"),
validateRequest,
userActionController.addUserAction userActionController.addUserAction
); );
router.get( router.get(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
query("action"),
validateRequest,
userActionController.getUserAction userActionController.getUserAction
); );
+7 -35
View File
@@ -1,74 +1,46 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import { requireAuth } from "../../middleware";
import { body, param, query } from "express-validator"; import { AuthMode } from "../../variables";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { webhookController } from "../../controllers/v1"; import { webhookController } from "../../controllers/v1";
router.post( router.post(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body"
}),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("webhookUrl").exists().isString().isURL().trim(),
body("webhookSecretKey").isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
webhookController.createWebhook webhookController.createWebhook
); );
router.patch( router.patch(
"/:webhookId", "/:webhookId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("webhookId").exists().isString().trim(),
body("isDisabled").default(false).isBoolean(),
validateRequest,
webhookController.updateWebhook webhookController.updateWebhook
); );
router.post( router.post(
"/:webhookId/test", "/:webhookId/test",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("webhookId").exists().isString().trim(),
validateRequest,
webhookController.testWebhook webhookController.testWebhook
); );
router.delete( router.delete(
"/:webhookId", "/:webhookId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("webhookId").exists().isString().trim(),
validateRequest,
webhookController.deleteWebhook webhookController.deleteWebhook
); );
router.get( router.get(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query"
}),
query("workspaceId").exists().isString().trim(),
query("environment").optional().isString().trim(),
query("secretPath").optional().isString().trim(),
validateRequest,
webhookController.listWebhooks webhookController.listWebhooks
); );
+13 -81
View File
@@ -1,50 +1,29 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param } from "express-validator"; import { requireAuth } from "../../middleware";
import { import { AuthMode } from "../../variables";
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { membershipController, workspaceController } from "../../controllers/v1"; import { membershipController, workspaceController } from "../../controllers/v1";
router.get( router.get(
"/:workspaceId/keys", "/:workspaceId/keys",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspacePublicKeys workspaceController.getWorkspacePublicKeys
); );
router.get( router.get(
"/:workspaceId/users", "/:workspaceId/users",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceMemberships workspaceController.getWorkspaceMemberships
); );
router.get( router.get(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
workspaceController.getWorkspaces workspaceController.getWorkspaces
); );
@@ -52,111 +31,64 @@ router.get(
router.get( router.get(
"/:workspaceId", "/:workspaceId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspace workspaceController.getWorkspace
); );
router.post( router.post(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
body("workspaceName").exists().trim().notEmpty(),
body("organizationId").exists().trim().notEmpty(),
validateRequest,
workspaceController.createWorkspace workspaceController.createWorkspace
); );
router.delete( router.delete(
"/:workspaceId", "/:workspaceId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.deleteWorkspace workspaceController.deleteWorkspace
); );
router.post( router.post(
"/:workspaceId/name", "/:workspaceId/name",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("name").exists().trim().notEmpty(),
validateRequest,
workspaceController.changeWorkspaceName workspaceController.changeWorkspaceName
); );
router.post( router.post(
"/:workspaceId/invite-signup", "/:workspaceId/invite-signup",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("email").exists().trim().notEmpty(),
validateRequest,
membershipController.inviteUserToWorkspace membershipController.inviteUserToWorkspace
); );
router.get( router.get(
"/:workspaceId/integrations", "/:workspaceId/integrations",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceIntegrations workspaceController.getWorkspaceIntegrations
); );
router.get( router.get(
"/:workspaceId/authorizations", "/:workspaceId/authorizations",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceIntegrationAuthorizations workspaceController.getWorkspaceIntegrationAuthorizations
); );
router.get( router.get(
"/:workspaceId/service-tokens", // TODO endpoint: deprecate "/:workspaceId/service-tokens", // TODO endpoint: deprecate
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceServiceTokens workspaceController.getWorkspaceServiceTokens
); );
+7 -18
View File
@@ -5,7 +5,8 @@ import { requireMfaAuth, validateRequest } from "../../middleware";
import { authController } from "../../controllers/v2"; import { authController } from "../../controllers/v2";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
router.post( // TODO: deprecate (moved to api/v3/auth/login1) router.post(
// TODO: deprecate (moved to api/v3/auth/login1)
"/login1", "/login1",
authLimiter, authLimiter,
body("email").isString().trim().notEmpty().toLowerCase(), body("email").isString().trim().notEmpty().toLowerCase(),
@@ -14,7 +15,8 @@ router.post( // TODO: deprecate (moved to api/v3/auth/login1)
authController.login1 authController.login1
); );
router.post( // TODO: deprecate (moved to api/v3/auth/login1) router.post(
// TODO: deprecate (moved to api/v3/auth/login1)
"/login2", "/login2",
authLimiter, authLimiter,
body("email").isString().trim().notEmpty().toLowerCase(), body("email").isString().trim().notEmpty().toLowerCase(),
@@ -23,22 +25,9 @@ router.post( // TODO: deprecate (moved to api/v3/auth/login1)
authController.login2 authController.login2
); );
router.post( //remove above ones after depreciation
"/mfa/send", router.post("/mfa/send", authLimiter, authController.sendMfaToken);
authLimiter,
body("email").isString().trim().notEmpty().isEmail(),
validateRequest,
authController.sendMfaToken
);
router.post( router.post("/mfa/verify", authLimiter, requireMfaAuth, authController.verifyMfaToken);
"/mfa/verify",
authLimiter,
requireMfaAuth,
body("email").isString().trim().notEmpty(),
body("mfaToken").isString().trim().notEmpty(),
validateRequest,
authController.verifyMfaToken
);
export default router; export default router;
+7 -56
View File
@@ -1,95 +1,46 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param } from "express-validator";
import { environmentController } from "../../controllers/v2"; import { environmentController } from "../../controllers/v2";
import { import { requireAuth } from "../../middleware";
requireAuth, import { AuthMode } from "../../variables";
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
router.post( router.post(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("environmentSlug").exists().trim(),
body("environmentName").exists().trim(),
validateRequest,
environmentController.createWorkspaceEnvironment environmentController.createWorkspaceEnvironment
); );
router.put( router.put(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("environmentSlug").exists().trim(),
body("environmentName").exists().trim(),
body("oldEnvironmentSlug").exists().trim(),
validateRequest,
environmentController.renameWorkspaceEnvironment environmentController.renameWorkspaceEnvironment
); );
router.patch( router.patch(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("environmentSlug").exists().isString().trim(),
body("environmentName").exists().isString().trim(),
body("otherEnvironmentSlug").exists().isString().trim(),
body("otherEnvironmentName").exists().isString().trim(),
validateRequest,
environmentController.reorderWorkspaceEnvironments environmentController.reorderWorkspaceEnvironments
); );
router.delete( router.delete(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("environmentSlug").exists().trim(),
validateRequest,
environmentController.deleteWorkspaceEnvironment environmentController.deleteWorkspaceEnvironment
); );
router.get( router.get(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
environmentController.getAllAccessibleEnvironmentsOfWorkspace environmentController.getAllAccessibleEnvironmentsOfWorkspace
); );
+9 -54
View File
@@ -2,99 +2,54 @@ import express from "express";
const router = express.Router(); const router = express.Router();
import { import {
requireAuth, requireAuth,
requireMembershipOrgAuth, requireOrganizationAuth
requireOrganizationAuth,
validateRequest,
} from "../../middleware"; } from "../../middleware";
import { body, param } from "express-validator"; import { ACCEPTED, ADMIN, AuthMode, OWNER } from "../../variables";
import {
ACCEPTED,
ADMIN,
AuthMode,
MEMBER,
OWNER
} from "../../variables";
import { organizationsController } from "../../controllers/v2"; import { organizationsController } from "../../controllers/v2";
// TODO: /POST to create membership // TODO: /POST to create membership
router.get( router.get(
"/:organizationId/memberships", "/:organizationId/memberships",
param("organizationId").exists().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}), }),
organizationsController.getOrganizationMemberships organizationsController.getOrganizationMemberships
); );
router.patch( router.patch(
"/:organizationId/memberships/:membershipId", "/:organizationId/memberships/:membershipId",
param("organizationId").exists().trim(),
param("membershipId").exists().trim(),
body("role").exists().isString().trim().isIn([OWNER, ADMIN, MEMBER]),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}),
requireMembershipOrgAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}), }),
organizationsController.updateOrganizationMembership organizationsController.updateOrganizationMembership
); );
router.delete( router.delete(
"/:organizationId/memberships/:membershipId", "/:organizationId/memberships/:membershipId",
param("organizationId").exists().trim(),
param("membershipId").exists().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}),
requireMembershipOrgAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}), }),
organizationsController.deleteOrganizationMembership organizationsController.deleteOrganizationMembership
); );
router.get( router.get(
"/:organizationId/workspaces", "/:organizationId/workspaces",
param("organizationId").exists().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}), }),
organizationsController.getOrganizationWorkspaces organizationsController.getOrganizationWorkspaces
); );
router.get( // TODO endpoint: deprecate service accounts router.get(
// TODO endpoint: deprecate service accounts
"/:organizationId/service-accounts", "/:organizationId/service-accounts",
param("organizationId").exists().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireOrganizationAuth({ requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN], acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED], acceptedStatuses: [ACCEPTED]
}), }),
organizationsController.getOrganizationServiceAccounts organizationsController.getOrganizationServiceAccounts
); );
+11 -43
View File
@@ -1,14 +1,12 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { Types } from "mongoose";
import { import {
requireAuth, requireAuth,
requireSecretsAuth, requireSecretsAuth,
requireWorkspaceAuth, requireWorkspaceAuth,
validateRequest validateRequest
} from "../../middleware"; } from "../../middleware";
import { validateClientForSecrets } from "../../validation"; import { body } from "express-validator";
import { body, query } from "express-validator";
import { secretsController } from "../../controllers/v2"; import { secretsController } from "../../controllers/v2";
import { import {
ADMIN, ADMIN,
@@ -19,44 +17,18 @@ import {
SECRET_PERSONAL, SECRET_PERSONAL,
SECRET_SHARED SECRET_SHARED
} from "../../variables"; } from "../../variables";
import { BatchSecretRequest } from "../../types/secret";
router.post( // TODO endpoint: strongly consider deprecation in favor of a single operation experience on dashboard router.post(
// TODO endpoint: strongly consider deprecation in favor of a single operation experience on dashboard
"/batch", "/batch",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body"
}),
body("workspaceId").exists().isString().trim(),
body("folderId").default("root").isString().trim(),
body("environment").exists().isString().trim(),
body("secretPath").optional().isString().trim(),
body("requests")
.exists()
.custom(async (requests: BatchSecretRequest[], { req }) => {
if (Array.isArray(requests)) {
const secretIds = requests
.map((request) => request.secret._id)
.filter((secretId) => secretId !== undefined);
if (secretIds.length > 0) {
req.secrets = await validateClientForSecrets({
authData: req.authData,
secretIds: secretIds.map((secretId: string) => new Types.ObjectId(secretId)),
requiredPermissions: []
});
}
}
return true;
}),
validateRequest,
secretsController.batchSecrets secretsController.batchSecrets
); );
router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets) router.post(
// TODO endpoint: deprecate (moved to POST api/v3/secrets)
"/", "/",
body("workspaceId").exists().isString().trim(), body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(), body("environment").exists().isString().trim(),
@@ -117,15 +89,9 @@ router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
secretsController.createSecrets secretsController.createSecrets
); );
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets) router.get(
// TODO endpoint: deprecate (moved to GET api/v3/secrets)
"/", "/",
query("workspaceId").exists().trim(),
query("environment").exists().trim(),
query("tagSlugs"),
query("folderId").default("root").isString().trim(),
query("secretPath").optional().isString().trim(),
query("include_imports").optional().default(false).isBoolean(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}), }),
@@ -138,7 +104,8 @@ router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
secretsController.getSecrets secretsController.getSecrets
); );
router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets) router.patch(
// TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
"/", "/",
body("secrets") body("secrets")
.exists() .exists()
@@ -173,7 +140,8 @@ router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
secretsController.updateSecrets secretsController.updateSecrets
); );
router.delete( // TODO endpoint: deprecate (moved to DELETE api/v3/secrets) router.delete(
// TODO endpoint: deprecate (moved to DELETE api/v3/secrets)
"/", "/",
body("secretIds") body("secretIds")
.exists() .exists()
+2 -43
View File
@@ -1,18 +1,9 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import {
requireAuth, requireAuth
requireServiceTokenDataAuth,
requireWorkspaceAuth,
validateRequest
} from "../../middleware"; } from "../../middleware";
import { body, param } from "express-validator"; import { AuthMode } from "../../variables";
import {
ADMIN,
AuthMode,
MEMBER,
PERMISSION_WRITE_SECRETS
} from "../../variables";
import { serviceTokenDataController } from "../../controllers/v2"; import { serviceTokenDataController } from "../../controllers/v2";
router.get( router.get(
@@ -28,33 +19,6 @@ router.post(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
body("name").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("scopes").exists().isArray(),
body("scopes.*.environment").exists().isString().trim(),
body("scopes.*.secretPath").exists().isString().trim(),
body("encryptedKey").exists().isString().trim(),
body("iv").exists().isString().trim(),
body("tag").exists().isString().trim(),
body("expiresIn").exists().isNumeric(), // measured in ms
body("permissions")
.isArray({ min: 1 })
.custom((value: string[]) => {
const allowedPermissions = ["read", "write"];
const invalidValues = value.filter((v) => !allowedPermissions.includes(v));
if (invalidValues.length > 0) {
throw new Error(`permissions contains invalid values: ${invalidValues.join(", ")}`);
}
return true;
}),
validateRequest,
serviceTokenDataController.createServiceTokenData serviceTokenDataController.createServiceTokenData
); );
@@ -63,11 +27,6 @@ router.delete(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireServiceTokenDataAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
param("serviceTokenDataId").exists().trim(),
validateRequest,
serviceTokenDataController.deleteServiceTokenData serviceTokenDataController.deleteServiceTokenData
); );
+5 -31
View File
@@ -1,56 +1,30 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param } from "express-validator";
import { tagController } from "../../controllers/v2"; import { tagController } from "../../controllers/v2";
import { import { requireAuth } from "../../middleware";
requireAuth, import { AuthMode } from "../../variables";
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
router.get( router.get(
"/:workspaceId/tags", "/:workspaceId/tags",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
tagController.getWorkspaceTags tagController.getWorkspaceTags
); );
router.delete( router.delete(
"/tags/:tagId", "/tags/:tagId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
param("tagId").exists().trim(),
validateRequest,
tagController.deleteWorkspaceTag tagController.deleteWorkspaceTag
); );
router.post( router.post(
"/:workspaceId/tags", "/:workspaceId/tags",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("name").exists().trim(),
body("tagColor").exists().trim(),
body("slug").exists().trim(),
validateRequest,
tagController.createWorkspaceTag tagController.createWorkspaceTag
); );
+7 -34
View File
@@ -1,20 +1,13 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../middleware";
requireAuth,
validateRequest,
} from "../../middleware";
import { body, param } from "express-validator";
import { usersController } from "../../controllers/v2"; import { usersController } from "../../controllers/v2";
import { AuthMode } from "../../variables"; import { AuthMode } from "../../variables";
import {
AuthMethod
} from "../../models";
router.get( router.get(
"/me", "/me",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
usersController.getMe usersController.getMe
); );
@@ -22,46 +15,31 @@ router.get(
router.patch( router.patch(
"/me/mfa", "/me/mfa",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
body("isMfaEnabled").exists().isBoolean(),
validateRequest,
usersController.updateMyMfaEnabled usersController.updateMyMfaEnabled
); );
router.patch( router.patch(
"/me/name", "/me/name",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
body("firstName").exists().isString(),
body("lastName").isString(),
validateRequest,
usersController.updateName usersController.updateName
); );
router.put( router.put(
"/me/auth-methods", "/me/auth-methods",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
body("authMethods").exists().isArray({ usersController.updateAuthMethods
min: 1,
}).custom((authMethods: AuthMethod[]) => {
return authMethods.every(provider => [
AuthMethod.EMAIL,
AuthMethod.GOOGLE,
AuthMethod.GITHUB
].includes(provider))
}),
validateRequest,
usersController.updateAuthMethods,
); );
router.get( router.get(
"/me/organizations", "/me/organizations",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
usersController.getMyOrganizations usersController.getMyOrganizations
); );
@@ -79,9 +57,6 @@ router.post(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
body("name").exists().isString().trim(),
body("expiresIn").isNumeric(),
validateRequest,
usersController.createAPIKey usersController.createAPIKey
); );
@@ -90,8 +65,6 @@ router.delete(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
param("apiKeyDataId").exists().trim(),
validateRequest,
usersController.deleteAPIKey usersController.deleteAPIKey
); );
+23 -74
View File
@@ -1,27 +1,19 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param, query } from "express-validator"; import { body, param, query } from "express-validator";
import { import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
requireAuth, import { ADMIN, AuthMode, MEMBER } from "../../variables";
requireMembershipAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { workspaceController } from "../../controllers/v2"; import { workspaceController } from "../../controllers/v2";
router.post( // TODO endpoint: deprecate (moved to POST v3/secrets) router.post(
// TODO endpoint: deprecate (moved to POST v3/secrets)
"/:workspaceId/secrets", "/:workspaceId/secrets",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params", locationWorkspaceId: "params"
}), }),
body("secrets").exists(), body("secrets").exists(),
body("keys").exists(), body("keys").exists(),
@@ -32,14 +24,15 @@ router.post( // TODO endpoint: deprecate (moved to POST v3/secrets)
workspaceController.pushWorkspaceSecrets workspaceController.pushWorkspaceSecrets
); );
router.get( // TODO endpoint: deprecate (moved to GET v3/secrets) router.get(
// TODO endpoint: deprecate (moved to GET v3/secrets)
"/:workspaceId/secrets", "/:workspaceId/secrets",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN], acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params", locationWorkspaceId: "params"
}), }),
query("environment").exists().trim(), query("environment").exists().trim(),
query("channel"), query("channel"),
@@ -48,83 +41,46 @@ router.get( // TODO endpoint: deprecate (moved to GET v3/secrets)
workspaceController.pullSecrets workspaceController.pullSecrets
); );
router.get( // TODO endpoint: consider moving to v3/users/me/workspaces/:workspaceId/key router.get(
// TODO endpoint: consider moving to v3/users/me/workspaces/:workspaceId/key
"/:workspaceId/encrypted-key", "/:workspaceId/encrypted-key",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceKey workspaceController.getWorkspaceKey
); );
router.get( router.get(
"/:workspaceId/service-token-data", "/:workspaceId/service-token-data",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceServiceTokenData workspaceController.getWorkspaceServiceTokenData
); );
router.get( // new - TODO: rewire dashboard to this route router.get(
// new - TODO: rewire dashboard to this route
"/:workspaceId/memberships", "/:workspaceId/memberships",
param("workspaceId").exists().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}), }),
workspaceController.getWorkspaceMemberships workspaceController.getWorkspaceMemberships
); );
router.patch( // TODO - rewire dashboard to this route router.patch(
// TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId", "/:workspaceId/memberships/:membershipId",
param("workspaceId").exists().trim(),
param("membershipId").exists().trim(),
body("role").exists().isString().trim().isIn([ADMIN, MEMBER]),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
requireMembershipAuth({
acceptedRoles: [ADMIN],
locationMembershipId: "params",
}), }),
workspaceController.updateWorkspaceMembership workspaceController.updateWorkspaceMembership
); );
router.delete( // TODO - rewire dashboard to this route router.delete(
// TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId", "/:workspaceId/memberships/:membershipId",
param("workspaceId").exists().trim(),
param("membershipId").exists().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
requireMembershipAuth({
acceptedRoles: [ADMIN],
locationMembershipId: "params",
}), }),
workspaceController.deleteWorkspaceMembership workspaceController.deleteWorkspaceMembership
); );
@@ -134,13 +90,6 @@ router.patch(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("autoCapitalization").exists().trim().notEmpty(),
validateRequest,
workspaceController.toggleAutoCapitalization workspaceController.toggleAutoCapitalization
); );
+2 -20
View File
@@ -1,29 +1,11 @@
import express from "express"; import express from "express";
import { body } from "express-validator";
import { validateRequest } from "../../middleware";
import { authController } from "../../controllers/v3"; import { authController } from "../../controllers/v3";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
const router = express.Router(); const router = express.Router();
router.post( router.post("/login1", authLimiter, authController.login1);
"/login1",
authLimiter,
body("email").isString().trim().toLowerCase(),
body("providerAuthToken").isString().trim().optional({nullable: true}),
body("clientPublicKey").isString().trim().notEmpty(),
validateRequest,
authController.login1
);
router.post( router.post("/login2", authLimiter, authController.login2);
"/login2",
authLimiter,
body("email").isString().trim().toLowerCase(),
body("providerAuthToken").isString().trim().optional({nullable: true}),
body("clientProof").isString().trim().notEmpty(),
validateRequest,
authController.login2
);
export default router; export default router;
+46 -204
View File
@@ -1,288 +1,130 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import {
import { body, param, query } from "express-validator"; requireAuth,
requireBlindIndicesEnabled,
requireE2EEOff
} from "../../middleware";
import { secretsController } from "../../controllers/v3"; import { secretsController } from "../../controllers/v3";
import { import {
ADMIN, AuthMode
AuthMode,
MEMBER,
PERMISSION_READ_SECRETS,
PERMISSION_WRITE_SECRETS,
SECRET_PERSONAL,
SECRET_SHARED
} from "../../variables"; } from "../../variables";
router.get( router.get(
"/raw", "/raw",
query("workspaceId").optional().isString().trim(),
query("environment").optional().isString().trim(),
query("folderId").optional().isString().trim(),
query("secretPath").default("/").isString().trim(),
query("include_imports").optional().isBoolean().default(false),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
secretsController.getSecretsRaw secretsController.getSecretsRaw
); );
router.get( router.get(
"/raw/:secretName", "/raw/:secretName",
param("secretName").exists().isString().trim(),
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("secretPath").default("/").isString().trim(),
query("type").optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "query"
locationWorkspaceId: "query", }),
locationEnvironment: "query", requireE2EEOff({
requiredPermissions: [PERMISSION_READ_SECRETS], locationWorkspaceId: "query"
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
}), }),
secretsController.getSecretByNameRaw secretsController.getSecretByNameRaw
); );
router.post( router.post(
"/raw/:secretName", "/raw/:secretName",
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretValue").exists().isString().trim(),
body("secretComment").default("").isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "body"
locationWorkspaceId: "body", }),
locationEnvironment: "body", requireE2EEOff({
requiredPermissions: [PERMISSION_WRITE_SECRETS], locationWorkspaceId: "body"
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
}), }),
secretsController.createSecretRaw secretsController.createSecretRaw
); );
router.patch( router.patch(
"/raw/:secretName", "/raw/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretValue").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "body"
locationWorkspaceId: "body", }),
locationEnvironment: "body", requireE2EEOff({
requiredPermissions: [PERMISSION_WRITE_SECRETS], locationWorkspaceId: "body"
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
}), }),
secretsController.updateSecretByNameRaw secretsController.updateSecretByNameRaw
); );
router.delete( router.delete(
"/raw/:secretName", "/raw/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "body"
locationWorkspaceId: "body", }),
locationEnvironment: "body", requireE2EEOff({
requiredPermissions: [PERMISSION_WRITE_SECRETS], locationWorkspaceId: "body"
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
}), }),
secretsController.deleteSecretByNameRaw secretsController.deleteSecretByNameRaw
); );
router.get( router.get(
"/", "/",
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("folderId").optional().isString().trim(),
query("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "query"
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
}), }),
secretsController.getSecrets secretsController.getSecrets
); );
router.post( router.post(
"/:secretName", "/:secretName",
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretKeyCiphertext").exists().isString().trim(),
body("secretKeyIV").exists().isString().trim(),
body("secretKeyTag").exists().isString().trim(),
body("secretValueCiphertext").exists().isString().trim(),
body("secretValueIV").exists().isString().trim(),
body("secretValueTag").exists().isString().trim(),
body("secretCommentCiphertext").optional().isString().trim(),
body("secretCommentIV").optional().isString().trim(),
body("secretCommentTag").optional().isString().trim(),
body("secretPath").default("/").isString().trim(),
body("metadata").optional().isObject().withMessage("Metadata should be an object"),
body("metadata.source").optional().isString().withMessage("Source should be a string"),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "body"
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
}), }),
secretsController.createSecret secretsController.createSecret
); );
router.get( router.get(
"/:secretName", "/:secretName",
param("secretName").exists().isString().trim(),
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("secretPath").default("/").isString().trim(),
query("type").optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "query"
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
checkIPAllowlist: false
}), }),
secretsController.getSecretByName secretsController.getSecretByName
); );
router.patch( router.patch(
"/:secretName", "/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretValueCiphertext").exists().isString().trim(),
body("secretValueIV").exists().isString().trim(),
body("secretValueTag").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "body"
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
}), }),
secretsController.updateSecretByName secretsController.updateSecretByName
); );
router.delete( router.delete(
"/:secretName", "/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
}), }),
requireWorkspaceAuth({ requireBlindIndicesEnabled({
acceptedRoles: [ADMIN, MEMBER], locationWorkspaceId: "body"
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
}), }),
secretsController.deleteSecretByName secretsController.deleteSecretByName
); );
+1 -17
View File
@@ -1,6 +1,5 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body } from "express-validator";
import { signupController } from "../../controllers/v3"; import { signupController } from "../../controllers/v3";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
import { validateRequest } from "../../middleware"; import { validateRequest } from "../../middleware";
@@ -8,23 +7,8 @@ import { validateRequest } from "../../middleware";
router.post( router.post(
"/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup "/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup
authLimiter, authLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(),
body("firstName").exists().isString().trim().notEmpty(),
body("lastName").exists().isString().trim().optional({nullable: true}),
body("protectedKey").exists().isString().trim().notEmpty(),
body("protectedKeyIV").exists().isString().trim().notEmpty(),
body("protectedKeyTag").exists().isString().trim().notEmpty(),
body("publicKey").exists().isString().trim().notEmpty(),
body("encryptedPrivateKey").exists().isString().trim().notEmpty(),
body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(),
body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(),
body("salt").exists().isString().trim().notEmpty(),
body("verifier").exists().isString().trim().notEmpty(),
body("organizationName").exists().isString().trim().notEmpty(),
body("providerAuthToken").isString().trim().optional({ nullable: true }),
body("attributionSource").optional().isString().trim(),
validateRequest, validateRequest,
signupController.completeAccountSignup, signupController.completeAccountSignup
); );
export default router; export default router;
+9 -51
View File
@@ -1,75 +1,33 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { import { requireAuth } from "../../middleware";
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import { workspacesController } from "../../controllers/v3"; import { workspacesController } from "../../controllers/v3";
import { import { AuthMode } from "../../variables";
ADMIN,
AuthMode
} from "../../variables";
import { body, param } from "express-validator";
// -- migration to blind indices endpoints // -- migration to blind indices endpoints
router.get( router.get(
"/:workspaceId/secrets/blind-index-status", "/:workspaceId/secrets/blind-index-status",
param("workspaceId").exists().isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}), }),
workspacesController.getWorkspaceBlindIndexStatus workspacesController.getWorkspaceBlindIndexStatus
); );
router.get( // allow admins to get all workspace secrets (part of blind indices migration) router.get(
// allow admins to get all workspace secrets (part of blind indices migration)
"/:workspaceId/secrets", "/:workspaceId/secrets",
param("workspaceId").exists().isString().trim(),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}), }),
workspacesController.getWorkspaceSecrets workspacesController.getWorkspaceSecrets
); );
router.post( // allow admins to name all workspace secrets (part of blind indices migration) router.post(
// allow admins to name all workspace secrets (part of blind indices migration)
"/:workspaceId/secrets/names", "/:workspaceId/secrets/names",
param("workspaceId").exists().isString().trim(),
body("secretsToUpdate")
.exists()
.isArray()
.withMessage("secretsToUpdate must be an array")
.customSanitizer((value) => {
return value.map((secret: any) => ({
secretName: secret.secretName,
_id: secret._id,
}));
}),
body("secretsToUpdate.*.secretName")
.exists()
.isString()
.withMessage("secretName must be a string"),
body("secretsToUpdate.*._id")
.exists()
.isString()
.withMessage("secretId must be a string"),
validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}), }),
workspacesController.nameWorkspaceSecrets workspacesController.nameWorkspaceSecrets
); );
+258
View File
@@ -0,0 +1,258 @@
import {
AbilityBuilder,
ForcedSubject,
MongoAbility,
RawRuleOf,
buildMongoQueryMatcher,
createMongoAbility
} from "@casl/ability";
import { Membership } from "../models";
import { IRole } from "../models/role";
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
import picomatch from "picomatch";
const $glob: FieldInstruction<string> = {
type: "field",
validate(instruction, value) {
if (typeof value !== "string") {
throw new Error(`"${instruction.name}" expects value to be a string`);
}
}
};
const glob: JsInterpreter<FieldCondition<string>> = (node, object, context) => {
const secretPath = context.get(object, node.field);
const permissionSecretGlobPath = node.value;
return picomatch.isMatch(secretPath, permissionSecretGlobPath, { strictSlashes: false });
};
export const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
export enum ProjectPermissionActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete"
}
export enum ProjectPermissionSub {
Role = "role",
Member = "member",
Settings = "settings",
Integrations = "integrations",
Webhooks = "webhooks",
ServiceTokens = "service-tokens",
Environments = "environments",
Tags = "tags",
AuditLogs = "audit-logs",
IpAllowList = "ip-allowlist",
Workspace = "workspace",
Secrets = "secrets",
SecretImports = "secret-imports",
SecretRollback = "secret-rollback",
Folders = "folders"
}
type SubjectFields = {
environment: string;
secretPath: string;
};
export type ProjectPermissionSet =
| [
ProjectPermissionActions,
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
]
| [
ProjectPermissionActions,
ProjectPermissionSub.Folders | (ForcedSubject<ProjectPermissionSub.Folders> & SubjectFields)
]
| [
ProjectPermissionActions,
(
| ProjectPermissionSub.SecretImports
| (ForcedSubject<ProjectPermissionSub.SecretImports> & SubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.Role]
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
| [ProjectPermissionActions, ProjectPermissionSub.Member]
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
| [ProjectPermissionActions, ProjectPermissionSub.AuditLogs]
| [ProjectPermissionActions, ProjectPermissionSub.Environments]
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace]
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback];
const buildAdminPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace);
return build({ conditionsMatcher });
};
export const adminProjectPermissions = buildAdminPermission();
const buildMemberPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
return build({ conditionsMatcher });
};
export const memberProjectPermissions = buildMemberPermission();
const buildViewerPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
return build({ conditionsMatcher });
};
export const viewerProjectPermission = buildViewerPermission();
export const getUserProjectPermissions = async (userId: string, workspaceId: string) => {
// TODO(akhilmhdh): speed this up by pulling from cache later
const membership = await Membership.findOne({
user: userId,
workspace: workspaceId
})
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
if (!membership || (membership.role === "custom" && !membership.customRole)) {
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
}
if (membership.role === "admin") return { permission: adminProjectPermissions, membership };
if (membership.role === "member") return { permission: memberProjectPermissions, membership };
if (membership.role === "viewer") return { permission: viewerProjectPermission, membership };
if (membership.role === "custom") {
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions, {
conditionsMatcher
});
return { permission, membership };
}
throw BadRequestError({ message: "User role not found" });
};
+130
View File
@@ -0,0 +1,130 @@
import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability";
import { MembershipOrg } from "../models";
import { IRole } from "../models/role";
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
import { ACCEPTED } from "../variables";
import { conditionsMatcher } from "./ProjectRoleService";
export enum OrgPermissionActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete"
}
export enum OrgPermissionSubjects {
Workspace = "workspace",
Role = "role",
Member = "member",
Settings = "settings",
IncidentAccount = "incident-contact",
Sso = "sso",
Billing = "billing",
SecretScanning = "secret-scanning"
}
export type OrgPermissionSet =
| [OrgPermissionActions.Read, OrgPermissionSubjects.Workspace]
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
| [OrgPermissionActions, OrgPermissionSubjects.Role]
| [OrgPermissionActions, OrgPermissionSubjects.Member]
| [OrgPermissionActions, OrgPermissionSubjects.Settings]
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
| [OrgPermissionActions, OrgPermissionSubjects.Billing];
const buildAdminPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
// ws permissions
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
// role permission
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
can(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
return build({ conditionsMatcher });
};
export const adminPermissions = buildAdminPermission();
const buildMemberPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
return build({ conditionsMatcher });
};
export const memberPermissions = buildMemberPermission();
export const getUserOrgPermissions = async (userId: string, orgId: string) => {
// TODO(akhilmhdh): speed this up by pulling from cache later
const membership = await MembershipOrg.findOne({
user: userId,
organization: orgId,
status: ACCEPTED
})
.populate<{ customRole: IRole & { permissions: RawRuleOf<MongoAbility<OrgPermissionSet>>[] } }>(
"customRole"
)
.exec();
if (!membership || (membership.role === "custom" && !membership.customRole)) {
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
}
if (membership.role === "admin" || membership.role === "owner")
return { permission: adminPermissions, membership };
if (membership.role === "member") return { permission: memberPermissions, membership };
if (membership.role === "custom") {
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions, {
conditionsMatcher
});
return { permission, membership };
}
throw BadRequestError({ message: "User role not found" });
};
+8 -2
View File
@@ -28,7 +28,13 @@ export interface BatchSecretRequest {
} }
export interface BatchSecret { export interface BatchSecret {
_id: string; version?: number;
_id?: string;
user?: string;
environment: string;
workspace?: string;
algorithm?: string;
keyEncoding?: string;
type: "shared" | "personal"; type: "shared" | "personal";
secretName: string; secretName: string;
secretBlindIndex: string; secretBlindIndex: string;
@@ -42,5 +48,5 @@ export interface BatchSecret {
secretCommentIV: string; secretCommentIV: string;
secretCommentTag: string; secretCommentTag: string;
tags: string[]; tags: string[];
folder: string folder: string;
} }
+29 -13
View File
@@ -3,13 +3,7 @@ import crypto from "crypto";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto"; import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
import { EESecretService } from "../../ee/services"; import { EESecretService } from "../../ee/services";
import { import { IPType, ISecretVersion, SecretSnapshot, SecretVersion, TrustedIP } from "../../ee/models";
IPType,
ISecretVersion,
SecretSnapshot,
SecretVersion,
TrustedIP
} from "../../ee/models";
import { import {
AuthMethod, AuthMethod,
BackupPrivateKey, BackupPrivateKey,
@@ -18,6 +12,7 @@ import {
ISecret, ISecret,
Integration, Integration,
IntegrationAuth, IntegrationAuth,
Membership,
Organization, Organization,
Secret, Secret,
SecretBlindIndexData, SecretBlindIndexData,
@@ -30,7 +25,9 @@ import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
import { import {
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8 ENCODING_SCHEME_UTF8,
MEMBER,
VIEWER
} from "../../variables"; } from "../../variables";
import { InternalServerError } from "../errors"; import { InternalServerError } from "../errors";
@@ -582,7 +579,7 @@ export const backfillTrustedIps = async () => {
filter: { filter: {
workspace: Types.ObjectId; workspace: Types.ObjectId;
ipAddress: string; ipAddress: string;
}, };
update: { update: {
workspace: Types.ObjectId; workspace: Types.ObjectId;
ipAddress: string; ipAddress: string;
@@ -590,9 +587,9 @@ export const backfillTrustedIps = async () => {
prefix: number; prefix: number;
isActive: boolean; isActive: boolean;
comment: string; comment: string;
}, };
upsert: boolean; upsert: boolean;
} };
}[] = []; }[] = [];
workspaceIdsToAddTrustedIp.forEach((workspaceId) => { workspaceIdsToAddTrustedIp.forEach((workspaceId) => {
@@ -638,7 +635,7 @@ export const backfillTrustedIps = async () => {
await TrustedIP.bulkWrite(operations); await TrustedIP.bulkWrite(operations);
console.log("Backfill: Trusted IPs complete"); console.log("Backfill: Trusted IPs complete");
} }
} };
export const backfillUserAuthMethods = async () => { export const backfillUserAuthMethods = async () => {
await User.updateMany( await User.updateMany(
@@ -655,7 +652,6 @@ export const backfillUserAuthMethods = async () => {
} }
); );
const documentsToUpdate = await User.find({ const documentsToUpdate = await User.find({
authProvider: { $exists: true }, authProvider: { $exists: true },
authMethods: { $exists: false } authMethods: { $exists: false }
@@ -676,4 +672,24 @@ export const backfillUserAuthMethods = async () => {
} }
); );
} }
};
export const backfillPermission = async () => {
await Membership.updateMany(
{
deniedPermissions: {
$exists: true,
$ne: []
},
role: MEMBER
},
[
{
$set: {
role: VIEWER
} }
}
]
);
console.log("Backfill: Finishing converting old denied permission in workspace to viewers");
};
+7 -8
View File
@@ -11,6 +11,7 @@ import {
backfillBots, backfillBots,
backfillEncryptionMetadata, backfillEncryptionMetadata,
backfillIntegration, backfillIntegration,
backfillPermission,
backfillSecretBlindIndexData, backfillSecretBlindIndexData,
backfillSecretFolders, backfillSecretFolders,
backfillSecretVersions, backfillSecretVersions,
@@ -24,12 +25,7 @@ import {
reencryptBotPrivateKeys, reencryptBotPrivateKeys,
reencryptSecretBlindIndexDataSalts reencryptSecretBlindIndexDataSalts
} from "./reencryptData"; } from "./reencryptData";
import { import { getMongoURL, getNodeEnv, getRedisUrl, getSentryDSN } from "../../config";
getMongoURL,
getNodeEnv,
getRedisUrl,
getSentryDSN
} from "../../config";
import { initializePassport } from "../auth"; import { initializePassport } from "../auth";
/** /**
@@ -43,8 +39,10 @@ import { initializePassport } from "../auth";
* - Re-encrypting data * - Re-encrypting data
*/ */
export const setup = async () => { export const setup = async () => {
if (await getRedisUrl() === undefined || await getRedisUrl() === "") { if ((await getRedisUrl()) === undefined || (await getRedisUrl()) === "") {
console.error("WARNING: Redis is not yet configured. Infisical may not function as expected without it.") console.error(
"WARNING: Redis is not yet configured. Infisical may not function as expected without it."
);
} }
await validateEncryptionKeysConfig(); await validateEncryptionKeysConfig();
@@ -86,6 +84,7 @@ export const setup = async () => {
await backfillServiceTokenMultiScope(); await backfillServiceTokenMultiScope();
await backfillTrustedIps(); await backfillTrustedIps();
await backfillUserAuthMethods(); await backfillUserAuthMethods();
await backfillPermission();
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
// to base64 256-bit ROOT_ENCRYPTION_KEY // to base64 256-bit ROOT_ENCRYPTION_KEY
+19
View File
@@ -0,0 +1,19 @@
import { z } from "zod";
export const GetActionV1 = z.object({
params: z.object({
actionId: z.string().trim()
})
});
export const AddUserActionV1 = z.object({
body: z.object({
action: z.string().trim()
})
});
export const GetUserActionV1 = z.object({
query: z.object({
action: z.string().trim()
})
});
+134
View File
@@ -0,0 +1,134 @@
import { z } from "zod";
export const BeginEmailSignUpV1 = z.object({
body: z.object({
email: z.string().email().trim()
})
});
export const VerifyEmailSignUpV1 = z.object({
body: z.object({
email: z.string().email().trim(),
code: z.string().trim()
})
});
export const Login1V1 = z.object({
body: z.object({
email: z.string().email().trim(),
clientPublicKey: z.string().trim()
})
});
export const Login2V1 = z.object({
body: z.object({
email: z.string().email().trim(),
clientProof: z.string().trim()
})
});
export const Srp1V1 = z.object({
body: z.object({
clientPublicKey: z.string().trim()
})
});
export const ChangePasswordV1 = z.object({
body: z.object({
clientProof: z.string().trim(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
})
});
export const EmailPasswordResetV1 = z.object({
body: z.object({
email: z.string().email().trim()
})
});
export const EmailPasswordResetVerifyV1 = z.object({
body: z.object({
email: z.string().email().trim(),
code: z.string().trim()
})
});
export const CreateBackupPrivateKeyV1 = z.object({
body: z.object({
clientProof: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
iv: z.string().trim(),
tag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
})
});
export const ResetPasswordV1 = z.object({
body: z.object({
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
})
});
export const SendMfaTokenV2 = z.object({
body: z.object({
email: z.string().email().trim()
})
});
export const VerifyMfaTokenV2 = z.object({
body: z.object({
email: z.string().email().trim(),
mfaToken: z.string().trim()
})
});
export const Login1V3 = z.object({
body: z.object({
email: z.string().email().trim(),
providerAuthToken: z.string().trim().optional(),
clientPublicKey: z.string().trim()
})
});
export const Login2V3 = z.object({
body: z.object({
email: z.string().email().trim(),
providerAuthToken: z.string().trim().optional(),
clientProof: z.string().trim()
})
});
export const CompletedAccountSignupV3 = z.object({
body: z.object({
email: z.string().email().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim(),
organizationName: z.string().trim(),
providerAuthToken: z.string().trim().optional().nullish(),
attributionSource: z.string().trim().optional()
})
});
+27 -11
View File
@@ -1,15 +1,10 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import { Bot, IUser } from "../models";
Bot,
IUser,
} from "../models";
import { validateUserClientForWorkspace } from "./user"; import { validateUserClientForWorkspace } from "./user";
import { import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
BotNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
import { AuthData } from "../interfaces/middleware"; import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models"; import { ActorType } from "../ee/models";
import { z } from "zod";
/** /**
* Validate authenticated clients for bot with id [botId] based * Validate authenticated clients for bot with id [botId] based
@@ -22,7 +17,7 @@ import { ActorType } from "../ee/models";
export const validateClientForBot = async ({ export const validateClientForBot = async ({
authData, authData,
botId, botId,
acceptedRoles, acceptedRoles
}: { }: {
authData: AuthData; authData: AuthData;
botId: Types.ObjectId; botId: Types.ObjectId;
@@ -36,12 +31,33 @@ export const validateClientForBot = async ({
await validateUserClientForWorkspace({ await validateUserClientForWorkspace({
user: authData.authPayload as IUser, user: authData.authPayload as IUser,
workspaceId: bot.workspace, workspaceId: bot.workspace,
acceptedRoles, acceptedRoles
}); });
return bot; return bot;
case ActorType.SERVICE: case ActorType.SERVICE:
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: "Failed service token authorization for bot", message: "Failed service token authorization for bot"
}); });
} }
}; };
export const GetBotByWorkspaceIdV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const SetBotActiveStateV1 = z.object({
body: z.object({
isActive: z.boolean(),
botKey: z
.object({
nonce: z.string().trim().optional(),
encryptedKey: z.string().trim().optional()
})
.optional()
}),
params: z.object({
botId: z.string().trim()
})
});

Some files were not shown because too many files have changed in this diff Show More