feat(rbac): added new validation to all routes and permission check to most

This commit is contained in:
Akhil Mohan
2023-08-27 19:53:04 +05:30
parent 520a553ea1
commit 549121f44e
78 changed files with 4518 additions and 3499 deletions

View File

@@ -17,6 +17,8 @@ import {
getJwtRefreshSecret
} from "../../config";
import { ActorType } from "../../ee/models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -32,7 +34,9 @@ declare module "jsonwebtoken" {
* @returns
*/
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({
email
@@ -76,7 +80,10 @@ export const login1 = async (req: Request, res: Response) => {
* @returns
*/
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({
email
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
@@ -194,6 +201,14 @@ export const logout = async (req: Request, res: Response) => {
});
};
export const getCommonPasswords = async (req: Request, res: Response) => {
const commonPasswords = fs
.readFileSync(path.resolve(__dirname, "../../data/" + "common_passwords.txt"), "utf8")
.split("\n");
return res.status(200).send(commonPasswords);
};
export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany(
{

View File

@@ -2,10 +2,19 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import { Bot, BotKey } from "../../models";
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 {
encryptedKey: string;
nonce: string;
encryptedKey: string;
nonce: string;
}
/**
@@ -16,23 +25,30 @@ interface BotKey {
* @returns
*/
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({
workspace: workspaceId,
workspace: workspaceId
});
if (!bot) {
// case: bot doesn't exist for workspace with id [workspaceId]
// -> create a new bot and return it
bot = await createBot({
name: "Infisical Bot",
workspaceId: new Types.ObjectId(workspaceId),
});
// case: bot doesn't exist for workspace with id [workspaceId]
// -> create a new bot and return it
bot = await createBot({
name: "Infisical Bot",
workspaceId: new Types.ObjectId(workspaceId)
});
}
return res.status(200).send({
bot,
bot
});
};
@@ -43,46 +59,69 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
* @returns
*/
export const setBotActiveState = async (req: Request, res: Response) => {
const { isActive, botKey }: { isActive: boolean, botKey: BotKey } = req.body;
if (isActive) {
// bot state set to active -> share workspace key with bot
if (!botKey?.encryptedKey || !botKey?.nonce) {
return res.status(400).send({
message: "Failed to set bot state to active - missing bot key",
});
}
await BotKey.findOneAndUpdate({
workspace: req.bot.workspace,
}, {
encryptedKey: botKey.encryptedKey,
nonce: botKey.nonce,
sender: req.user._id,
bot: req.bot._id,
workspace: req.bot.workspace,
}, {
upsert: true,
new: true,
});
} else {
// case: bot state set to inactive -> delete bot's workspace key
await BotKey.deleteOne({
bot: req.bot._id,
});
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) {
// bot state set to active -> share workspace key with bot
if (!botKey?.encryptedKey || !botKey?.nonce) {
return res.status(400).send({
message: "Failed to set bot state to active - missing bot key"
});
}
const bot = await Bot.findOneAndUpdate({
_id: req.bot._id,
}, {
isActive,
}, {
new: true,
});
if (!bot) throw new Error("Failed to update bot active state");
return res.status(200).send({
bot,
await BotKey.findOneAndUpdate(
{
workspace: bot.workspace
},
{
encryptedKey: botKey.encryptedKey,
nonce: botKey.nonce,
sender: userId,
bot: bot._id,
workspace: bot.workspace
},
{
upsert: true,
new: true
}
);
} else {
// case: bot state set to inactive -> delete bot's workspace key
await BotKey.deleteOne({
bot: bot._id
});
}
const updatedBot = await Bot.findOneAndUpdate(
{
_id: bot._id
},
{
isActive
},
{
new: true
}
);
if (!updatedBot) throw new Error("Failed to update bot active state");
return res.status(200).send({
bot
});
};

View File

@@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import { standardRequest } from "../../config/request";
import { getApps, getTeams, revokeAccess } from "../../integrations";
import { Bot, IntegrationAuth } from "../../models";
import { Bot, IntegrationAuth, Workspace } from "../../models";
import { EventType } from "../../ee/models";
import { IntegrationService } from "../../services";
import { EEAuditLogService } from "../../ee/services";
@@ -18,12 +18,25 @@ import {
getIntegrationOptions as getIntegrationOptionsFunc
} from "../../variables";
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]
*/
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);
if (!integrationAuth)
@@ -31,6 +44,15 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
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({
integrationAuth
});
@@ -51,15 +73,19 @@ export const getIntegrationOptions = async (req: Request, res: Response) => {
* @returns
*/
export const oAuthExchange = async (req: Request, res: Response) => {
const {
workspaceId,
code,
integration,
url
} = req.body;
const {
body: { integration, workspaceId, code, url }
} = await validateRequest(reqValidator.OauthExchangeV1, req);
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) {
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) => {
// TODO: refactor
// TODO: check if access token is valid for each integration
let integrationAuth;
const {
workspaceId,
accessId,
refreshToken,
accessToken,
url,
namespace,
integration
}: {
workspaceId: string;
accessId: string | undefined;
refreshToken: string | undefined;
accessToken: string | undefined;
url: string;
namespace: string;
integration: string;
} = req.body;
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
@@ -169,7 +186,7 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
}
if (!integrationAuth) throw new Error("Failed to save integration access token");
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -195,13 +212,29 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
* @returns
*/
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
const teamId = req.query.teamId as string;
const workspaceSlug = req.query.workspaceSlug as string;
const {
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({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
accessId: req.accessId,
integrationAuth: integrationAuth,
accessToken: accessToken,
accessId: accessId,
...(teamId && { teamId }),
...(workspaceSlug && { workspaceSlug })
});
@@ -218,9 +251,27 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
* @returns
*/
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({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
integrationAuth: integrationAuth,
accessToken: accessToken
});
return res.status(200).send({
@@ -235,7 +286,24 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
* @param res
*/
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 {
ref: string;
@@ -245,9 +313,9 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
const params = new URLSearchParams({
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,
headers: {
Authorization: `Bearer ${req.accessToken}`,
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
@@ -281,7 +349,24 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
* @param res
*/
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 {
node: {
@@ -331,7 +416,7 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
},
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
@@ -357,7 +442,24 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
* @param res
*/
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 {
node: {
@@ -422,7 +524,7 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
},
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
@@ -446,7 +548,6 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
* @returns
*/
export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: Response) => {
interface WorkspaceResponse {
size: number;
page: number;
@@ -466,31 +567,46 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
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[] = [];
let hasNextPage = true;
let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces`
let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces`;
while (hasNextPage) {
const { data }: { data: WorkspaceResponse } = await standardRequest.get(
workspaceUrl,
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Accept-Encoding": "application/json"
}
const { data }: { data: WorkspaceResponse } = await standardRequest.get(workspaceUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
);
});
if (data?.values.length > 0) {
data.values.forEach((workspace) => {
workspaces.push(workspace)
})
workspaces.push(workspace);
});
}
if (data.next) {
workspaceUrl = data.next
workspaceUrl = data.next;
} else {
hasNextPage = false
hasNextPage = false;
}
}
@@ -501,13 +617,30 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
/**
* Return list of secret groups for Northflank project with id [appId]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
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 {
id: string;
name: string;
@@ -515,43 +648,41 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
priority: number;
projectId: string;
}
interface SecretGroup {
name: string;
groupId: string;
}
const secretGroups: SecretGroup[] = [];
if (appId && appId !== "") {
if (appId && appId !== "") {
let page = 1;
const perPage = 10;
let hasMorePages = true;
while(hasMorePages) {
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage),
filter: "all",
filter: "all"
});
const {
data: {
data: {
secrets
}
data: { secrets }
}
} = await standardRequest.get<{ data: { secrets: NorthflankSecretGroup[] }}>(
} = await standardRequest.get<{ data: { secrets: NorthflankSecretGroup[] } }>(
`${INTEGRATION_NORTHFLANK_API_URL}/v1/projects/${appId}/secrets`,
{
params,
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Accept-Encoding": "application/json",
},
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
secrets.forEach((a: any) => {
secretGroups.push({
name: a.name,
@@ -566,11 +697,11 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
page++;
}
}
return res.status(200).send({
secretGroups
});
}
};
/**
* Return list of build configs for TeamCity project with id [appId]
@@ -579,7 +710,23 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
* @returns
*/
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 {
id: string;
@@ -630,30 +777,48 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
* @returns
*/
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
const integrationAuth = await revokeAccess({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
const {
params: { integrationAuthId }
} = 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({
message: "Failed to find integration authorization"
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"
});
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
integration: deletedIntegrationAuth.integration
}
},
{
workspaceId: integrationAuth.workspace
workspaceId: deletedIntegrationAuth.workspace
}
);
return res.status(200).send({
integrationAuth
integrationAuth: deletedIntegrationAuth
});
};

View File

@@ -1,6 +1,6 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Folder, Integration } from "../../models";
import { IWorkspace, Integration, Folder, IntegrationAuth } from "../../models";
import { EventService } from "../../services";
import { eventStartIntegration } from "../../events";
import { getFolderByPath } from "../../services/FolderService";
@@ -8,6 +8,14 @@ import { BadRequestError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models";
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
@@ -17,24 +25,42 @@ import { syncSecretsToActiveIntegrationsQueue } from "../../queues/integrations/
*/
export const createIntegration = async (req: Request, res: Response) => {
const {
integrationAuthId,
app,
appId,
isActive,
sourceEnvironment,
targetEnvironment,
targetEnvironmentId,
targetService,
targetServiceId,
owner,
path,
region,
secretPath,
metadata
} = req.body;
body: {
isActive,
sourceEnvironment,
secretPath,
app,
path,
appId,
owner,
region,
targetService,
targetServiceId,
integrationAuthId,
targetEnvironment,
targetEnvironmentId
}
} = await validateRequest(reqValidator.CreateIntegrationV1, req);
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({
workspace: req.integrationAuth.workspace._id,
workspace: integrationAuth.workspace._id,
environment: sourceEnvironment
});
@@ -42,7 +68,7 @@ export const createIntegration = async (req: Request, res: Response) => {
const folder = getFolderByPath(folders.nodes, secretPath);
if (!folder) {
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
const integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
workspace: integrationAuth.workspace._id,
environment: sourceEnvironment,
isActive,
app,
@@ -64,9 +90,8 @@ export const createIntegration = async (req: Request, res: Response) => {
path,
region,
secretPath,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId),
metadata
integration: integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();
if (integration) {
@@ -120,17 +145,32 @@ export const updateIntegration = async (req: Request, res: Response) => {
// integration has the correct fields populated in [Integration]
const {
environment,
isActive,
app,
appId,
targetEnvironment,
owner, // github-specific integration param
secretPath
} = req.body;
body: {
environment,
isActive,
app,
appId,
targetEnvironment,
owner, // github-specific integration param
secretPath
},
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({
workspace: req.integration.workspace,
workspace: integration.workspace,
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,
@@ -161,18 +201,18 @@ export const updateIntegration = async (req: Request, res: Response) => {
}
);
if (integration) {
if (updatedIntegration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventStartIntegration({
workspaceId: integration.workspace,
workspaceId: updatedIntegration.workspace,
environment
})
});
}
return res.status(200).send({
integration
integration: updatedIntegration
});
};
@@ -183,13 +223,27 @@ export const updateIntegration = async (req: Request, res: Response) => {
* @returns
*/
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
});
if (!integration) throw new Error("Failed to find integration");
if (!deletedIntegration) throw new Error("Failed to find integration");
await EEAuditLogService.createAuditLog(
req.authData,

View File

@@ -4,6 +4,14 @@ import { Key } from "../../models";
import { findMembership } from "../../helpers/membership";
import { EventType } from "../../ee/models";
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
@@ -13,13 +21,21 @@ import { EEAuditLogService } from "../../ee/services";
* @returns
*/
export const uploadKey = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const { key } = req.body;
const {
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
const receiverMembership = await findMembership({
user: key.userId,
workspace: workspaceId,
workspace: workspaceId
});
if (!receiverMembership) {
@@ -31,12 +47,12 @@ export const uploadKey = async (req: Request, res: Response) => {
nonce: key.nonce,
sender: req.user._id,
receiver: key.userId,
workspace: workspaceId,
workspace: workspaceId
}).save();
return res.status(200).send({
message: "Successfully uploaded key to workspace",
});
return res.status(200).send({
message: "Successfully uploaded key to workspace"
});
};
/**
@@ -46,21 +62,23 @@ export const uploadKey = async (req: Request, res: Response) => {
* @returns
*/
export const getLatestKey = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetLatestKeyV1, req);
// get latest key
const latestKey = await Key.find({
workspace: workspaceId,
receiver: req.user._id,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.limit(1)
.populate("sender", "+publicKey");
const resObj: any = {};
const resObj: any = {};
if (latestKey.length > 0) {
resObj["latestKey"] = latestKey[0];
if (latestKey.length > 0) {
resObj["latestKey"] = latestKey[0];
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -73,7 +91,7 @@ export const getLatestKey = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId)
}
);
}
}
return res.status(200).send(resObj);
return res.status(200).send(resObj);
};

View File

@@ -7,6 +7,14 @@ import { sendMail } from "../../helpers/nodemailer";
import { ACCEPTED, ADMIN, MEMBER } from "../../variables";
import { getSiteURL } from "../../config";
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";
/**
* Check that user is a member of workspace with id [workspaceId]
@@ -15,7 +23,10 @@ import { EEAuditLogService } from "../../ee/services";
* @returns
*/
export const validateMembership = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
params: { workspaceId }
} = await validateRequest(reqValidator.ValidateMembershipV1, req);
// validate membership
const membership = await findMembership({
user: req.user._id,
@@ -38,8 +49,10 @@ export const validateMembership = async (req: Request, res: Response) => {
* @returns
*/
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
const membershipToDelete = await Membership.findOne({
_id: membershipId
@@ -49,27 +62,20 @@ export const deleteMembership = async (req: Request, res: Response) => {
throw new Error("Failed to delete workspace membership that doesn't exist");
}
// check if user is a member and admin of the workspace
// whose membership we wish to delete
const membership = await Membership.findOne({
user: req.user._id,
workspace: membershipToDelete.workspace
});
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");
}
const { permission } = await getUserProjectPermissions(
req.user._id,
membershipToDelete.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Member
);
// delete workspace membership
const deletedMembership = await deleteMember({
membershipId: membershipToDelete._id.toString()
});
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -80,7 +86,7 @@ export const deleteMembership = async (req: Request, res: Response) => {
}
},
{
workspaceId: membership.workspace
workspaceId: membershipToDelete.workspace
}
);
@@ -96,43 +102,37 @@ export const deleteMembership = async (req: Request, res: Response) => {
* @returns
*/
export const changeMembershipRole = async (req: Request, res: Response) => {
const { membershipId } = req.params;
const { role } = req.body;
const {
body: { role },
params: { membershipId }
} = await validateRequest(reqValidator.ChangeMembershipRoleV1, req);
if (![ADMIN, MEMBER].includes(role)) {
throw new Error("Failed to validate role");
}
// validate target membership
const membershipToChangeRole = await Membership
.findById(membershipId)
.populate<{ user: IUser }>("user");
const membershipToChangeRole = await Membership.findById(membershipId).populate<{ user: IUser }>(
"user"
);
if (!membershipToChangeRole) {
throw new Error("Failed to find membership to change role");
}
// check if user is a member and admin of target membership's
// workspace
const membership = await findMembership({
user: req.user._id,
workspace: membershipToChangeRole.workspace
});
const { permission } = await getUserProjectPermissions(
req.user._id,
membershipToChangeRole.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
);
if (!membership) {
throw new Error("Failed to validate membership");
}
if (membership.role !== ADMIN) {
// user is not an admin member of the workspace
throw new Error("Insufficient role for changing member roles");
}
const oldRole = membershipToChangeRole.role;
membershipToChangeRole.role = role;
await membershipToChangeRole.save();
await EEAuditLogService.createAuditLog(
req.authData,
{

View File

@@ -14,6 +14,8 @@ import {
getSiteURL
} from "../../config";
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]
@@ -23,7 +25,9 @@ import { ActorType } from "../../ee/models";
* @returns
*/
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");
if (!user || !user?.publicKey) {
@@ -62,7 +66,9 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
* @returns
*/
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");
if (!user || !user?.publicKey) {
@@ -103,8 +109,10 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
*/
export const srp1 = async (req: Request, res: Response) => {
// 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({
email: req.user.email
}).select("+salt +verifier");
@@ -149,16 +157,18 @@ export const srp1 = async (req: Request, res: Response) => {
*/
export const changePassword = async (req: Request, res: Response) => {
const {
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
body: {
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
}
} = await validateRequest(reqValidator.ChangePasswordV1, req);
const user = await User.findOne({
email: req.user.email
@@ -208,10 +218,7 @@ export const changePassword = async (req: Request, res: Response) => {
}
);
if (
req.authData.actor.type === ActorType.USER &&
req.authData.tokenVersionId
) {
if (req.authData.actor.type === ActorType.USER && 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
// requires verifying [clientProof] as part of second step of SRP protocol
// as initiated in /srp1
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = req.body;
const {
body: { clientProof, encryptedPrivateKey, salt, verifier, iv, tag }
} = await validateRequest(reqValidator.CreateBackupPrivateKeyV1, req);
const user = await User.findOne({
email: req.user.email
}).select("+salt +verifier");
@@ -325,15 +333,17 @@ export const getBackupPrivateKey = async (req: Request, res: Response) => {
export const resetPassword = async (req: Request, res: Response) => {
const {
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
body: {
encryptedPrivateKey,
protectedKeyTag,
protectedKey,
protectedKeyIV,
salt,
verifier,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag
}
} = await validateRequest(reqValidator.ResetPasswordV1, req);
await User.findByIdAndUpdate(
req.user._id.toString(),

View File

@@ -1,15 +1,28 @@
import { Request, Response } from "express";
import { isValidScope, validateMembership } from "../../helpers";
import { isValidScope } from "../../helpers";
import { Folder, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import { getFolderWithPathFromId } from "../../services/FolderService";
import { BadRequestError, ResourceNotFoundError,UnauthorizedRequestError } from "../../utils/errors";
import { ADMIN, MEMBER } from "../../variables";
import {
BadRequestError,
ResourceNotFoundError,
UnauthorizedRequestError
} from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";
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) => {
const { workspaceId, environment, folderId, secretImport } = req.body;
const {
body: { workspaceId, environment, folderId, secretImport }
} = await validateRequest(reqValidator.CreateSecretImportV1, req);
const folders = await Folder.findOne({
workspace: workspaceId,
@@ -27,12 +40,19 @@ export const createSecretImport = async (req: Request, res: Response) => {
const { folderPath } = getFolderWithPathFromId(folders.nodes, folderId);
secretPath = folderPath;
}
if (req.authData.authPayload instanceof ServiceTokenData) {
// root check
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
if (!isValidScopeAccess) {
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({
@@ -40,8 +60,10 @@ export const createSecretImport = async (req: Request, res: Response) => {
environment,
folderId
});
const importToSecretPath = folders?getFolderWithPathFromId(folders.nodes, folderId).folderPath:"/";
const importToSecretPath = folders
? getFolderWithPathFromId(folders.nodes, folderId).folderPath
: "/";
if (!importSecDoc) {
const doc = new SecretImport({
@@ -50,7 +72,7 @@ export const createSecretImport = async (req: Request, res: Response) => {
folderId,
imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }]
});
await doc.save();
await EEAuditLogService.createAuditLog(
req.authData,
@@ -84,7 +106,7 @@ export const createSecretImport = async (req: Request, res: Response) => {
secretPath: secretImport.secretPath
});
await importSecDoc.save();
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -108,32 +130,30 @@ 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
// this is because the order decide which import gets overriden
export const updateSecretImport = async (req: Request, res: Response) => {
const { id } = req.params;
const { secretImports } = req.body;
const {
body: { secretImports },
params: { id }
} = await validateRequest(reqValidator.UpdateSecretImportV1, req);
const importSecDoc = await SecretImport.findById(id);
if (!importSecDoc) {
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
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
// check for service token validity
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
if (req.authData.authPayload instanceof ServiceTokenData) {
// token permission check
const isValidScopeAccess = isValidScope(
req.authData.authPayload,
importSecDoc.environment,
@@ -142,23 +162,25 @@ export const updateSecretImport = async (req: Request, res: Response) => {
if (!isValidScopeAccess) {
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;
importSecDoc.imports = secretImports;
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(
req.authData,
@@ -166,7 +188,7 @@ export const updateSecretImport = async (req: Request, res: Response) => {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
importToEnvironment: importSecDoc.environment,
importToSecretPath,
importToSecretPath: secretPath,
secretImportId: importSecDoc._id.toString(),
folderId: importSecDoc.folderId.toString(),
orderBefore,
@@ -181,32 +203,29 @@ export const updateSecretImport = async (req: Request, res: Response) => {
};
export const deleteSecretImport = async (req: Request, res: Response) => {
const { id } = req.params;
const { secretImportEnv, secretImportPath } = req.body;
const {
params: { id },
body: { secretImportEnv, secretImportPath }
} = await validateRequest(reqValidator.DeleteSecretImportV1, req);
const importSecDoc = await SecretImport.findById(id);
if (!importSecDoc) {
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
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
// check for service token validity
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
if (req.authData.authPayload instanceof ServiceTokenData) {
const isValidScopeAccess = isValidScope(
req.authData.authPayload,
importSecDoc.environment,
@@ -215,6 +234,18 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
if (!isValidScopeAccess) {
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(
({ environment, secretPath }) =>
@@ -222,17 +253,6 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
);
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(
req.authData,
{
@@ -243,7 +263,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
importFromEnvironment: secretImportEnv,
importFromSecretPath: secretImportPath,
importToEnvironment: importSecDoc.environment,
importToSecretPath
importToSecretPath: secretPath
}
},
{
@@ -266,19 +286,19 @@ export const getSecretImports = async (req: Request, res: Response) => {
return res.status(200).json({ secretImport: {} });
}
// check for service token validity
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
if (req.authData.authPayload instanceof ServiceTokenData) {
// check for service token validity
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
const isValidScopeAccess = isValidScope(
req.authData.authPayload,
importSecDoc.environment,
@@ -287,6 +307,18 @@ export const getSecretImports = async (req: Request, res: Response) => {
if (!isValidScopeAccess) {
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 });
@@ -308,19 +340,19 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
return res.status(200).json({ secrets: [] });
}
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
if (req.authData.authPayload instanceof ServiceTokenData) {
// check for service token validity
const folders = await Folder.findOne({
workspace: importSecDoc.workspace,
environment: importSecDoc.environment
}).lean();
let secretPath = "/";
if (folders) {
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
secretPath = folderPath;
}
const isValidScopeAccess = isValidScope(
req.authData.authPayload,
importSecDoc.environment,
@@ -329,6 +361,25 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
if (!isValidScopeAccess) {
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(

View File

@@ -1,10 +1,12 @@
import { ForbiddenError, subject } from "@casl/ability";
import { Request, Response } from "express";
import { Types } from "mongoose";
import { EventType, FolderVersion } from "../../ee/models";
import { EEAuditLogService, EESecretService } from "../../ee/services";
import { validateMembership } from "../../helpers/membership";
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 {
appendFolder,
deleteFolderById,
@@ -15,12 +17,20 @@ import {
getParentFromFolderId,
validateFolderName
} from "../../services/FolderService";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { ADMIN, MEMBER } from "../../variables";
import * as reqValidator from "../../validation/folders";
// verify workspace id/environment
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)) {
throw BadRequestError({
message: "Folder name cannot contain spaces. Only underscore and dashes"
@@ -32,8 +42,20 @@ export const createFolder = async (req: Request, res: Response) => {
environment
}).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
if (!folders) {
if (req.authData.authPayload instanceof ServiceTokenData) {
// root check
@@ -62,10 +84,10 @@ export const createFolder = async (req: Request, res: Response) => {
});
await folderVersion.save();
await EESecretService.takeSecretSnapshot({
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment
});
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -86,9 +108,9 @@ export const createFolder = async (req: Request, res: Response) => {
}
const folder = appendFolder(folders.nodes, { folderName, parentFolderId });
await Folder.findByIdAndUpdate(folders._id, folders);
const { folder: parentFolder, folderPath: parentFolderPath } = getFolderWithPathFromId(
folders.nodes,
parentFolderId || "root"
@@ -116,13 +138,13 @@ export const createFolder = async (req: Request, res: Response) => {
await folderVersion.save();
await EESecretService.takeSecretSnapshot({
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId: parentFolderId
});
const {folderPath} = getFolderWithPathFromId(folders.nodes, folder.id);
const { folderPath } = getFolderWithPathFromId(folders.nodes, folder.id);
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -143,8 +165,11 @@ export const createFolder = async (req: Request, res: Response) => {
};
export const updateFolderById = async (req: Request, res: Response) => {
const { folderId } = req.params;
const { name, workspaceId, environment } = req.body;
const {
body: { workspaceId, environment, name },
params: { folderId }
} = await validateRequest(reqValidator.UpdateFolderV1, req);
if (!validateFolderName(name)) {
throw BadRequestError({
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" });
}
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);
if (!parentFolder) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
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) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
@@ -197,13 +222,13 @@ export const updateFolderById = async (req: Request, res: Response) => {
await folderVersion.save();
await EESecretService.takeSecretSnapshot({
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId: parentFolder.id
});
const {folderPath} = getFolderWithPathFromId(folders.nodes, folder.id);
const { folderPath } = getFolderWithPathFromId(folders.nodes, folder.id);
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -228,37 +253,35 @@ export const updateFolderById = async (req: Request, res: Response) => {
};
export const deleteFolder = async (req: Request, res: Response) => {
const { folderId } = req.params;
const { workspaceId, environment } = req.body;
const {
params: { folderId },
body: { environment, workspaceId }
} = await validateRequest(reqValidator.DeleteFolderV1, req);
const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (!folders) {
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);
if (!delOp) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
const { deletedNode: delFolder, parent: parentFolder } = delOp;
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
if (req.authData.authPayload instanceof ServiceTokenData) {
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
if (!isValidScopeAccess) {
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;
@@ -280,7 +303,7 @@ export const deleteFolder = async (req: Request, res: Response) => {
}
await EESecretService.takeSecretSnapshot({
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId: parentFolder.id
});
@@ -288,12 +311,12 @@ export const deleteFolder = async (req: Request, res: Response) => {
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.DELETE_FOLDER ,
type: EventType.DELETE_FOLDER,
metadata: {
environment,
folderId,
folderName: delFolder.name,
folderPath
folderPath: secretPath
}
},
{
@@ -306,28 +329,30 @@ export const deleteFolder = async (req: Request, res: Response) => {
// TODO: validate workspace
export const getFolders = async (req: Request, res: Response) => {
const { workspaceId, environment, parentFolderId, parentFolderPath } = req.query as {
workspaceId: string;
environment: string;
parentFolderId?: string;
parentFolderPath?: string;
};
const {
query: { workspaceId, environment, parentFolderId, parentFolderPath }
} = await validateRequest(reqValidator.GetFoldersV1, req);
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) {
res.send({ folders: [], dir: [] });
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 (parentFolderPath) {
if (req.authData.authPayload instanceof ServiceTokenData) {

View File

@@ -10,6 +10,8 @@ import {
getSmtpConfigured
} from "../../config";
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
@@ -19,7 +21,9 @@ import { validateUserEmail } from "../../validation";
* @returns
*/
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
validateUserEmail(email);
@@ -50,7 +54,9 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
*/
export const verifyEmailSignup = async (req: Request, res: Response) => {
let user;
const { email, code } = req.body;
const {
body: { email, code }
} = await validateRequest(reqValidator.VerifyEmailSignUpV1, req);
// initialize user account
user = await User.findOne({ email }).select("+publicKey");

View File

@@ -1,5 +1,7 @@
import { Request, Response } from "express";
import { validateRequest } from "../../helpers/validation";
import { UserAction } from "../../models";
import * as reqValidator from "../../validation/action";
/**
* Add user action [action]
@@ -8,26 +10,27 @@ import { UserAction } from "../../models";
* @returns
*/
export const addUserAction = async (req: Request, res: Response) => {
// add/record new action [action] for user with id [req.user._id]
const { action } = req.body;
// add/record new action [action] for user with id [req.user._id]
const {
body: { action }
} = await validateRequest(reqValidator.AddUserActionV1, req);
const userAction = await UserAction.findOneAndUpdate(
{
user: req.user._id,
action,
action
},
{ user: req.user._id, action },
{
new: true,
upsert: true,
upsert: true
}
);
return res.status(200).send({
message: "Successfully recorded user action",
userAction,
});
return res.status(200).send({
message: "Successfully recorded user action",
userAction
});
};
/**
@@ -37,15 +40,17 @@ export const addUserAction = async (req: Request, res: Response) => {
* @returns
*/
export const getUserAction = async (req: Request, res: Response) => {
// get user action [action] for user with id [req.user._id]
const action: string = req.query.action as string;
// get user action [action] for user with id [req.user._id]
const {
query: { action }
} = await validateRequest(reqValidator.GetUserActionV1, req);
const userAction = await UserAction.findOne({
user: req.user._id,
action,
action
});
return res.status(200).send({
userAction,
});
return res.status(200).send({
userAction
});
};

View File

@@ -1,16 +1,32 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config";
import { validateMembership } from "../../helpers";
import { Webhook } from "../../models";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";
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) => {
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({
workspace: workspaceId,
environment,
@@ -29,7 +45,7 @@ export const createWebhook = async (req: Request, res: Response) => {
}
await webhook.save();
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -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) => {
const { webhookId } = req.params;
const { isDisabled } = req.body;
const {
body: { isDisabled },
params: { webhookId }
} = await validateRequest(reqValidator.UpdateWebhookV1, req);
const webhook = await Webhook.findById(webhookId);
if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" });
}
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId: webhook.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
const { permission } = await getUserProjectPermissions(
req.user._id,
webhook.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Webhooks
);
if (typeof isDisabled !== undefined) {
webhook.isDisabled = isDisabled;
@@ -97,19 +118,24 @@ export const updateWebhook = 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);
if (!webhook) {
throw ResourceNotFoundError({ message: "Webhook not found!!" });
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: webhook.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
const { permission } = await getUserProjectPermissions(
req.user._id,
webhook.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Webhooks
);
webhook = await Webhook.findByIdAndDelete(webhookId);
if (!webhook) {
@@ -139,17 +165,23 @@ export const deleteWebhook = 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);
if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" });
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: webhook.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
const { permission } = await getUserProjectPermissions(
req.user._id,
webhook.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks
);
try {
await triggerWebhookRequest(
@@ -182,7 +214,15 @@ export const testWebhook = 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> = {};
if (environment) optionalFilters.environment = environment as string;

View File

@@ -20,6 +20,13 @@ import {
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]
@@ -28,7 +35,15 @@ import { ForbiddenError } from "@casl/ability";
* @returns
*/
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 = (
await Membership.find({
@@ -53,7 +68,15 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
* @returns
*/
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({
workspace: workspaceId
@@ -89,7 +112,9 @@ export const getWorkspaces = async (req: Request, res: Response) => {
* @returns
*/
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({
_id: workspaceId
@@ -108,7 +133,9 @@ export const getWorkspace = async (req: Request, res: Response) => {
* @returns
*/
export const createWorkspace = async (req: Request, res: Response) => {
const { workspaceName, organizationId } = req.body;
const {
body: { organizationId, workspaceName }
} = await validateRequest(reqValidator.CreateWorkspaceV1, req);
const organization = await Organization.findById(organizationId);
if (!organization) {
@@ -183,8 +210,16 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
* @returns
*/
export const changeWorkspaceName = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const { name } = req.body;
const {
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(
{
@@ -229,7 +264,15 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
* @returns
*/
export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const authorizations = await IntegrationAuth.find({
workspace: workspaceId
@@ -247,7 +290,16 @@ export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: R
* @returns
*/
export const getWorkspaceServiceTokens = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.ServiceTokens
);
// ?? FIX.
const serviceTokens = await ServiceToken.find({
user: req.user._id,

View File

@@ -10,16 +10,11 @@ import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors";
import {
ACTION_LOGIN,
TOKEN_EMAIL_MFA,
} from "../../variables";
import { ACTION_LOGIN, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import {
getHttpsEnabled,
getJwtMfaLifetime,
getJwtMfaSecret,
} from "../../config";
import { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -34,13 +29,10 @@ declare module "jsonwebtoken" {
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
const {
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email,
email
}).select("+salt +verifier");
if (!user) throw new Error("Failed to find user");
@@ -49,25 +41,28 @@ export const login1 = async (req: Request, res: Response) => {
server.init(
{
salt: user.salt,
verifier: user.verifier,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, {
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false });
await LoginSRPDetail.findOneAndReplace(
{ email: email },
{
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({
serverPublicKey,
salt: user.salt,
salt: user.salt
});
}
);
};
/**
@@ -78,19 +73,22 @@ export const login1 = async (req: Request, res: Response) => {
* @returns
*/
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 user = await User.findOne({
email,
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
email
}).select(
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
);
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) {
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();
@@ -98,7 +96,7 @@ export const login2 = async (req: Request, res: Response) => {
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetail.serverBInt,
b: loginSRPDetail.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
@@ -111,15 +109,15 @@ export const login2 = async (req: Request, res: Response) => {
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString(),
userId: user._id.toString()
},
expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret(),
secret: await getJwtMfaSecret()
});
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email,
email
});
// send MFA code [code] to [email]
@@ -128,27 +126,27 @@ export const login2 = async (req: Request, res: Response) => {
subjectLine: "Infisical MFA code",
recipients: [email],
substitutions: {
code,
},
code
}
});
return res.status(200).send({
mfaEnabled: true,
token,
token
});
}
await checkUserDevice({
user,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
// issue tokens
const tokens = await issueAuthTokens({
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
// store (refresh) token in httpOnly cookie
@@ -156,7 +154,7 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
secure: await getHttpsEnabled()
});
// case: user does not have MFA enabled
@@ -182,36 +180,33 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
}
tag: user.tag
};
if (
user?.protectedKey &&
user?.protectedKeyIV &&
user?.protectedKeyTag
) {
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
response.protectedKey = user.protectedKey;
response.protectedKeyIV = user.protectedKeyIV
response.protectedKeyIV = user.protectedKeyIV;
response.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.ip,
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.ip
}));
return res.status(200).send(response);
}
return res.status(400).send({
message: "Failed to authenticate. Try again?",
message: "Failed to authenticate. Try again?"
});
}
);
@@ -219,15 +214,17 @@ export const login2 = async (req: Request, res: Response) => {
/**
* Send MFA token to email [email]
* @param req
* @param res
* @param req
* @param res
*/
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({
type: TOKEN_EMAIL_MFA,
email,
email
});
// send MFA code [code] to [email]
@@ -236,49 +233,53 @@ export const sendMfaToken = async (req: Request, res: Response) => {
subjectLine: "Infisical MFA code",
recipients: [email],
substitutions: {
code,
},
code
}
});
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
* MFA token [mfaToken] is valid
* @param req
* @param res
* @param req
* @param res
*/
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({
type: TOKEN_EMAIL_MFA,
email,
token: mfaToken,
token: mfaToken
});
const user = await User.findOne({
email,
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
email
}).select(
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
);
if (!user) throw new Error("Failed to find user");
await LoginSRPDetail.deleteOne({ userId: user.id })
await LoginSRPDetail.deleteOne({ userId: user.id });
await checkUserDevice({
user,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
// issue tokens
const tokens = await issueAuthTokens({
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
// store (refresh) token in httpOnly cookie
@@ -286,7 +287,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
secure: await getHttpsEnabled()
});
interface VerifyMfaTokenRes {
@@ -319,8 +320,8 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string,
}
tag: user.tag as string
};
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey;
@@ -330,15 +331,16 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP,
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send(resObj);
}
};

View File

@@ -6,13 +6,21 @@ import {
Secret,
ServiceToken,
ServiceTokenData,
Workspace,
Workspace
} from "../../models";
import { EventType, SecretVersion } from "../../ee/models";
import { EEAuditLogService, EELicenseService } from "../../ee/services";
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
import _ from "lodash";
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
@@ -20,13 +28,18 @@ import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variabl
* @param res
* @returns
*/
export const createWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
export const createWorkspaceEnvironment = async (req: Request, res: Response) => {
const {
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();
if (!workspace) throw WorkspaceNotFoundError();
@@ -39,7 +52,8 @@ export const createWorkspaceEnvironment = async (
// case: number of environments used exceeds the number of environments allowed
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({
name: environmentName,
slug: environmentSlug.toLowerCase(),
slug: environmentSlug.toLowerCase()
});
await workspace.save();
@@ -80,8 +94,8 @@ export const createWorkspaceEnvironment = async (
workspace: workspaceId,
environment: {
name: environmentName,
slug: environmentSlug,
},
slug: environmentSlug
}
});
};
@@ -129,12 +143,18 @@ export const reorderWorkspaceEnvironments = async (
* @param res
* @returns
*/
export const renameWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
export const renameWorkspaceEnvironment = async (req: Request, res: Response) => {
const {
params: { workspaceId },
body: { environmentName, environmentSlug, oldEnvironmentSlug }
} = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req);
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
if (!environmentSlug || !environmentName) {
throw new Error("Invalid environment given.");
@@ -148,16 +168,13 @@ export const renameWorkspaceEnvironment = async (
const isEnvExist = workspace.environments.some(
({ name, slug }) =>
slug !== oldEnvironmentSlug &&
(name === environmentName || slug === environmentSlug)
slug !== oldEnvironmentSlug && (name === environmentName || slug === environmentSlug)
);
if (isEnvExist) {
throw new Error("Invalid environment given");
}
const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === oldEnvironmentSlug
);
const envIndex = workspace?.environments.findIndex(({ slug }) => slug === oldEnvironmentSlug);
if (envIndex === -1) {
throw new Error("Invalid environment given");
}
@@ -191,7 +208,7 @@ export const renameWorkspaceEnvironment = async (
await Membership.updateMany(
{
workspace: workspaceId,
"deniedPermissions.environmentSlug": oldEnvironmentSlug,
"deniedPermissions.environmentSlug": oldEnvironmentSlug
},
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
@@ -218,8 +235,8 @@ export const renameWorkspaceEnvironment = async (
workspace: workspaceId,
environment: {
name: environmentName,
slug: environmentSlug,
},
slug: environmentSlug
}
});
};
@@ -229,21 +246,25 @@ export const renameWorkspaceEnvironment = async (
* @param res
* @returns
*/
export const deleteWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentSlug } = req.body;
export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => {
const {
params: { workspaceId },
body: { environmentSlug }
} = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Environments
);
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error("Failed to create workspace environment");
}
const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === environmentSlug
);
const envIndex = workspace?.environments.findIndex(({ slug }) => slug === environmentSlug);
if (envIndex === -1) {
throw new Error("Invalid environment given");
}
@@ -256,11 +277,11 @@ export const deleteWorkspaceEnvironment = async (
// clean up
await Secret.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
environment: environmentSlug
});
await SecretVersion.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
environment: environmentSlug
});
// await ServiceToken.deleteMany({
@@ -279,7 +300,7 @@ export const deleteWorkspaceEnvironment = async (
await Integration.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
environment: environmentSlug
});
await Membership.updateMany(
{ workspace: workspaceId },
@@ -305,46 +326,48 @@ export const deleteWorkspaceEnvironment = async (
return res.status(200).send({
message: "Successfully deleted environment",
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 (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const workspacesUserIsMemberOf = await Membership.findOne({
workspace: workspaceId,
user: req.user,
})
const { membership: workspacesUserIsMemberOf } = await getUserProjectPermissions(
req.user._id,
workspaceId
);
if (!workspacesUserIsMemberOf) {
throw BadRequestError()
}
const accessibleEnvironments: any = [];
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions;
const accessibleEnvironments: any = []
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions
const relatedWorkspace = await Workspace.findById(workspaceId)
const relatedWorkspace = await Workspace.findById(workspaceId);
if (!relatedWorkspace) {
throw BadRequestError()
throw BadRequestError();
}
relatedWorkspace.environments.forEach(environment => {
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_READ_SECRETS })
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_WRITE_SECRETS })
relatedWorkspace.environments.forEach((environment) => {
const isReadBlocked = _.some(deniedPermission, {
environmentSlug: environment.slug,
ability: PERMISSION_READ_SECRETS
});
const isWriteBlocked = _.some(deniedPermission, {
environmentSlug: environment.slug,
ability: PERMISSION_WRITE_SECRETS
});
if (isReadBlocked && isWriteBlocked) {
return
return;
} else {
accessibleEnvironments.push({
name: environment.name,
slug: environment.slug,
isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked,
})
isReadDenied: isReadBlocked
});
}
})
});
res.json({ accessibleEnvironments })
res.json({ accessibleEnvironments });
};

View File

@@ -25,7 +25,7 @@ import {
userHasWriteOnlyAbility
} from "../../ee/helpers/checkMembershipPermissions";
import _ from "lodash";
import { BatchSecret, BatchSecretRequest } from "../../types/secret";
import { BatchSecret } from "../../types/secret";
import {
getFolderByPath,
getFolderIdFromServiceToken,
@@ -35,6 +35,8 @@ import {
import { isValidScope } from "../../helpers/secrets";
import path from "path";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import { validateRequest } from "../../helpers/validation";
import { BatchSecretsV2, GetSecretsV2 } from "../../validation";
/**
* Peform a batch of any specified CUD secret operations
@@ -46,22 +48,17 @@ export const batchSecrets = async (req: Request, res: Response) => {
const channel = getUserAgentType(req.headers["user-agent"]);
const postHogClient = await TelemetryService.getPostHogClient();
const validatedData = await validateRequest(BatchSecretsV2, req);
const {
workspaceId,
environment,
requests
}: {
workspaceId: string;
environment: string;
requests: BatchSecretRequest[];
} = req.body;
let secretPath = req.body.secretPath as string;
let folderId = req.body.folderId as string;
body: { workspaceId, environment, requests }
} = validatedData;
let {
body: { secretPath, folderId }
} = validatedData;
const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = [];
const deleteSecrets: { _id: Types.ObjectId, secretName: string; }[] = [];
const deleteSecrets: { _id: Types.ObjectId; secretName: string }[] = [];
const actions: IAction[] = [];
// get secret blind index salt
@@ -72,7 +69,11 @@ export const batchSecrets = async (req: Request, res: Response) => {
const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (req.authData.authPayload instanceof ServiceTokenData) {
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
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
@@ -133,7 +134,10 @@ export const batchSecrets = async (req: Request, res: Response) => {
});
break;
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;
}
}
@@ -332,18 +336,19 @@ export const batchSecrets = async (req: Request, res: Response) => {
if (deleteSecrets.length > 0) {
const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id);
const deletedSecretsObj = (await Secret.find({
_id: {
$in: deleteSecretIds
}
}))
.reduce(
(obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}),
{}
);
const deletedSecretsObj = (
await Secret.find({
_id: {
$in: deleteSecretIds
}
})
).reduce(
(obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}),
{}
);
await Secret.deleteMany({
_id: {
@@ -781,10 +786,13 @@ export const getSecrets = async (req: Request, res: Response) => {
}
*/
const { tagSlugs, secretPath, include_imports } = req.query;
let { folderId } = req.query;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const validatedData = await validateRequest(GetSecretsV2, req);
const {
query: { tagSlugs, secretPath, include_imports, workspaceId, environment }
} = validatedData;
let {
query: { folderId }
} = validatedData;
const folders = await Folder.findOne({ workspace: workspaceId, environment });
@@ -926,7 +934,7 @@ export const getSecrets = async (req: Request, res: Response) => {
// TODO(akhilmhdh) - secret-imp change this to org type
let importedSecrets: any[] = [];
if (include_imports === "true") {
if (include_imports) {
importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string);
}

View File

@@ -6,6 +6,15 @@ import { getSaltRounds } from "../../config";
import { BadRequestError } from "../../utils/errors";
import { ActorType, EventType } from "../../ee/models";
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
@@ -63,7 +72,14 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
export const createServiceTokenData = async (req: Request, res: Response) => {
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 secretHash = await bcrypt.hash(secret, await getSaltRounds());
@@ -75,7 +91,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
}
let user;
if (req.authData.actor.type === ActorType.USER) {
user = req.authData.authPayload._id;
}
@@ -100,7 +116,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
if (!serviceTokenData) throw new Error("Failed to find service token data");
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -111,7 +127,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
}
},
{
workspaceId
workspaceId: new Types.ObjectId(workspaceId)
}
);
@@ -128,14 +144,29 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
* @returns
*/
export const deleteServiceTokenData = async (req: Request, res: Response) => {
const { serviceTokenDataId } = req.params;
const {
params: { serviceTokenDataId }
} = await validateRequest(reqValidator.DeleteServiceTokenV2, req);
let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId);
if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" });
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"
});
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
if (!serviceTokenData) return res.status(200).send({
message: "Failed to delete service token"
});
await EEAuditLogService.createAuditLog(
req.authData,
{

View File

@@ -1,42 +1,58 @@
import { ForbiddenError } from "@casl/ability";
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Membership, Secret, Tag } from "../../models";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { Secret, Tag } from "../../models";
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) => {
const { workspaceId } = req.params;
const { name, slug, tagColor } = req.body;
const {
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 = {
name,
tagColor,
workspace: new Types.ObjectId(workspaceId),
slug,
user: new Types.ObjectId(req.user._id),
};
name,
workspace: new Types.ObjectId(workspaceId),
slug,
user: new Types.ObjectId(req.user._id)
};
const createdTag = await new Tag(tagToCreate).save();
res.json(createdTag);
};
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);
if (!tagFromDB) {
throw BadRequestError();
}
// can only delete if the request user is one that belongs to the same workspace as the tag
const membership = await Membership.findOne({
user: req.user,
workspace: tagFromDB.workspace
});
if (!membership) {
UnauthorizedRequestError({ message: "Failed to validate membership" });
}
const { permission } = await getUserProjectPermissions(
req.user._id,
tagFromDB.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Tags
);
const result = await Tag.findByIdAndDelete(tagId);
@@ -47,12 +63,19 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
};
export const getWorkspaceTags = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const workspaceTags = await Tag.find({
workspace: new Types.ObjectId(workspaceId)
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({
workspace: new Types.ObjectId(workspaceId)
});
return res.json({
workspaceTags
});

View File

@@ -2,23 +2,19 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt";
import {
APIKeyData,
AuthMethod,
MembershipOrg,
TokenVersion,
User
} from "../../models";
import { APIKeyData, AuthMethod, MembershipOrg, TokenVersion, User } from "../../models";
import { getSaltRounds } from "../../config";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation";
/**
* Return the current user.
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getMe = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = "Retrieve the current user on the request"
#swagger.description = "Retrieve the current user on the request"
@@ -43,124 +39,117 @@ export const getMe = async (req: Request, res: Response) => {
}
}
*/
const user = await User
.findById(req.user._id)
.select("+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag");
return res.status(200).send({
user,
});
}
const user = await User.findById(req.user._id).select(
"+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag"
);
return res.status(200).send({
user
});
};
/**
* Update the current user's MFA-enabled status [isMfaEnabled].
* Note: Infisical currently only supports email-based 2FA only; this will expand to
* include SMS and authenticator app modes of authentication in the future.
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const updateMyMfaEnabled = async (req: Request, res: Response) => {
const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body;
req.user.isMfaEnabled = isMfaEnabled;
if (isMfaEnabled) {
// TODO: adapt this route/controller
// to work for different forms of MFA
req.user.mfaMethods = ["email"];
} else {
req.user.mfaMethods = [];
}
const {
body: { isMfaEnabled }
} = await validateRequest(reqValidator.UpdateMyMfaEnabledV2, req);
await req.user.save();
const user = req.user;
return res.status(200).send({
user,
});
}
req.user.isMfaEnabled = isMfaEnabled;
if (isMfaEnabled) {
// TODO: adapt this route/controller
// to work for different forms of MFA
req.user.mfaMethods = ["email"];
} else {
req.user.mfaMethods = [];
}
await req.user.save();
const user = req.user;
return res.status(200).send({
user
});
};
/**
* Update name of the current user to [firstName, lastName].
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const updateName = async (req: Request, res: Response) => {
const {
firstName,
lastName
}: {
firstName: string;
lastName: string;
} = req.body;
const user = await User.findByIdAndUpdate(
req.user._id.toString(),
{
firstName,
lastName: lastName ?? ""
},
{
new: true
}
);
return res.status(200).send({
user,
});
}
const {
body: { lastName, firstName }
} = await validateRequest(reqValidator.UpdateNameV2, req);
const user = await User.findByIdAndUpdate(
req.user._id.toString(),
{
firstName,
lastName: lastName ?? ""
},
{
new: true
}
);
return res.status(200).send({
user
});
};
/**
* Update auth method of the current user to [authMethods]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const updateAuthMethods = async (req: Request, res: Response) => {
const {
authMethods
} = req.body;
const hasSamlEnabled = req.user.authMethods
.some(
(authMethod: AuthMethod) => [
AuthMethod.OKTA_SAML,
AuthMethod.AZURE_SAML,
AuthMethod.JUMPCLOUD_SAML
].includes(authMethod)
);
export const updateAuthMethods = async (req: Request, res: Response) => {
const {
body: { authMethods }
} = await validateRequest(reqValidator.UpdateAuthMethodsV2, req);
if (hasSamlEnabled) {
return res.status(400).send({
message: "Failed to update user authentication method because SAML SSO is enforced"
});
}
const hasSamlEnabled = req.user.authMethods.some((authMethod: AuthMethod) =>
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod)
);
const user = await User.findByIdAndUpdate(
req.user._id.toString(),
{
authMethods
},
{
new: true
}
);
return res.status(200).send({
user
if (hasSamlEnabled) {
return res.status(400).send({
message: "Failed to update user authentication method because SAML SSO is enforced"
});
}
}
const user = await User.findByIdAndUpdate(
req.user._id.toString(),
{
authMethods
},
{
new: true
}
);
return res.status(200).send({
user
});
};
/**
* Return organizations that the current user is part of.
* @param req
* @param res
* @param req
* @param res
*/
export const getMyOrganizations = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Return organizations that current user is part of'
#swagger.description = 'Return organizations that current user is part of'
@@ -189,114 +178,121 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
*/
const organizations = (
await MembershipOrg.find({
user: req.user._id,
user: req.user._id
}).populate("organization")
).map((m) => m.organization);
return res.status(200).send({
organizations,
});
}
return res.status(200).send({
organizations
});
};
/**
* Return API keys belonging to current user.
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getMyAPIKeys = async (req: Request, res: Response) => {
const apiKeyData = await APIKeyData.find({
user: req.user._id,
});
const apiKeyData = await APIKeyData.find({
user: req.user._id
});
return res.status(200).send(apiKeyData);
}
return res.status(200).send(apiKeyData);
};
/**
* Create new API key for current user.
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
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 secretHash = await bcrypt.hash(secret, await getSaltRounds());
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
let apiKeyData = await new APIKeyData({
name,
lastUsed: new Date(),
expiresAt,
user: req.user._id,
secretHash,
}).save();
let apiKeyData = await new APIKeyData({
name,
lastUsed: new Date(),
expiresAt,
user: req.user._id,
secretHash
}).save();
// return api key data without sensitive data
apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any;
// return api key data without sensitive data
apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any;
if (!apiKeyData) throw new Error("Failed to find API key data");
if (!apiKeyData) throw new Error("Failed to find API key data");
const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
return res.status(200).send({
apiKey,
apiKeyData,
});
}
return res.status(200).send({
apiKey,
apiKeyData
});
};
/**
* Delete API key with id [apiKeyDataId] belonging to current user
* @param req
* @param res
* @param req
* @param res
*/
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({
_id: new Types.ObjectId(apiKeyDataId),
user: req.user._id
});
const apiKeyData = await APIKeyData.findOneAndDelete({
_id: new Types.ObjectId(apiKeyDataId),
user: req.user._id
});
return res.status(200).send({
apiKeyData
});
}
return res.status(200).send({
apiKeyData
});
};
/**
* Return active sessions (TokenVersion) belonging to user
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getMySessions = async (req: Request, res: Response) => {
const tokenVersions = await TokenVersion.find({
user: req.user._id
});
return res.status(200).send(tokenVersions);
}
const tokenVersions = await TokenVersion.find({
user: req.user._id
});
return res.status(200).send(tokenVersions);
};
/**
* Revoke all active sessions belong to user
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const deleteMySessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({
user: req.user._id,
}, {
$inc: {
refreshVersion: 1,
accessVersion: 1,
},
});
await TokenVersion.updateMany(
{
user: req.user._id
},
{
$inc: {
refreshVersion: 1,
accessVersion: 1
}
}
);
return res.status(200).send({
message: "Successfully revoked all sessions"
});
}
return res.status(200).send({
message: "Successfully revoked all sessions"
});
};

View File

@@ -11,6 +11,14 @@ import { EventService, TelemetryService } from "../../services";
import { eventPushSecrets } from "../../events";
import { EEAuditLogService } from "../../ee/services";
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 {
type: string; // personal or shared
@@ -181,15 +189,17 @@ 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({
workspace: workspaceId,
receiver: req.user._id
}).populate("sender", "+publicKey");
if (!key) throw new Error("Failed to find workspace key");
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -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({
workspace: workspaceId
@@ -329,8 +347,16 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
}
}
*/
const { membershipId } = req.params;
const { role } = req.body;
const {
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(
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);
@@ -413,8 +447,16 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
* @returns
*/
export const toggleAutoCapitalization = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const { autoCapitalization } = req.body;
const {
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(
{

View File

@@ -10,25 +10,20 @@ import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors";
import {
ACTION_LOGIN,
TOKEN_EMAIL_MFA,
} from "../../variables";
import { ACTION_LOGIN, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import {
getHttpsEnabled,
getJwtMfaLifetime,
getJwtMfaSecret,
} from "../../config";
import { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config";
import { AuthMethod } from "../../models/user";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
declare module "jsonwebtoken" {
export interface ProviderAuthJwtPayload extends jwt.JwtPayload {
userId: string;
email: string;
authProvider: AuthMethod;
isUserCompleted: boolean,
}
export interface ProviderAuthJwtPayload extends jwt.JwtPayload {
userId: string;
email: string;
authProvider: AuthMethod;
isUserCompleted: boolean;
}
}
/**
@@ -38,53 +33,51 @@ declare module "jsonwebtoken" {
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
const {
email,
providerAuthToken,
clientPublicKey,
}: {
email: string;
clientPublicKey: string,
providerAuthToken?: string;
} = req.body;
const user = await User.findOne({
email,
}).select("+salt +verifier");
const {
body: { email, clientPublicKey, providerAuthToken }
} = await validateRequest(reqValidator.Login1V3, req);
if (!user) throw new Error("Failed to find user");
if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({
email,
providerAuthToken,
});
}
const user = await User.findOne({
email
}).select("+salt +verifier");
const server = new jsrp.server();
server.init(
if (!user) throw new Error("Failed to find user");
if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({
email,
providerAuthToken
});
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace(
{
salt: user.salt,
verifier: user.verifier,
email: email
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({
email: email,
}, {
email,
userId: user.id,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false });
{
email,
userId: user.id,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({
serverPublicKey,
salt: user.salt,
});
}
);
return res.status(200).send({
serverPublicKey,
salt: user.salt
});
}
);
};
/**
@@ -95,150 +88,151 @@ export const login1 = async (req: Request, res: Response) => {
* @returns
*/
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({
email,
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
const user = await User.findOne({
email
}).select(
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
);
if (!user) throw new Error("Failed to find user");
if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({
email,
providerAuthToken,
})
}
if (!user) throw new Error("Failed to find user");
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({
email,
providerAuthToken
});
}
if (!loginSRPDetail) {
return BadRequestError(Error("Failed to find login details for SRP"))
}
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email });
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetail.serverBInt,
},
async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
if (!loginSRPDetail) {
return BadRequestError(Error("Failed to find login details for SRP"));
}
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetail.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
if (user.isMfaEnabled) {
// case: user has MFA enabled
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
if (user.isMfaEnabled) {
// case: user has MFA enabled
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString(),
},
expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret(),
});
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret()
});
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email,
});
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email
});
// send MFA code [code] to [email]
await sendMail({
template: "emailMfa.handlebars",
subjectLine: "Infisical MFA code",
recipients: [user.email],
substitutions: {
code,
},
});
return res.status(200).send({
mfaEnabled: true,
token,
});
}
await checkUserDevice({
user,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
// case: user does not have MFA enablgged
// return (access) token in response
interface ResponseData {
mfaEnabled: boolean;
encryptionVersion: any;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
}
const response: ResponseData = {
mfaEnabled: false,
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
}
if (
user?.protectedKey &&
user?.protectedKeyIV &&
user?.protectedKeyTag
) {
response.protectedKey = user.protectedKey;
response.protectedKeyIV = user.protectedKeyIV
response.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id,
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP,
});
return res.status(200).send(response);
// send MFA code [code] to [email]
await sendMail({
template: "emailMfa.handlebars",
subjectLine: "Infisical MFA code",
recipients: [user.email],
substitutions: {
code
}
});
return res.status(400).send({
message: "Failed to authenticate. Try again?",
});
return res.status(200).send({
mfaEnabled: true,
token
});
}
);
await checkUserDevice({
user,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? ""
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? ""
});
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled()
});
// case: user does not have MFA enablgged
// return (access) token in response
interface ResponseData {
mfaEnabled: boolean;
encryptionVersion: any;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
}
const response: ResponseData = {
mfaEnabled: false,
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
};
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
response.protectedKey = user.protectedKey;
response.protectedKeyIV = user.protectedKeyIV;
response.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send(response);
}
return res.status(400).send({
message: "Failed to authenticate. Try again?"
});
}
);
};

View File

@@ -9,8 +9,14 @@ import { getAllImportedSecrets } from "../../services/SecretImportService";
import { Folder, IServiceTokenData } from "../../models";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
import { requireWorkspaceAuth } from "../../middleware";
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/secrets";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability";
/**
* Return secrets for workspace with id [workspaceId] and environment
@@ -19,31 +25,29 @@ import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
* @param res
*/
export const getSecretsRaw = async (req: Request, res: Response) => {
let workspaceId = req.query.workspaceId as string;
let environment = req.query.environment as string;
let secretPath = req.query.secretPath as string;
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string;
let {
query: { secretPath, environment, workspaceId, include_imports: includeImports, folderId }
} = await validateRequest(reqValidator.GetSecretsRawV3, req);
// if the service token has single scope, it will get all secrets for that scope by default
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];
secretPath = scope.secretPath;
environment = scope.environment;
workspaceId = serviceTokenDetails.workspace.toString();
} else {
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
});
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
}
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
@@ -56,7 +60,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId)
});
if (includeImports === "true") {
if (includeImports) {
const folders = await Folder.findOne({ workspace: workspaceId, environment });
let folderId = "root";
// if folder exist get it and replace folderid with new one
@@ -99,11 +103,18 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
* @param res
*/
export const getSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const type = req.query.type as "shared" | "personal" | undefined;
const {
query: { secretPath, environment, workspaceId, type },
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
if (req.user._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
}
const secret = await SecretService.getSecret({
secretName,
@@ -132,8 +143,18 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
* @param res
*/
export const createSecretRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretValue, secretComment, secretPath = "/" } = req.body;
const {
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 })
);
}
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
@@ -197,8 +218,18 @@ export const createSecretRaw = async (req: Request, res: Response) => {
* @param res
*/
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretValue, secretPath = "/" } = req.body;
const {
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 })
);
}
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
@@ -211,7 +242,7 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
@@ -243,12 +274,22 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
* @param res
*/
export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretPath = "/" } = req.body;
const {
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 })
);
}
const { secret } = await SecretService.deleteSecret({
secretName,
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
@@ -282,11 +323,17 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
* @param res
*/
export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string;
const {
query: { secretPath, environment, workspaceId, include_imports: includeImports,folderId }
} = await validateRequest(reqValidator.GetSecretsV3, req);
if (req.user._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
}
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
@@ -296,7 +343,7 @@ export const getSecrets = async (req: Request, res: Response) => {
authData: req.authData
});
if (includeImports === "true") {
if (includeImports) {
const folders = await Folder.findOne({ workspace: workspaceId, environment });
let folderId = "root";
// if folder exist get it and replace folderid with new one
@@ -325,11 +372,18 @@ export const getSecrets = async (req: Request, res: Response) => {
* @param res
*/
export const getSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const type = req.query.type as "shared" | "personal" | undefined;
const {
query: { secretPath, environment, workspaceId, type },
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
if (req.user._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
}
const secret = await SecretService.getSecret({
secretName,
@@ -351,23 +405,33 @@ export const getSecretByName = async (req: Request, res: Response) => {
* @param res
*/
export const createSecret = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
secretPath = "/",
metadata
} = req.body;
body: {
workspaceId,
secretName,
secretPath,
environment,
metadata,
type,
secretKeyIV,
secretKeyTag,
secretValueIV,
secretValueTag,
secretCommentIV,
secretCommentTag,
secretKeyCiphertext,
secretValueCiphertext,
secretCommentCiphertext
}
} = 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 })
);
}
const secret = await SecretService.createSecret({
secretName,
@@ -410,20 +474,30 @@ export const createSecret = async (req: Request, res: Response) => {
* @param res
*/
export const updateSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath = "/"
} = req.body;
body: {
secretValueCiphertext,
secretValueTag,
secretValueIV,
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 })
);
}
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
@@ -452,12 +526,22 @@ export const updateSecretByName = async (req: Request, res: Response) => {
* @param res
*/
export const deleteSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretPath = "/" } = req.body;
const {
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 })
);
}
const { secret } = await SecretService.deleteSecret({
secretName,
workspaceId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,

View File

@@ -3,9 +3,7 @@ import { Request, Response } from "express";
import * as Sentry from "@sentry/node";
import { MembershipOrg, User } from "../../models";
import { completeAccount } from "../../helpers/user";
import {
initializeDefaultOrg,
} from "../../helpers/signup";
import { initializeDefaultOrg } from "../../helpers/signup";
import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
import { ACCEPTED, INVITED } from "../../variables";
import { standardRequest } from "../../config/request";
@@ -13,6 +11,8 @@ import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../confi
import { BadRequestError } from "../../utils/errors";
import { TelemetryService } from "../../services";
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
@@ -22,177 +22,174 @@ import { AuthMethod } from "../../models";
* @returns
*/
export const completeAccountSignup = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName,
providerAuthToken,
attributionSource,
}: {
email: string;
firstName: string;
lastName: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
organizationName: string;
providerAuthToken?: string;
attributionSource?: string;
} = req.body;
let user, token;
try {
const {
body: {
email,
publicKey,
salt,
lastName,
verifier,
firstName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
organizationName,
providerAuthToken,
attributionSource,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag
}
} = await validateRequest(reqValidator.CompletedAccountSignupV3, req);
user = await User.findOne({ email });
user = await User.findOne({ email });
if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: "Failed to complete account for complete user",
});
}
if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: "Failed to complete account for complete user"
});
}
if (providerAuthToken) {
await validateProviderAuthToken({
email,
providerAuthToken
});
} else {
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers["authorization"]?.split(" ", 2) ?? [null, null]
if (AUTH_TOKEN_TYPE === null) {
throw BadRequestError({ message: "Missing Authorization Header in the request header." });
}
if (AUTH_TOKEN_TYPE.toLowerCase() !== "bearer") {
throw BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` })
}
if (AUTH_TOKEN_VALUE === null) {
throw BadRequestError({
message: "Missing Authorization Body in the request header",
})
}
if (providerAuthToken) {
await validateProviderAuthToken({
email,
providerAuthToken,
user
});
} else {
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>(
req.headers["authorization"]?.split(" ", 2)
) ?? [null, null];
if (AUTH_TOKEN_TYPE === null) {
throw BadRequestError({ message: "Missing Authorization Header in the request header." });
}
if (AUTH_TOKEN_TYPE.toLowerCase() !== "bearer") {
throw BadRequestError({
message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`
});
}
if (AUTH_TOKEN_VALUE === null) {
throw BadRequestError({
message: "Missing Authorization Body in the request header"
});
}
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret())
);
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret())
);
if (decodedToken.userId !== user.id) {
throw BadRequestError();
}
}
if (decodedToken.userId !== user.id) {
throw BadRequestError();
}
}
// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
});
// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
});
if (!user)
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
if (!user) 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));
if (!hasSamlEnabled) { // TODO: modify this part
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user,
});
}
const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) =>
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod)
);
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED,
},
{
user,
status: ACCEPTED,
}
);
if (!hasSamlEnabled) {
// TODO: modify this part
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user
});
}
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);
token = tokens.token;
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? ""
});
// sending a welcome email to new users
if (await getLoopsApiKey()) {
await standardRequest.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName,
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + (await getLoopsApiKey()),
},
});
}
token = tokens.token;
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
// sending a welcome email to new users
if (await getLoopsApiKey()) {
await standardRequest.post(
"https://app.loops.so/api/v1/events/send",
{
email: email,
eventName: "Sign Up",
firstName: firstName,
lastName: lastName
},
{
headers: {
Accept: "application/json",
Authorization: "Bearer " + (await getLoopsApiKey())
}
}
);
}
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "User Signed Up",
distinctId: email,
properties: {
email,
...(attributionSource ? { attributionSource } : {})
},
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to complete account setup",
});
}
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled()
});
return res.status(200).send({
message: "Successfully set up account",
user,
token,
});
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "User Signed Up",
distinctId: email,
properties: {
email,
...(attributionSource ? { attributionSource } : {})
}
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to complete account setup"
});
}
return res.status(200).send({
message: "Successfully set up account",
user,
token
});
};

View File

@@ -1,90 +1,103 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../helpers/validation";
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]
* are blind-indexed
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req);
const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId),
secretBlindIndex: {
$exists: false,
},
});
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
return res.status(200).send(secretsWithoutBlindIndex === 0);
}
const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId),
secretBlindIndex: {
$exists: false
}
});
return res.status(200).send(secretsWithoutBlindIndex === 0);
};
/**
* Get all secrets for workspace with id [workspaceId]
*/
export const getWorkspaceSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req);
const secrets = await Secret.find({
workspace: new Types.ObjectId (workspaceId),
});
return res.status(200).send({
secrets,
});
}
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({
workspace: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
secrets
});
};
/**
* Update blind indices for secrets in workspace with id [workspaceId]
* @param req
* @param res
* @param req
* @param res
*/
export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
interface SecretToUpdate {
secretName: string;
_id: string;
}
const {
params: { workspaceId },
body: { secretsToUpdate }
} = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req);
const { workspaceId } = req.params;
const {
secretsToUpdate,
}: {
secretsToUpdate: SecretToUpdate[];
} = 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
const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId),
});
// get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId)
});
// update secret blind indices
const operations = await Promise.all(
secretsToUpdate.map(async (secretToUpdate: SecretToUpdate) => {
const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
secretName: secretToUpdate.secretName,
salt,
});
return ({
updateOne: {
filter: {
_id: new Types.ObjectId(secretToUpdate._id),
},
update: {
secretBlindIndex,
},
},
});
})
);
// update secret blind indices
const operations = await Promise.all(
secretsToUpdate.map(async (secretToUpdate) => {
const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
secretName: secretToUpdate.secretName,
salt
});
await Secret.bulkWrite(operations);
return {
updateOne: {
filter: {
_id: new Types.ObjectId(secretToUpdate._id)
},
update: {
secretBlindIndex
}
}
};
})
);
return res.status(200).send({
message: "Successfully named workspace secrets",
});
}
await Secret.bulkWrite(operations);
return res.status(200).send({
message: "Successfully named workspace secrets"
});
};

View File

@@ -1,30 +1,32 @@
import { Request, Response } from "express";
import { Action } from "../../models";
import { ActionNotFoundError } from "../../../utils/errors";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/action";
export const getAction = async (req: Request, res: Response) => {
let action;
try {
const { actionId } = req.params;
action = await Action
.findById(actionId)
.populate([
"payload.secretVersions.oldSecretVersion",
"payload.secretVersions.newSecretVersion",
]);
if (!action) throw ActionNotFoundError({
message: "Failed to find action",
});
let action;
try {
const {
params: { actionId }
} = await validateRequest(reqValidator.GetActionV1, req);
} catch (err) {
throw ActionNotFoundError({
message: "Failed to find action",
});
}
return res.status(200).send({
action,
action = await Action.findById(actionId).populate([
"payload.secretVersions.oldSecretVersion",
"payload.secretVersions.newSecretVersion"
]);
if (!action)
throw ActionNotFoundError({
message: "Failed to find action"
});
} catch (err) {
throw ActionNotFoundError({
message: "Failed to find action"
});
}
}
return res.status(200).send({
action
});
};

View File

@@ -2,27 +2,31 @@ import { Request, Response } from "express";
import { EELicenseService } from "../../services";
import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from "../../../config/request";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/cloudProducts";
/**
* Return available cloud product information.
* Note: Nicely formatted to easily construct a table from
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
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") {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
if (EELicenseService.instanceType === "cloud") {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
return res.status(200).send(data);
}
return res.status(200).send({
head: [],
rows: [],
});
}
return res.status(200).send(data);
}
return res.status(200).send({
head: [],
rows: []
});
};

View File

@@ -1,5 +1,14 @@
import { ForbiddenError, subject } from "@casl/ability";
import { Request, Response } from "express";
import { validateRequest } from "../../../helpers/validation";
import { 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 { EESecretService } from "../../services";
@@ -54,10 +63,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 limit: number = parseInt(req.query.limit as string);
const secret = await Secret.findById(secretId);
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({
secret: secretId
@@ -126,8 +146,28 @@ 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
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment: toBeUpdatedSec.environment })
);
// validate secret version
const oldSecretVersion = await SecretVersion.findOne({

View File

@@ -1,9 +1,13 @@
import { ForbiddenError } from "@casl/ability";
import { Request, Response } from "express";
import { validateRequest } from "../../../helpers/validation";
import {
ISecretVersion,
SecretSnapshot,
TFolderRootVersionSchema,
} from "../../models";
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../../services/ProjectRoleService";
import * as reqValidator from "../../../validation/secretSnapshot";
import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models";
/**
* Return secret snapshot with id [secretSnapshotId]
@@ -12,7 +16,9 @@ import {
* @returns
*/
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)
.lean()
@@ -20,26 +26,36 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
path: "secretVersions",
populate: {
path: "tags",
model: "Tag",
},
model: "Tag"
}
})
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
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;
// to show only the folder required secrets
secretSnapshot.secretVersions = secretSnapshot.secretVersions.filter(
({ folder }) => folder === folderId
);
secretSnapshot.folderVersion =
secretSnapshot?.folderVersion?.nodes?.children?.map(({ id, name }) => ({
secretSnapshot.folderVersion = secretSnapshot?.folderVersion?.nodes?.children?.map(
({ id, name }) => ({
id,
name,
})) as any;
name
})
) as any;
return res.status(200).send({
secretSnapshot,
secretSnapshot
});
};

View File

@@ -22,16 +22,32 @@ import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
import { searchByFolderId } from "../../../services/FolderService";
import { EEAuditLogService, EELicenseService } from "../../services";
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]
* @param req
* @param res
*/
export const getWorkspaceSecretSnapshots = async (
req: Request,
res: Response
) => {
export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return project secret snapshot ids'
#swagger.description = 'Return project secret snapshots ids'
@@ -77,23 +93,28 @@ export const getWorkspaceSecretSnapshots = async (
}
}
*/
const { workspaceId } = req.params;
const { environment, folderId } = req.query;
const {
params: { workspaceId },
query: { environment, folderId, offset, limit }
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const secretSnapshots = await SecretSnapshot.find({
workspace: workspaceId,
environment,
folderId: folderId || "root",
folderId: folderId || "root"
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
return res.status(200).send({
secretSnapshots,
secretSnapshots
});
};
@@ -102,21 +123,26 @@ export const getWorkspaceSecretSnapshots = async (
* @param req
* @param res
*/
export const getWorkspaceSecretSnapshotsCount = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environment, folderId } = req.query;
export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Response) => {
const {
params: { workspaceId },
query: { environment, folderId }
} = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const count = await SecretSnapshot.countDocuments({
workspace: workspaceId,
environment,
folderId: folderId || "root",
folderId: folderId || "root"
});
return res.status(200).send({
count,
count
});
};
@@ -126,10 +152,7 @@ export const getWorkspaceSecretSnapshotsCount = async (
* @param res
* @returns
*/
export const rollbackWorkspaceSecretSnapshot = async (
req: Request,
res: Response
) => {
export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => {
/*
#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.'
@@ -181,19 +204,27 @@ export const rollbackWorkspaceSecretSnapshot = async (
}
*/
const { workspaceId } = req.params;
const { version, environment, folderId = "root" } = req.body;
const {
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
const secretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId,
version,
environment,
folderId: folderId,
folderId: folderId
})
.populate<{ secretVersions: ISecretVersion[] }>({
path: "secretVersions",
select: "+secretBlindIndex",
select: "+secretBlindIndex"
})
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
@@ -202,13 +233,13 @@ export const rollbackWorkspaceSecretSnapshot = async (
const snapshotFolderTree = secretSnapshot.folderVersion;
const latestFolderTree = await Folder.findOne({
workspace: workspaceId,
environment,
environment
});
const latestFolderVersion = await FolderVersion.findOne({
environment,
workspace: workspaceId,
"nodes.id": folderId,
"nodes.id": folderId
}).sort({ "nodes.version": -1 });
const oldSecretVersionsObj: Record<string, ISecretVersion> = {};
@@ -222,8 +253,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
// the parent node from current latest one
// this will be modified according to the snapshot and latest snapshots
const newFolderTree =
latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId);
const newFolderTree = latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId);
if (newFolderTree) {
newFolderTree.children = snapshotFolderTree?.nodes?.children || [];
@@ -252,43 +282,43 @@ export const rollbackWorkspaceSecretSnapshot = async (
workspace: new Types.ObjectId(workspaceId),
environment,
folderId: {
$in: Object.keys(groupByFolderId),
},
},
$in: Object.keys(groupByFolderId)
}
}
};
const sortByFolderIdAndVersion: PipelineStage = {
$sort: { folderId: 1, version: -1 },
$sort: { folderId: 1, version: -1 }
};
const pickLatestVersionOfEachFolder = {
$group: {
_id: "$folderId",
latestVersion: { $first: "$version" },
doc: {
$first: "$$ROOT",
},
},
$first: "$$ROOT"
}
}
};
const populateSecVersion = {
$lookup: {
from: SecretVersion.collection.name,
localField: "doc.secretVersions",
foreignField: "_id",
as: "doc.secretVersions",
},
as: "doc.secretVersions"
}
};
const populateFolderVersion = {
$lookup: {
from: FolderVersion.collection.name,
localField: "doc.folderVersion",
foreignField: "_id",
as: "doc.folderVersion",
},
as: "doc.folderVersion"
}
};
const unwindFolderVerField = {
$unwind: {
path: "$doc.folderVersion",
preserveNullAndEmptyArrays: true,
},
preserveNullAndEmptyArrays: true
}
};
const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> =
await SecretSnapshot.aggregate([
@@ -297,7 +327,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
pickLatestVersionOfEachFolder,
populateSecVersion,
populateFolderVersion,
unwindFolderVerField,
unwindFolderVerField
]);
// recursive snapshotting each level
@@ -327,7 +357,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
// TODO: fix any
const latestSecretVersionIds = await getLatestSecretVersionIds({
secretIds,
secretIds
});
// TODO: fix any
@@ -335,32 +365,31 @@ export const rollbackWorkspaceSecretSnapshot = async (
await SecretVersion.find(
{
_id: {
$in: latestSecretVersionIds.map((s) => s.versionId),
},
$in: latestSecretVersionIds.map((s) => s.versionId)
}
},
"secret version"
)
).reduce(
(accumulator, s) => ({
...accumulator,
[`${s.secret.toString()}`]: s,
[`${s.secret.toString()}`]: s
}),
{}
);
const secDelQuery: Record<string, unknown> = {
workspace: workspaceId,
environment,
environment
// undefined means root thus collect all secrets
};
if (folderId !== "root" && folderIds.length)
secDelQuery.folder = { $in: folderIds };
if (folderId !== "root" && folderIds.length) secDelQuery.folder = { $in: folderIds };
// delete existing secrets
await Secret.deleteMany(secDelQuery);
await Folder.deleteOne({
workspace: workspaceId,
environment,
environment
});
// add secrets
@@ -382,7 +411,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
createdAt,
algorithm,
keyEncoding,
folder: secFolderId,
folder: secFolderId
} = oldSecretVersionsObj[sv];
return {
@@ -405,7 +434,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
createdAt,
algorithm,
keyEncoding,
folder: secFolderId,
folder: secFolderId
};
})
);
@@ -429,7 +458,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
secretValueTag,
algorithm,
keyEncoding,
folder: secFolderId,
folder: secFolderId
}) => ({
_id: new Types.ObjectId(),
secret: _id,
@@ -448,7 +477,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
secretValueTag,
algorithm,
keyEncoding,
folder: secFolderId,
folder: secFolderId
})
)
);
@@ -464,7 +493,7 @@ export const rollbackWorkspaceSecretSnapshot = async (
const newFolderVersion = new FolderVersion({
workspace: workspaceId,
environment,
nodes: newFolderTree,
nodes: newFolderTree
});
await newFolderVersion.save();
}
@@ -473,13 +502,11 @@ export const rollbackWorkspaceSecretSnapshot = async (
await SecretVersion.updateMany(
{
secret: {
$in: Object.keys(oldSecretVersionsObj).map(
(sv) => oldSecretVersionsObj[sv].secret
),
},
$in: Object.keys(oldSecretVersionsObj).map((sv) => oldSecretVersionsObj[sv].secret)
}
},
{
isDeleted: false,
isDeleted: false
}
);
@@ -487,11 +514,11 @@ export const rollbackWorkspaceSecretSnapshot = async (
await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId,
folderId
});
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 limit: number = parseInt(req.query.limit as string);
const sortBy: string = req.query.sortBy as string;
const userId: string = req.query.userId as string;
const actionNames: string = req.query.actionNames as string;
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
);
const logs = await Log.find({
workspace: workspaceId,
@@ -582,10 +612,10 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
...(actionNames
? {
actionNames: {
$in: actionNames.split(","),
},
$in: actionNames.split(",")
}
}
: {}),
: {})
})
.sort({ createdAt: sortBy === "recent" ? -1 : 1 })
.skip(offset)
@@ -594,93 +624,109 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
.populate("user serviceAccount serviceTokenData");
return res.status(200).send({
logs,
logs
});
};
/**
* Return audit logs for workspace with id [workspaceId]
* @param req
* @param res
* @param res
*/
export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const eventType = req.query.eventType;
const userAgentType = req.query.userAgentType;
const actor = req.query.actor as string | undefined;
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 endDate = req.query.endDate as string;
const {
query: { limit, offset, endDate, eventType, startDate, userAgentType, actor },
params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
);
const query = {
workspace: new Types.ObjectId(workspaceId),
...(eventType ? {
"event.type": eventType
} : {}),
...(userAgentType ? {
userAgentType
} : {}),
...(actor ? {
"actor.type": actor.split("-", 2)[0],
...(actor.split("-", 2)[0] === ActorType.USER ? {
"actor.metadata.userId": actor.split("-", 2)[1]
} : {
"actor.metadata.serviceId": actor.split("-", 2)[1]
})
} : {}),
...(startDate || endDate ? {
createdAt: {
...(startDate && { $gte: new Date(startDate) }),
...(endDate && { $lte: new Date(endDate) })
}
} : {})
}
const auditLogs = await AuditLog.find(query)
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
...(eventType
? {
"event.type": eventType
}
: {}),
...(userAgentType
? {
userAgentType
}
: {}),
...(actor
? {
"actor.type": actor.split("-", 2)[0],
...(actor.split("-", 2)[0] === ActorType.USER
? {
"actor.metadata.userId": actor.split("-", 2)[1]
}
: {
"actor.metadata.serviceId": actor.split("-", 2)[1]
})
}
: {}),
...(startDate || endDate
? {
createdAt: {
...(startDate && { $gte: new Date(startDate) }),
...(endDate && { $lte: new Date(endDate) })
}
}
: {})
};
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
const totalCount = await AuditLog.countDocuments(query);
return res.status(200).send({
auditLogs,
totalCount
});
}
};
/**
* Return audit log actor filter options for workspace with id [workspaceId]
* @param req
* @param res
* @param res
*/
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", {
workspace: new Types.ObjectId(workspaceId)
});
const userActors: UserActor[] = (await User.find({
_id: {
$in: userIds
}
})
.select("email"))
.map((user) => ({
const userActors: UserActor[] = (
await User.find({
_id: {
$in: userIds
}
}).select("email")
).map((user) => ({
type: ActorType.USER,
metadata: {
userId: user._id.toString(),
email: user.email
}
}));
const serviceActors: ServiceActor[] = (await ServiceTokenData.find({
workspace: new Types.ObjectId(workspaceId)
})
.select("name"))
.map((serviceTokenData) => ({
const serviceActors: ServiceActor[] = (
await ServiceTokenData.find({
workspace: new Types.ObjectId(workspaceId)
}).select("name")
).map((serviceTokenData) => ({
type: ActorType.SERVICE,
metadata: {
serviceId: serviceTokenData._id.toString(),
@@ -691,50 +737,65 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
return res.status(200).send({
actors: [...userActors, ...serviceActors]
});
}
};
/**
* Return trusted ips for workspace with id [workspaceId]
* @param req
* @param res
* @param res
*/
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({
workspace: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
trustedIps
});
}
};
/**
* Add a trusted ip to workspace with id [workspaceId]
* @param req
* @param res
* @param req
* @param res
*/
export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
ipAddress: ip,
comment,
isActive
} = req.body;
params: { workspaceId },
body: { comment, isActive, ipAddress: ip }
} = await validateRequest(AddWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.IpAllowList
);
const plan = await EELicenseService.getPlan(req.workspace.organization);
if (!plan.ipAllowlisting) return res.status(400).send({
message: "Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
});
if (!plan.ipAllowlisting)
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);
if (!isValidIPOrCidr) return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
if (!isValidIPOrCidr)
return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
const { ipAddress, type, prefix } = extractIPDetails(ip);
const trustedIp = await new TrustedIP({
@@ -743,9 +804,9 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
type,
prefix,
isActive,
comment,
comment
}).save();
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -764,32 +825,40 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
return res.status(200).send({
trustedIp
});
}
};
/**
* Update trusted ip with id [trustedIpId] workspace with id [workspaceId]
* @param req
* @param res
* @param req
* @param res
*/
export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId, trustedIpId } = req.params;
const {
ipAddress: ip,
comment
} = req.body;
params: { workspaceId, trustedIpId },
body: { ipAddress: ip, comment }
} = 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);
if (!plan.ipAllowlisting) return res.status(400).send({
message: "Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range."
});
if (!plan.ipAllowlisting)
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);
if (!isValidIPOrCidr) return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
if (!isValidIPOrCidr)
return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
const { ipAddress, type, prefix } = extractIPDetails(ip);
const updateObject: {
@@ -799,33 +868,34 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
prefix?: number;
$unset?: {
prefix: number;
}
};
} = {
ipAddress,
type,
comment
};
if (prefix !== undefined) {
updateObject.prefix = prefix;
} else {
updateObject.$unset = { prefix: 1 };
}
const trustedIp = await TrustedIP.findOneAndUpdate(
{
_id: new Types.ObjectId(trustedIpId),
workspace: new Types.ObjectId(workspaceId),
workspace: new Types.ObjectId(workspaceId)
},
updateObject,
{
new: true
}
);
if (!trustedIp) return res.status(400).send({
message: "Failed to update trusted IP"
});
if (!trustedIp)
return res.status(400).send({
message: "Failed to update trusted IP"
});
await EEAuditLogService.createAuditLog(
req.authData,
@@ -841,34 +911,45 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
workspaceId: trustedIp.workspace
}
);
return res.status(200).send({
trustedIp
});
}
};
/**
* Delete IP access range from workspace with id [workspaceId]
* @param req
* @param res
* @param req
* @param res
*/
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);
if (!plan.ipAllowlisting) return res.status(400).send({
message: "Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range."
});
if (!plan.ipAllowlisting)
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({
_id: new Types.ObjectId(trustedIpId),
workspace: new Types.ObjectId(workspaceId)
});
if (!trustedIp) return res.status(400).send({
message: "Failed to delete trusted IP"
});
if (!trustedIp)
return res.status(400).send({
message: "Failed to delete trusted IP"
});
await EEAuditLogService.createAuditLog(
req.authData,
@@ -888,4 +969,4 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
return res.status(200).send({
trustedIp
});
}
};

View File

@@ -1,17 +1,8 @@
import express from "express";
const router = express.Router();
import {
validateRequest,
} from "../../../middleware";
import { param } from "express-validator";
import { actionController } from "../../controllers/v1";
// TODO: put into action controller
router.get(
"/:actionId",
param("actionId").exists().trim(),
validateRequest,
actionController.getAction
);
router.get("/:actionId", actionController.getAction);
export default router;
export default router;

View File

@@ -1,21 +1,16 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
validateRequest,
} from "../../../middleware";
import { query } from "express-validator";
import { requireAuth, validateRequest } from "../../../middleware";
import { cloudProductsController } from "../../controllers/v1";
import { AuthMode } from "../../../variables";
router.get(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
query("billing-cycle").exists().isIn(["monthly", "yearly"]),
validateRequest,
cloudProductsController.getCloudProducts
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
validateRequest,
cloudProductsController.getCloudProducts
);
export default router;
export default router;

View File

@@ -1,48 +1,25 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireSecretAuth,
validateRequest,
} from "../../../middleware";
import { body, param, query } from "express-validator";
import { requireAuth } from "../../../middleware";
import { secretController } from "../../controllers/v1";
import {
ADMIN,
AuthMode,
MEMBER,
PERMISSION_READ_SECRETS,
PERMISSION_WRITE_SECRETS
AuthMode
} from "../../../variables";
router.get(
"/:secretId/secret-versions",
requireAuth({
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
"/:secretId/secret-versions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
secretController.getSecretVersions
);
router.post(
"/:secretId/secret-versions/rollback",
requireAuth({
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
"/:secretId/secret-versions/rollback",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
secretController.rollbackSecretVersion
);
export default router;
export default router;

View File

@@ -1,182 +1,85 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../../middleware";
import { body, param, query } from "express-validator";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../../variables";
import { requireAuth } from "../../../middleware";
import { AuthMode } from "../../../variables";
import { workspaceController } from "../../controllers/v1";
import { EventType, UserAgentType } from "../../models";
router.get(
"/:workspaceId/secret-snapshots",
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
);
router.get(
"/:workspaceId/secret-snapshots/count",
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
);
router.post(
"/:workspaceId/secret-snapshots/rollback",
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
);
router.get(
"/:workspaceId/logs",
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
);
router.get(
"/:workspaceId/audit-logs",
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
);
router.get(
"/:workspaceId/audit-logs/filters/actors",
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
);
router.get(
"/:workspaceId/trusted-ips",
param("workspaceId").exists().isString().trim(),
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspaceTrustedIps
);
router.post(
"/:workspaceId/trusted-ips",
param("workspaceId").exists().isString().trim(),
body("ipAddress").exists().isString().trim(),
body("comment").default("").isString().trim(),
body("isActive").exists().isBoolean(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.addWorkspaceTrustedIp
);
router.patch(
"/: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({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.updateWorkspaceTrustedIp
);
router.delete(
"/:workspaceId/trusted-ips/:trustedIpId",
param("workspaceId").exists().isString().trim(),
param("trustedIpId").exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.deleteWorkspaceTrustedIp
);

View File

@@ -160,7 +160,7 @@ export const getIntegrationAuthAccessHelper = async ({
let accessId;
let accessToken;
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)
@@ -209,6 +209,7 @@ export const getIntegrationAuthAccessHelper = async ({
if (!accessToken) throw InternalServerError();
return {
integrationAuth,
accessId,
accessToken
};

View File

@@ -1,6 +1,5 @@
import express from "express";
const router = express.Router();
import { body } from "express-validator";
import { requireAuth, validateRequest } from "../../middleware";
import { authController } from "../../controllers/v1";
import { authLimiter } from "../../helpers/rateLimiter";
@@ -12,9 +11,6 @@ router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login1)
"/login1",
authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(),
body("clientPublicKey").exists().trim().notEmpty(),
validateRequest,
authController.login1
);
@@ -22,9 +18,6 @@ router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login2)
"/login2",
authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(),
body("clientProof").exists().trim().notEmpty(),
validateRequest,
authController.login2
);
@@ -45,6 +38,8 @@ router.post(
authController.checkAuth
);
router.get("/common-passwords", authLimiter, authController.getCommonPasswords);
router.delete(
// TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
"/sessions",

View File

@@ -1,41 +1,25 @@
import express from "express";
const router = express.Router();
import { body, param } from "express-validator";
import {
requireAuth,
requireBotAuth,
requireWorkspaceAuth,
validateRequest,
requireAuth
} from "../../middleware";
import { botController } from "../../controllers/v1";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { AuthMode } from "../../variables";
router.get(
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim().notEmpty(),
validateRequest,
botController.getBotByWorkspaceId
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
botController.getBotByWorkspaceId
);
router.patch(
"/:botId/active",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireBotAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
body("isActive").exists().isBoolean(),
body("botKey"),
validateRequest,
botController.setBotActiveState
"/:botId/active",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
botController.setBotActiveState
);
export default router;
export default router;

View File

@@ -1,45 +1,16 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireIntegrationAuth,
requireIntegrationAuthorizationAuth,
requireWorkspaceAuth,
validateRequest,
requireAuth
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { body, param } from "express-validator";
import { AuthMode } from "../../variables";
import { integrationController } from "../../controllers/v1";
router.post(
"/",
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
);
@@ -48,18 +19,6 @@ router.patch(
requireAuth({
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
);
@@ -68,11 +27,6 @@ router.delete(
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireIntegrationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationId").exists().trim(),
validateRequest,
integrationController.deleteIntegration
);
@@ -81,13 +35,6 @@ router.post(
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
}),
body("environment").isString().exists().trim(),
body("workspaceId").exists().trim(),
validateRequest,
integrationController.manualSync
);

View File

@@ -1,174 +1,95 @@
import express from "express";
const router = express.Router();
import { body, param, query } from "express-validator";
import {
requireAuth,
requireIntegrationAuthorizationAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
import { integrationAuthController } from "../../controllers/v1";
router.get(
"/integration-options",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
integrationAuthController.getIntegrationOptions
"/integration-options",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationOptions
);
router.get(
"/:integrationAuthId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
attachAccessToken: false
}),
param("integrationAuthId"),
validateRequest,
integrationAuthController.getIntegrationAuth
"/:integrationAuthId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuth
);
router.post(
"/oauth-token",
requireAuth({
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
"/oauth-token",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.oAuthExchange
);
router.post(
"/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({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
}),
integrationAuthController.saveIntegrationToken
"/access-token",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
integrationAuthController.saveIntegrationToken
);
router.get(
"/:integrationAuthId/apps",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId"),
query("teamId"),
query("workspaceSlug"),
validateRequest,
integrationAuthController.getIntegrationAuthApps
"/:integrationAuthId/apps",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthApps
);
router.get(
"/:integrationAuthId/teams",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId"),
validateRequest,
integrationAuthController.getIntegrationAuthTeams
"/:integrationAuthId/teams",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthTeams
);
router.get(
"/:integrationAuthId/vercel/branches",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
query("teamId").optional().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthVercelBranches
"/:integrationAuthId/vercel/branches",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthVercelBranches
);
router.get(
"/:integrationAuthId/railway/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthRailwayEnvironments
"/:integrationAuthId/railway/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthRailwayEnvironments
);
router.get(
"/:integrationAuthId/railway/services",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthRailwayServices
"/:integrationAuthId/railway/services",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthRailwayServices
);
router.get(
"/:integrationAuthId/bitbucket/workspaces",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthBitBucketWorkspaces
"/:integrationAuthId/bitbucket/workspaces",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthBitBucketWorkspaces
);
router.get(
"/:integrationAuthId/northflank/secret-groups",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthNorthflankSecretGroups
"/:integrationAuthId/northflank/secret-groups",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthNorthflankSecretGroups
);
router.get(
@@ -176,27 +97,15 @@ router.get(
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
);
router.delete(
"/:integrationAuthId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
attachAccessToken: false,
}),
param("integrationAuthId"),
validateRequest,
integrationAuthController.deleteIntegrationAuth
"/:integrationAuthId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.deleteIntegrationAuth
);
export default router;

View File

@@ -1,43 +1,26 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import { body, param } from "express-validator";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
import { keyController } from "../../controllers/v1";
// TODO endpoint: consider moving these endpoints to be under /workspaces to be more RESTful
router.post(
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("key").exists(),
validateRequest,
keyController.uploadKey
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
keyController.uploadKey
);
router.get( // TODO endpoint: deprecate (note: move frontend to v2/workspace/key or something)
"/:workspaceId/latest",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId"),
validateRequest,
keyController.getLatestKey
router.get(
// TODO endpoint: deprecate (note: move frontend to v2/workspace/key or something)
"/:workspaceId/latest",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
keyController.getLatestKey
);
export default router;

View File

@@ -3,7 +3,6 @@ const router = express.Router();
import { body, param } from "express-validator";
import { requireAuth, validateRequest } from "../../middleware";
import { membershipController } from "../../controllers/v1";
import { membershipController as EEMembershipControllers } from "../../ee/controllers/v1";
import { AuthMode } from "../../variables";
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
@@ -42,16 +41,4 @@ router.post(
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;

View File

@@ -1,93 +1,51 @@
import express from "express";
const router = express.Router();
import { body } from "express-validator";
import { requireAuth, requireSignupAuth, validateRequest } from "../../middleware";
import { requireAuth, requireSignupAuth } from "../../middleware";
import { passwordController } from "../../controllers/v1";
import { passwordLimiter } from "../../helpers/rateLimiter";
import { AuthMode } from "../../variables";
router.post(
"/srp1",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
body("clientPublicKey").exists().isString().trim().notEmpty(),
validateRequest,
passwordController.srp1
"/srp1",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
passwordController.srp1
);
router.post(
"/change-password",
passwordLimiter,
requireAuth({
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
"/change-password",
passwordLimiter,
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
passwordController.changePassword
);
router.post(
"/email/password-reset",
passwordLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(),
validateRequest,
passwordController.emailPasswordReset
);
router.post("/email/password-reset", passwordLimiter, passwordController.emailPasswordReset);
router.post(
"/email/password-reset-verify",
passwordLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(),
body("code").exists().isString().trim().notEmpty(),
validateRequest,
passwordController.emailPasswordResetVerify
"/email/password-reset-verify",
passwordLimiter,
passwordController.emailPasswordResetVerify
);
router.get(
"/backup-private-key",
passwordLimiter,
requireSignupAuth,
passwordController.getBackupPrivateKey
"/backup-private-key",
passwordLimiter,
requireSignupAuth,
passwordController.getBackupPrivateKey
);
router.post(
"/backup-private-key",
passwordLimiter,
requireAuth({
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
"/backup-private-key",
passwordLimiter,
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
passwordController.createBackupPrivateKey
);
router.post(
"/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
);
router.post("/password-reset", requireSignupAuth, passwordController.resetPassword);
export default router;
export default router;

View File

@@ -1,83 +1,46 @@
import express from "express";
import { body, param, query } from "express-validator";
import { secretImportController } from "../../controllers/v1";
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
const router = express.Router();
router.post(
"/",
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
);
router.put(
"/:id",
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
);
router.delete(
"/:id",
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
);
router.get(
"/",
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
);
router.get(
"/secrets",
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
);

View File

@@ -1,66 +1,43 @@
import express from "express";
import { body, param, query } from "express-validator";
import {
createFolder,
deleteFolder,
getFolders,
updateFolderById
} from "../../controllers/v1/secretsFolderController";
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
const router = express.Router();
router.post(
"/",
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
);
router.patch(
"/:folderId",
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
);
router.delete(
"/:folderId",
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
);
router.get(
"/",
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
);

View File

@@ -1,27 +1,21 @@
import express from "express";
const router = express.Router();
import { body } from "express-validator";
import { validateRequest } from "../../middleware";
import { signupController } from "../../controllers/v1";
import { authLimiter } from "../../helpers/rateLimiter";
// TODO: consider moving to users/v3/signup
router.post( // TODO endpoint: consider moving to v3/users/signup/mail
"/email/signup",
authLimiter,
body("email").exists().trim().notEmpty().isEmail(),
validateRequest,
signupController.beginEmailSignup
router.post(
// TODO endpoint: consider moving to v3/users/signup/mail
"/email/signup",
authLimiter,
signupController.beginEmailSignup
);
router.post(
"/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify
authLimiter,
body("email").exists().trim().notEmpty().isEmail(),
body("code").exists().trim().notEmpty(),
validateRequest,
signupController.verifyEmailSignup
"/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify
authLimiter,
signupController.verifyEmailSignup
);
export default router;
export default router;

View File

@@ -1,29 +1,25 @@
import express from "express";
const router = express.Router();
import { requireAuth, validateRequest } from "../../middleware";
import { body, query } from "express-validator";
import { requireAuth } from "../../middleware";
import { userActionController } from "../../controllers/v1";
import { AuthMode } from "../../variables";
// note: [userAction] will be deprecated in /v2 in favor of [action]
router.post( // TODO endpoint: move this into /users/me
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
body("action"),
validateRequest,
userActionController.addUserAction
router.post(
// TODO endpoint: move this into /users/me
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
userActionController.addUserAction
);
router.get(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
query("action"),
validateRequest,
userActionController.getUserAction
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
userActionController.getUserAction
);
export default router;

View File

@@ -1,74 +1,46 @@
import express from "express";
const router = express.Router();
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import { body, param, query } from "express-validator";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
import { webhookController } from "../../controllers/v1";
router.post(
"/",
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
);
router.patch(
"/:webhookId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("webhookId").exists().isString().trim(),
body("isDisabled").default(false).isBoolean(),
validateRequest,
webhookController.updateWebhook
);
router.post(
"/:webhookId/test",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("webhookId").exists().isString().trim(),
validateRequest,
webhookController.testWebhook
);
router.delete(
"/:webhookId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("webhookId").exists().isString().trim(),
validateRequest,
webhookController.deleteWebhook
);
router.get(
"/",
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
);

View File

@@ -1,163 +1,95 @@
import express from "express";
const router = express.Router();
import { body, param } from "express-validator";
import {
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { membershipController, workspaceController } from "../../controllers/v1";
router.get(
"/:workspaceId/keys",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspacePublicKeys
"/:workspaceId/keys",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspacePublicKeys
);
router.get(
"/:workspaceId/users",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceMemberships
"/:workspaceId/users",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspaceMemberships
);
router.get(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
workspaceController.getWorkspaces
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
workspaceController.getWorkspaces
);
router.get(
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspace
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspace
);
router.post(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
body("workspaceName").exists().trim().notEmpty(),
body("organizationId").exists().trim().notEmpty(),
validateRequest,
workspaceController.createWorkspace
);
router.delete(
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.deleteWorkspace
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.deleteWorkspace
);
router.post(
"/:workspaceId/name",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("name").exists().trim().notEmpty(),
validateRequest,
workspaceController.changeWorkspaceName
"/:workspaceId/name",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params"
}),
param("workspaceId").exists().trim(),
body("name").exists().trim().notEmpty(),
validateRequest,
workspaceController.changeWorkspaceName
);
router.post(
"/:workspaceId/invite-signup",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("email").exists().trim().notEmpty(),
validateRequest,
membershipController.inviteUserToWorkspace
"/:workspaceId/invite-signup",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
membershipController.inviteUserToWorkspace
);
router.get(
"/:workspaceId/integrations",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceIntegrations
"/:workspaceId/integrations",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspaceIntegrations
);
router.get(
"/:workspaceId/authorizations",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceIntegrationAuthorizations
"/:workspaceId/authorizations",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspaceIntegrationAuthorizations
);
router.get(
"/:workspaceId/service-tokens", // TODO endpoint: deprecate
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceServiceTokens
"/:workspaceId/service-tokens", // TODO endpoint: deprecate
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspaceServiceTokens
);
export default router;

View File

@@ -5,7 +5,8 @@ import { requireMfaAuth, validateRequest } from "../../middleware";
import { authController } from "../../controllers/v2";
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",
authLimiter,
body("email").isString().trim().notEmpty().toLowerCase(),
@@ -14,7 +15,8 @@ router.post( // TODO: deprecate (moved to api/v3/auth/login1)
authController.login1
);
router.post( // TODO: deprecate (moved to api/v3/auth/login1)
router.post(
// TODO: deprecate (moved to api/v3/auth/login1)
"/login2",
authLimiter,
body("email").isString().trim().notEmpty().toLowerCase(),
@@ -23,22 +25,9 @@ router.post( // TODO: deprecate (moved to api/v3/auth/login1)
authController.login2
);
router.post(
"/mfa/send",
authLimiter,
body("email").isString().trim().notEmpty().isEmail(),
validateRequest,
authController.sendMfaToken
);
//remove above ones after depreciation
router.post("/mfa/send", authLimiter, authController.sendMfaToken);
router.post(
"/mfa/verify",
authLimiter,
requireMfaAuth,
body("email").isString().trim().notEmpty(),
body("mfaToken").isString().trim().notEmpty(),
validateRequest,
authController.verifyMfaToken
);
router.post("/mfa/verify", authLimiter, requireMfaAuth, authController.verifyMfaToken);
export default router;
export default router;

View File

@@ -1,48 +1,22 @@
import express from "express";
const router = express.Router();
import { body, param } from "express-validator";
import { environmentController } from "../../controllers/v2";
import {
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
router.post(
"/:workspaceId/environments",
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
);
router.put(
"/:workspaceId/environments",
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
);
@@ -67,30 +41,17 @@ router.patch(
router.delete(
"/:workspaceId/environments",
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
);
router.get(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
environmentController.getAllAccessibleEnvironmentsOfWorkspace
);
export default router;
export default router;

View File

@@ -1,14 +1,12 @@
import express from "express";
const router = express.Router();
import { Types } from "mongoose";
import {
requireAuth,
requireSecretsAuth,
requireWorkspaceAuth,
validateRequest
} from "../../middleware";
import { validateClientForSecrets } from "../../validation";
import { body, query } from "express-validator";
import { body } from "express-validator";
import { secretsController } from "../../controllers/v2";
import {
ADMIN,
@@ -19,9 +17,9 @@ import {
SECRET_PERSONAL,
SECRET_SHARED
} 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",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
@@ -30,33 +28,11 @@ router.post( // TODO endpoint: strongly consider deprecation in favor of a singl
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
);
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("environment").exists().isString().trim(),
@@ -117,15 +93,9 @@ router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
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({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
@@ -138,7 +108,8 @@ router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
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")
.exists()
@@ -173,7 +144,8 @@ router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
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")
.exists()

View File

@@ -1,18 +1,9 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireServiceTokenDataAuth,
requireWorkspaceAuth,
validateRequest
requireAuth
} from "../../middleware";
import { body, param } from "express-validator";
import {
ADMIN,
AuthMode,
MEMBER,
PERMISSION_WRITE_SECRETS
} from "../../variables";
import { AuthMode } from "../../variables";
import { serviceTokenDataController } from "../../controllers/v2";
router.get(
@@ -28,33 +19,6 @@ router.post(
requireAuth({
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
);
@@ -63,11 +27,6 @@ router.delete(
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireServiceTokenDataAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
param("serviceTokenDataId").exists().trim(),
validateRequest,
serviceTokenDataController.deleteServiceTokenData
);

View File

@@ -1,56 +1,30 @@
import express from "express";
const router = express.Router();
import { body, param } from "express-validator";
import { tagController } from "../../controllers/v2";
import {
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
router.get(
"/:workspaceId/tags",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
tagController.getWorkspaceTags
);
router.delete(
"/tags/:tagId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("tagId").exists().trim(),
validateRequest,
tagController.deleteWorkspaceTag
);
router.post(
"/:workspaceId/tags",
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
);

View File

@@ -1,114 +1,87 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
validateRequest,
} from "../../middleware";
import { body, param } from "express-validator";
import { requireAuth } from "../../middleware";
import { usersController } from "../../controllers/v2";
import { AuthMode } from "../../variables";
import {
AuthMethod
} from "../../models";
router.get(
"/me",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
usersController.getMe
"/me",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
usersController.getMe
);
router.patch(
"/me/mfa",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
body("isMfaEnabled").exists().isBoolean(),
validateRequest,
usersController.updateMyMfaEnabled
"/me/mfa",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
usersController.updateMyMfaEnabled
);
router.patch(
"/me/name",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
body("firstName").exists().isString(),
body("lastName").isString(),
validateRequest,
usersController.updateName
"/me/name",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
usersController.updateName
);
router.put(
"/me/auth-methods",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
body("authMethods").exists().isArray({
min: 1,
}).custom((authMethods: AuthMethod[]) => {
return authMethods.every(provider => [
AuthMethod.EMAIL,
AuthMethod.GOOGLE,
AuthMethod.GITHUB
].includes(provider))
}),
validateRequest,
usersController.updateAuthMethods,
"/me/auth-methods",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
usersController.updateAuthMethods
);
router.get(
"/me/organizations",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
usersController.getMyOrganizations
"/me/organizations",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
usersController.getMyOrganizations
);
router.get(
"/me/api-keys",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.getMyAPIKeys
"/me/api-keys",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.getMyAPIKeys
);
router.post(
"/me/api-keys",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
body("name").exists().isString().trim(),
body("expiresIn").isNumeric(),
validateRequest,
usersController.createAPIKey
"/me/api-keys",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.createAPIKey
);
router.delete(
"/me/api-keys/:apiKeyDataId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
param("apiKeyDataId").exists().trim(),
validateRequest,
usersController.deleteAPIKey
"/me/api-keys/:apiKeyDataId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.deleteAPIKey
);
router.get(
"/me/sessions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.getMySessions
"/me/sessions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.getMySessions
);
router.delete(
"/me/sessions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.deleteMySessions
"/me/sessions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.deleteMySessions
);
export default router;
export default router;

View File

@@ -1,147 +1,96 @@
import express from "express";
const router = express.Router();
import { body, param, query } from "express-validator";
import {
requireAuth,
requireMembershipAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import {
ADMIN,
AuthMode,
MEMBER
} from "../../variables";
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { workspaceController } from "../../controllers/v2";
router.post( // TODO endpoint: deprecate (moved to POST v3/secrets)
"/:workspaceId/secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
body("secrets").exists(),
body("keys").exists(),
body("environment").exists().trim().notEmpty(),
body("channel"),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.pushWorkspaceSecrets
);
router.get( // TODO endpoint: deprecate (moved to GET v3/secrets)
"/:workspaceId/secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
query("environment").exists().trim(),
query("channel"),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.pullSecrets
);
router.get( // TODO endpoint: consider moving to v3/users/me/workspaces/:workspaceId/key
"/:workspaceId/encrypted-key",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceKey
router.post(
// TODO endpoint: deprecate (moved to POST v3/secrets)
"/:workspaceId/secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params"
}),
body("secrets").exists(),
body("keys").exists(),
body("environment").exists().trim().notEmpty(),
body("channel"),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.pushWorkspaceSecrets
);
router.get(
"/:workspaceId/service-token-data",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.getWorkspaceServiceTokenData
// TODO endpoint: deprecate (moved to GET v3/secrets)
"/:workspaceId/secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params"
}),
query("environment").exists().trim(),
query("channel"),
param("workspaceId").exists().trim(),
validateRequest,
workspaceController.pullSecrets
);
router.get( // new - TODO: rewire dashboard to this route
"/:workspaceId/memberships",
param("workspaceId").exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
workspaceController.getWorkspaceMemberships
router.get(
// TODO endpoint: consider moving to v3/users/me/workspaces/:workspaceId/key
"/:workspaceId/encrypted-key",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
workspaceController.getWorkspaceKey
);
router.patch( // TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId",
param("workspaceId").exists().trim(),
param("membershipId").exists().trim(),
body("role").exists().isString().trim().isIn([ADMIN, MEMBER]),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
requireMembershipAuth({
acceptedRoles: [ADMIN],
locationMembershipId: "params",
}),
workspaceController.updateWorkspaceMembership
router.get(
"/:workspaceId/service-token-data",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.getWorkspaceServiceTokenData
);
router.delete( // TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId",
param("workspaceId").exists().trim(),
param("membershipId").exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
requireMembershipAuth({
acceptedRoles: [ADMIN],
locationMembershipId: "params",
}),
workspaceController.deleteWorkspaceMembership
router.get(
// new - TODO: rewire dashboard to this route
"/:workspaceId/memberships",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
workspaceController.getWorkspaceMemberships
);
router.patch(
"/:workspaceId/auto-capitalization",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("autoCapitalization").exists().trim().notEmpty(),
validateRequest,
workspaceController.toggleAutoCapitalization
// TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
workspaceController.updateWorkspaceMembership
);
router.delete(
// TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
workspaceController.deleteWorkspaceMembership
);
router.patch(
"/:workspaceId/auto-capitalization",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspaceController.toggleAutoCapitalization
);
export default router;

View File

@@ -1,29 +1,11 @@
import express from "express";
import { body } from "express-validator";
import { validateRequest } from "../../middleware";
import { authController } from "../../controllers/v3";
import { authLimiter } from "../../helpers/rateLimiter";
const router = express.Router();
router.post(
"/login1",
authLimiter,
body("email").isString().trim().toLowerCase(),
body("providerAuthToken").isString().trim().optional({nullable: true}),
body("clientPublicKey").isString().trim().notEmpty(),
validateRequest,
authController.login1
);
router.post("/login1", authLimiter, authController.login1);
router.post(
"/login2",
authLimiter,
body("email").isString().trim().toLowerCase(),
body("providerAuthToken").isString().trim().optional({nullable: true}),
body("clientProof").isString().trim().notEmpty(),
validateRequest,
authController.login2
);
router.post("/login2", authLimiter, authController.login2);
export default router;

View File

@@ -15,36 +15,18 @@ import {
router.get(
"/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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
secretsController.getSecretsRaw
);
// TODO(akhilmhdh): tony please split the requireWorkspaceAuth to multiple middlewares
// IP checking into another one
router.get(
"/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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -60,19 +42,8 @@ router.get(
router.post(
"/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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -88,19 +59,8 @@ router.post(
router.patch(
"/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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -116,18 +76,8 @@ router.patch(
router.delete(
"/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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -149,11 +99,7 @@ router.get(
query("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -169,28 +115,8 @@ router.get(
router.post(
"/: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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -206,18 +132,8 @@ router.post(
router.get(
"/: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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -232,21 +148,8 @@ router.get(
router.patch(
"/: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({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -269,11 +172,7 @@ router.delete(
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN
]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],

View File

@@ -1,30 +1,14 @@
import express from "express";
const router = express.Router();
import { body } from "express-validator";
import { signupController } from "../../controllers/v3";
import { authLimiter } from "../../helpers/rateLimiter";
import { validateRequest } from "../../middleware";
router.post(
"/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup
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,
signupController.completeAccountSignup,
"/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup
authLimiter,
validateRequest,
signupController.completeAccountSignup
);
export default router;

View File

@@ -1,79 +1,37 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import { requireAuth } from "../../middleware";
import { workspacesController } from "../../controllers/v3";
import {
ADMIN,
AuthMode
} from "../../variables";
import { body, param } from "express-validator";
import { AuthMode } from "../../variables";
// -- migration to blind indices endpoints
router.get(
"/:workspaceId/secrets/blind-index-status",
param("workspaceId").exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
workspacesController.getWorkspaceBlindIndexStatus
"/:workspaceId/secrets/blind-index-status",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspacesController.getWorkspaceBlindIndexStatus
);
router.get( // allow admins to get all workspace secrets (part of blind indices migration)
"/:workspaceId/secrets",
param("workspaceId").exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
workspacesController.getWorkspaceSecrets
router.get(
// allow admins to get all workspace secrets (part of blind indices migration)
"/:workspaceId/secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspacesController.getWorkspaceSecrets
);
router.post( // allow admins to name all workspace secrets (part of blind indices migration)
"/: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({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
locationWorkspaceId: "params",
}),
workspacesController.nameWorkspaceSecrets
router.post(
// allow admins to name all workspace secrets (part of blind indices migration)
"/:workspaceId/secrets/names",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
workspacesController.nameWorkspaceSecrets
);
// --
export default router;
export default router;

View File

@@ -1,16 +1,22 @@
import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability";
import {
AbilityBuilder,
ForcedSubject,
MongoAbility,
RawRuleOf,
createMongoAbility
} from "@casl/ability";
import { Membership } from "../models";
import { IRole } from "../models/role";
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
export enum GeneralPermissionActions {
export enum ProjectPermissionActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete"
}
export enum ProjectPermission {
export enum ProjectPermissionSub {
Role = "role",
Member = "member",
Settings = "settings",
@@ -24,96 +30,119 @@ export enum ProjectPermission {
Workspace = "workspace",
Secrets = "secrets",
SecretImports = "secret-imports",
SecretRollback = "secret-rollback",
Folders = "folders"
}
type GenericFields = {
environment: string;
secretPath?: string;
};
export type ProjectPermissionSet =
| [GeneralPermissionActions, ProjectPermission.Secrets]
| [GeneralPermissionActions, ProjectPermission.Folders]
| [GeneralPermissionActions, ProjectPermission.SecretImports]
| [GeneralPermissionActions, ProjectPermission.Role]
| [GeneralPermissionActions, ProjectPermission.Tags]
| [GeneralPermissionActions, ProjectPermission.Member]
| [GeneralPermissionActions, ProjectPermission.Integrations]
| [GeneralPermissionActions, ProjectPermission.Webhooks]
| [GeneralPermissionActions, ProjectPermission.AuditLogs]
| [GeneralPermissionActions, ProjectPermission.Environments]
| [GeneralPermissionActions, ProjectPermission.IpAllowList]
| [GeneralPermissionActions, ProjectPermission.Settings]
| [GeneralPermissionActions, ProjectPermission.ServiceTokens]
| [GeneralPermissionActions.Delete, ProjectPermission.Workspace]
| [GeneralPermissionActions.Edit, ProjectPermission.Workspace];
| [
ProjectPermissionActions,
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & GenericFields)
]
| [
ProjectPermissionActions,
ProjectPermissionSub.Folders | (ForcedSubject<ProjectPermissionSub.Folders> & GenericFields)
]
| [
ProjectPermissionActions,
(
| ProjectPermissionSub.SecretImports
| (ForcedSubject<ProjectPermissionSub.SecretImports> & GenericFields)
)
]
| [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(GeneralPermissionActions.Read, ProjectPermission.Secrets);
can(GeneralPermissionActions.Create, ProjectPermission.Secrets);
can(GeneralPermissionActions.Edit, ProjectPermission.Secrets);
can(GeneralPermissionActions.Delete, ProjectPermission.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
can(GeneralPermissionActions.Read, ProjectPermission.Folders);
can(GeneralPermissionActions.Create, ProjectPermission.Folders);
can(GeneralPermissionActions.Edit, ProjectPermission.Folders);
can(GeneralPermissionActions.Delete, ProjectPermission.Folders);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Folders);
can(GeneralPermissionActions.Read, ProjectPermission.SecretImports);
can(GeneralPermissionActions.Create, ProjectPermission.SecretImports);
can(GeneralPermissionActions.Edit, ProjectPermission.SecretImports);
can(GeneralPermissionActions.Delete, ProjectPermission.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretImports);
can(GeneralPermissionActions.Read, ProjectPermission.Member);
can(GeneralPermissionActions.Create, ProjectPermission.Member);
can(GeneralPermissionActions.Edit, ProjectPermission.Member);
can(GeneralPermissionActions.Delete, ProjectPermission.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
can(GeneralPermissionActions.Read, ProjectPermission.Role);
can(GeneralPermissionActions.Create, ProjectPermission.Role);
can(GeneralPermissionActions.Edit, ProjectPermission.Role);
can(GeneralPermissionActions.Delete, ProjectPermission.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
can(GeneralPermissionActions.Read, ProjectPermission.Integrations);
can(GeneralPermissionActions.Create, ProjectPermission.Integrations);
can(GeneralPermissionActions.Edit, ProjectPermission.Integrations);
can(GeneralPermissionActions.Delete, ProjectPermission.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
can(GeneralPermissionActions.Read, ProjectPermission.Webhooks);
can(GeneralPermissionActions.Create, ProjectPermission.Webhooks);
can(GeneralPermissionActions.Edit, ProjectPermission.Webhooks);
can(GeneralPermissionActions.Delete, ProjectPermission.Webhooks);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
can(GeneralPermissionActions.Read, ProjectPermission.ServiceTokens);
can(GeneralPermissionActions.Create, ProjectPermission.ServiceTokens);
can(GeneralPermissionActions.Edit, ProjectPermission.ServiceTokens);
can(GeneralPermissionActions.Delete, ProjectPermission.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
can(GeneralPermissionActions.Read, ProjectPermission.Settings);
can(GeneralPermissionActions.Create, ProjectPermission.Settings);
can(GeneralPermissionActions.Edit, ProjectPermission.Settings);
can(GeneralPermissionActions.Delete, ProjectPermission.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
can(GeneralPermissionActions.Read, ProjectPermission.Environments);
can(GeneralPermissionActions.Create, ProjectPermission.Environments);
can(GeneralPermissionActions.Edit, ProjectPermission.Environments);
can(GeneralPermissionActions.Delete, ProjectPermission.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
can(GeneralPermissionActions.Read, ProjectPermission.Tags);
can(GeneralPermissionActions.Create, ProjectPermission.Tags);
can(GeneralPermissionActions.Edit, ProjectPermission.Tags);
can(GeneralPermissionActions.Delete, ProjectPermission.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
can(GeneralPermissionActions.Read, ProjectPermission.AuditLogs);
can(GeneralPermissionActions.Create, ProjectPermission.AuditLogs);
can(GeneralPermissionActions.Edit, ProjectPermission.AuditLogs);
can(GeneralPermissionActions.Delete, ProjectPermission.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
can(GeneralPermissionActions.Read, ProjectPermission.IpAllowList);
can(GeneralPermissionActions.Create, ProjectPermission.IpAllowList);
can(GeneralPermissionActions.Edit, ProjectPermission.IpAllowList);
can(GeneralPermissionActions.Delete, ProjectPermission.IpAllowList);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs);
can(GeneralPermissionActions.Edit, ProjectPermission.Workspace);
can(GeneralPermissionActions.Delete, ProjectPermission.IpAllowList);
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.IpAllowList);
return build();
};
@@ -123,31 +152,33 @@ export const adminProjectPermissions = buildAdminPermission();
const buildMemberPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(GeneralPermissionActions.Read, ProjectPermission.Secrets);
can(GeneralPermissionActions.Create, ProjectPermission.Secrets);
can(GeneralPermissionActions.Edit, ProjectPermission.Secrets);
can(GeneralPermissionActions.Delete, ProjectPermission.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
can(GeneralPermissionActions.Read, ProjectPermission.Folders);
can(GeneralPermissionActions.Create, ProjectPermission.Folders);
can(GeneralPermissionActions.Edit, ProjectPermission.Folders);
can(GeneralPermissionActions.Delete, ProjectPermission.Folders);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Folders);
can(GeneralPermissionActions.Read, ProjectPermission.SecretImports);
can(GeneralPermissionActions.Create, ProjectPermission.SecretImports);
can(GeneralPermissionActions.Edit, ProjectPermission.SecretImports);
can(GeneralPermissionActions.Delete, ProjectPermission.SecretImports);
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(GeneralPermissionActions.Read, ProjectPermission.Member);
can(GeneralPermissionActions.Read, ProjectPermission.Role);
can(GeneralPermissionActions.Read, ProjectPermission.Integrations);
can(GeneralPermissionActions.Read, ProjectPermission.Webhooks);
can(GeneralPermissionActions.Read, ProjectPermission.ServiceTokens);
can(GeneralPermissionActions.Read, ProjectPermission.Settings);
can(GeneralPermissionActions.Read, ProjectPermission.Environments);
can(GeneralPermissionActions.Read, ProjectPermission.Tags);
can(GeneralPermissionActions.Read, ProjectPermission.AuditLogs);
can(GeneralPermissionActions.Read, ProjectPermission.IpAllowList);
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();
};
@@ -157,19 +188,20 @@ export const memberProjectPermissions = buildMemberPermission();
const buildViewerPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(GeneralPermissionActions.Read, ProjectPermission.Secrets);
can(GeneralPermissionActions.Read, ProjectPermission.Folders);
can(GeneralPermissionActions.Read, ProjectPermission.SecretImports);
can(GeneralPermissionActions.Read, ProjectPermission.Member);
can(GeneralPermissionActions.Read, ProjectPermission.Role);
can(GeneralPermissionActions.Read, ProjectPermission.Integrations);
can(GeneralPermissionActions.Read, ProjectPermission.Webhooks);
can(GeneralPermissionActions.Read, ProjectPermission.ServiceTokens);
can(GeneralPermissionActions.Read, ProjectPermission.Settings);
can(GeneralPermissionActions.Read, ProjectPermission.Environments);
can(GeneralPermissionActions.Read, ProjectPermission.Tags);
can(GeneralPermissionActions.Read, ProjectPermission.AuditLogs);
can(GeneralPermissionActions.Read, ProjectPermission.IpAllowList);
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();
};

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()
})
});

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().email().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().nullish(),
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()
})
});

View File

@@ -1,15 +1,10 @@
import { Types } from "mongoose";
import {
Bot,
IUser,
} from "../models";
import { Bot, IUser } from "../models";
import { validateUserClientForWorkspace } from "./user";
import {
BotNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models";
import { z } from "zod";
/**
* Validate authenticated clients for bot with id [botId] based
@@ -22,7 +17,7 @@ import { ActorType } from "../ee/models";
export const validateClientForBot = async ({
authData,
botId,
acceptedRoles,
acceptedRoles
}: {
authData: AuthData;
botId: Types.ObjectId;
@@ -30,18 +25,39 @@ export const validateClientForBot = async ({
}) => {
const bot = await Bot.findById(botId);
if (!bot) throw BotNotFoundError();
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
user: authData.authPayload as IUser,
workspaceId: bot.workspace,
acceptedRoles,
acceptedRoles
});
return bot;
case ActorType.SERVICE:
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()
})
});

View File

@@ -0,0 +1,7 @@
import { z } from "zod";
export const GetCloudProductsV1 = z.object({
query: z.object({
"billing-cycle": z.enum(["monthly", "yearly"])
})
});

View File

@@ -0,0 +1,37 @@
import { z } from "zod";
export const CreateWorkspaceEnvironmentV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
environmentSlug: z.string().trim(),
environmentName: z.string().trim()
})
});
export const UpdateWorkspaceEnvironmentV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
environmentSlug: z.string().trim(),
environmentName: z.string().trim(),
oldEnvironmentSlug: z.string().trim()
})
});
export const DeleteWorkspaceEnvironmentV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
environmentSlug: z.string().trim()
})
});
export const GetAllAccessibileEnvironmentsOfWorkspaceV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});

View File

@@ -0,0 +1,40 @@
import { z } from "zod";
export const CreateFolderV1 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
folderName: z.string().trim(),
parentFolderId: z.string().trim().optional()
})
});
export const UpdateFolderV1 = z.object({
params: z.object({
folderId: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim()
})
});
export const DeleteFolderV1 = z.object({
params: z.object({
folderId: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim()
})
});
export const GetFoldersV1 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
parentFolderId: z.string().trim().optional(),
parentFolderPath: z.string().trim().optional()
})
});

View File

@@ -1,18 +1,15 @@
import { Types } from "mongoose";
import {
IUser,
Integration,
IntegrationAuth,
} from "../models";
import { IUser, Integration, IntegrationAuth } from "../models";
import { validateUserClientForWorkspace } from "./user";
import { IntegrationService } from "../services";
import {
IntegrationAuthNotFoundError,
IntegrationNotFoundError,
UnauthorizedRequestError,
IntegrationAuthNotFoundError,
IntegrationNotFoundError,
UnauthorizedRequestError
} from "../utils/errors";
import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models";
import { z } from "zod";
/**
* Validate authenticated clients for integration with id [integrationId] based
@@ -25,42 +22,80 @@ import { ActorType } from "../ee/models";
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
*/
export const validateClientForIntegration = async ({
authData,
integrationId,
acceptedRoles,
authData,
integrationId,
acceptedRoles
}: {
authData: AuthData;
integrationId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
authData: AuthData;
integrationId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
}) => {
const integration = await Integration.findById(integrationId);
if (!integration) throw IntegrationNotFoundError();
const integration = await Integration.findById(integrationId);
if (!integration) throw IntegrationNotFoundError();
const integrationAuth = await IntegrationAuth
.findById(integration.integrationAuth)
.select(
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt"
);
if (!integrationAuth) throw IntegrationAuthNotFoundError();
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth).select(
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt"
);
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id,
})).accessToken;
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
user: authData.authPayload as IUser,
workspaceId: integration.workspace,
acceptedRoles,
});
return ({ integration, accessToken });
case ActorType.SERVICE:
throw UnauthorizedRequestError({
message: "Failed service token authorization for integration",
});
}
}
if (!integrationAuth) throw IntegrationAuthNotFoundError();
const accessToken = (
await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id
})
).accessToken;
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
user: authData.authPayload as IUser,
workspaceId: integration.workspace,
acceptedRoles
});
return { integration, accessToken };
case ActorType.SERVICE:
throw UnauthorizedRequestError({
message: "Failed service token authorization for integration"
});
}
};
export const CreateIntegrationV1 = z.object({
body: z.object({
integrationAuthId: z.string().trim(),
app: z.string().trim(),
isActive: z.boolean(),
appId: z.string().trim(),
secretPath: z.string().trim().default("/"),
sourceEnvironment: z.string().trim(),
targetEnvironment: z.string().trim(),
targetEnvironmentId: z.string().trim(),
targetService: z.string().trim(),
targetServiceId: z.string().trim(),
owner: z.string().trim(),
path: z.string().trim(),
region: z.string().trim()
})
});
export const UpdateIntegrationV1 = z.object({
params: z.object({
integrationId: z.string().trim()
}),
body: z.object({
app: z.string().trim(),
appId: z.string().trim(),
isActive: z.boolean(),
secretPath: z.string().trim().default("/"),
targetEnvironment: z.string().trim(),
owner: z.string().trim(),
environment: z.string().trim()
})
});
export const DeleteIntegrationV1 = z.object({
params: z.object({
integrationId: z.string().trim()
})
});

View File

@@ -1,17 +1,11 @@
import { Types } from "mongoose";
import {
IUser,
IWorkspace,
IntegrationAuth,
} from "../models";
import {
IntegrationAuthNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
import { IUser, IWorkspace, IntegrationAuth } from "../models";
import { IntegrationAuthNotFoundError, UnauthorizedRequestError } from "../utils/errors";
import { IntegrationService } from "../services";
import { validateUserClientForWorkspace } from "./user";
import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models";
import { z } from "zod";
/**
* Validate authenticated clients for integration authorization with id [integrationAuthId] based
@@ -22,53 +16,147 @@ import { ActorType } from "../ee/models";
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
*/
const validateClientForIntegrationAuth = async ({
authData,
integrationAuthId,
acceptedRoles,
attachAccessToken,
const validateClientForIntegrationAuth = async ({
authData,
integrationAuthId,
acceptedRoles,
attachAccessToken
}: {
authData: AuthData;
integrationAuthId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
attachAccessToken?: boolean;
authData: AuthData;
integrationAuthId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
attachAccessToken?: boolean;
}) => {
const integrationAuth = await IntegrationAuth.findById(integrationAuthId)
.populate<{ workspace: IWorkspace }>("workspace")
.select(
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt"
);
const integrationAuth = await IntegrationAuth
.findById(integrationAuthId)
.populate<{ workspace: IWorkspace }>("workspace")
.select(
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt"
);
if (!integrationAuth) throw IntegrationAuthNotFoundError();
let accessToken, accessId;
if (attachAccessToken) {
const access = (await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id,
}));
accessToken = access.accessToken;
accessId = access.accessId;
}
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
user: authData.authPayload as IUser,
workspaceId: integrationAuth.workspace._id,
acceptedRoles,
});
if (!integrationAuth) throw IntegrationAuthNotFoundError();
return ({ integrationAuth, accessToken, accessId });
case ActorType.SERVICE:
throw UnauthorizedRequestError({
message: "Failed service token authorization for integration authorization",
});
}
}
let accessToken, accessId;
if (attachAccessToken) {
const access = await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id
});
export {
validateClientForIntegrationAuth,
};
accessToken = access.accessToken;
accessId = access.accessId;
}
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
user: authData.authPayload as IUser,
workspaceId: integrationAuth.workspace._id,
acceptedRoles
});
return { integrationAuth, accessToken, accessId };
case ActorType.SERVICE:
throw UnauthorizedRequestError({
message: "Failed service token authorization for integration authorization"
});
}
};
export const GetIntegrationAuthV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
})
});
export const OauthExchangeV1 = z.object({
body: z.object({
workspaceId: z.string().trim(),
code: z.string().trim(),
integration: z.string().trim(),
url: z.string().trim().url().optional(),
})
});
export const SaveIntegrationAccessTokenV1 = z.object({
body: z.object({
workspaceId: z.string().trim(),
accessId: z.string().trim().optional(),
accessToken: z.string().trim().optional(),
url: z.string().url().trim(),
namespace: z.string().trim(),
integration: z.string().trim(),
refreshToken:z.string().trim().optional()
})
});
export const GetIntegrationAuthAppsV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
}),
query: z.object({
teamId: z.string().trim(),
workspaceSlug: z.string().trim()
})
});
export const GetIntegrationAuthTeamsV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
})
});
export const GetIntegrationAuthVercelBranchesV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
}),
query: z.object({
appId: z.string().trim()
})
});
export const GetIntegrationAuthRailwayEnvironmentsV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
}),
query: z.object({
appId: z.string().trim()
})
});
export const GetIntegrationAuthRailwayServicesV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
}),
query: z.object({
appId: z.string().trim()
})
});
export const GetIntegrationAuthBitbucketWorkspacesV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
})
});
export const GetIntegrationAuthNorthflankSecretGroupsV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
}),
query: z.object({
appId: z.string().trim()
})
});
export const DeleteIntegrationAuthV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
})
});
export const GetIntegrationAuthTeamCityBuildConfigsV1 = z.object({
params:z.object({
appId:z.string().trim(),
integrationAuthId:z.string().trim()
})
})
export { validateClientForIntegrationAuth };

View File

@@ -0,0 +1,20 @@
import { z } from "zod";
export const UploadKeyV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
key: z.object({
encryptedKey: z.string().trim(),
nonce: z.string().trim(),
userId: z.string().trim()
})
})
});
export const GetLatestKeyV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});

View File

@@ -1,16 +1,12 @@
import { Types } from "mongoose";
import {
IServiceTokenData,
IUser,
Membership,
} from "../models";
import { IServiceTokenData, IUser, Membership } from "../models";
import { validateUserClientForWorkspace } from "./user";
import { validateServiceTokenDataClientForWorkspace } from "./serviceTokenData";
import {
MembershipNotFoundError,
} from "../utils/errors";
import { MembershipNotFoundError } from "../utils/errors";
import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models";
import { z } from "zod";
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
/**
* Validate authenticated clients for membership with id [membershipId] based
@@ -22,36 +18,64 @@ import { ActorType } from "../ee/models";
* @returns {Membership} - validated membership
*/
export const validateClientForMembership = async ({
authData,
membershipId,
acceptedRoles,
authData,
membershipId,
acceptedRoles
}: {
authData: AuthData;
membershipId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
authData: AuthData;
membershipId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
}) => {
const membership = await Membership.findById(membershipId);
if (!membership) throw MembershipNotFoundError({
message: "Failed to find membership",
});
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
user: authData.authPayload as IUser,
workspaceId: membership.workspace,
acceptedRoles,
});
return membership;
case ActorType.SERVICE:
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(membership.workspace),
});
return membership;
}
}
const membership = await Membership.findById(membershipId);
if (!membership)
throw MembershipNotFoundError({
message: "Failed to find membership"
});
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
user: authData.authPayload as IUser,
workspaceId: membership.workspace,
acceptedRoles
});
return membership;
case ActorType.SERVICE:
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: authData.authPayload as IServiceTokenData,
workspaceId: new Types.ObjectId(membership.workspace)
});
return membership;
}
};
export const ValidateMembershipV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const DeleteMembershipV1 = z.object({
params: z.object({
membershipId: z.string().trim()
})
});
export const ChangeMembershipRoleV1 = z.object({
body: z.object({
role: z.enum([ADMIN, VIEWER, MEMBER, CUSTOM])
}),
params: z.object({ membershipId: z.string().trim() })
});
export const DenyMembershipPermissionV1 = z.object({
params: z.object({
membershipId: z.string().trim()
}),
body: z.object({
permissions: z.object({}).array()
})
});

View File

@@ -197,3 +197,11 @@ export const DeleteOrgMemberv2 = z.object({
export const GetOrgWorkspacesv2 = z.object({
params: z.object({ organizationId: z.string().trim() })
});
export const VerfiyUserToOrganizationV1 = z.object({
body: z.object({
email: z.string().trim().email(),
organizationId: z.string().trim(),
code: z.string().trim()
})
});

View File

@@ -0,0 +1,53 @@
import { z } from "zod";
export const CreateSecretImportV1 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
folderId: z.string().trim().default("root"),
secretImport: z.object({
environment: z.string().trim(),
secretPath: z.string().trim()
})
})
});
export const UpdateSecretImportV1 = z.object({
params: z.object({
id: z.string().trim()
}),
body: z.object({
secretImports: z
.object({
environment: z.string().trim(),
secretPath: z.string().trim()
})
.array()
})
});
export const DeleteSecretImportV1 = z.object({
params: z.object({
id: z.string().trim()
}),
body: z.object({
secretImportPath: z.string().trim(),
secretImportEnv: z.string().trim()
})
});
export const GetSecretImportsV1 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
folderId: z.string().trim()
})
});
export const GetAllSecretsFromImportV1 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
folderId: z.string().trim().default("root")
})
});

View File

@@ -0,0 +1,7 @@
import { z } from "zod";
export const GetSecretSnapshotV1 = z.object({
params: z.object({
secretSnapshotId: z.string().trim()
})
});

View File

@@ -1,18 +1,15 @@
import { Types } from "mongoose";
import {
ISecret,
IServiceTokenData,
IUser,
Secret,
} from "../models";
import { ISecret, IServiceTokenData, IUser, Secret } from "../models";
import { validateUserClientForSecret, validateUserClientForSecrets } from "./user";
import { validateServiceTokenDataClientForSecrets, validateServiceTokenDataClientForWorkspace } from "./serviceTokenData";
import {
BadRequestError,
SecretNotFoundError,
} from "../utils/errors";
validateServiceTokenDataClientForSecrets,
validateServiceTokenDataClientForWorkspace
} from "./serviceTokenData";
import { BadRequestError, SecretNotFoundError } from "../utils/errors";
import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models";
import { z } from "zod";
import { SECRET_PERSONAL, SECRET_SHARED } from "../variables";
/**
* Validate authenticated clients for secrets with id [secretId] based
@@ -24,42 +21,43 @@ import { ActorType } from "../ee/models";
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
*/
export const validateClientForSecret = async ({
authData,
secretId,
acceptedRoles,
requiredPermissions,
authData,
secretId,
acceptedRoles,
requiredPermissions
}: {
authData: AuthData;
secretId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
requiredPermissions: string[];
authData: AuthData;
secretId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
requiredPermissions: string[];
}) => {
const secret = await Secret.findById(secretId);
const secret = await Secret.findById(secretId);
if (!secret) throw SecretNotFoundError({
message: "Failed to find secret",
if (!secret)
throw SecretNotFoundError({
message: "Failed to find secret"
});
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForSecret({
user: authData.authPayload as IUser,
secret,
acceptedRoles,
requiredPermissions,
});
return secret;
case ActorType.SERVICE:
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: authData.authPayload as IServiceTokenData,
workspaceId: secret.workspace,
environment: secret.environment,
});
return secret;
}
}
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForSecret({
user: authData.authPayload as IUser,
secret,
acceptedRoles,
requiredPermissions
});
return secret;
case ActorType.SERVICE:
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: authData.authPayload as IServiceTokenData,
workspaceId: secret.workspace,
environment: secret.environment
});
return secret;
}
};
/**
* Validate authenticated clients for secrets with ids [secretIds] based
@@ -72,43 +70,292 @@ export const validateClientForSecret = async ({
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
*/
export const validateClientForSecrets = async ({
authData,
secretIds,
requiredPermissions,
authData,
secretIds,
requiredPermissions
}: {
authData: AuthData;
secretIds: Types.ObjectId[];
requiredPermissions: string[];
authData: AuthData;
secretIds: Types.ObjectId[];
requiredPermissions: string[];
}) => {
let secrets: ISecret[] = [];
let secrets: ISecret[] = [];
secrets = await Secret.find({
_id: {
$in: secretIds,
},
});
secrets = await Secret.find({
_id: {
$in: secretIds
}
});
if (secrets.length != secretIds.length) {
throw BadRequestError({ message: "Failed to validate non-existent secrets" })
}
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForSecrets({
user: authData.authPayload as IUser,
secrets,
requiredPermissions,
});
return secrets;
case ActorType.SERVICE:
await validateServiceTokenDataClientForSecrets({
serviceTokenData: authData.authPayload as IServiceTokenData,
secrets,
requiredPermissions,
});
return secrets;
}
}
if (secrets.length != secretIds.length) {
throw BadRequestError({ message: "Failed to validate non-existent secrets" });
}
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForSecrets({
user: authData.authPayload as IUser,
secrets,
requiredPermissions
});
return secrets;
case ActorType.SERVICE:
await validateServiceTokenDataClientForSecrets({
serviceTokenData: authData.authPayload as IServiceTokenData,
secrets,
requiredPermissions
});
return secrets;
}
};
export const GetSecretVersionsV1 = z.object({
params: z.object({
secretId: z.string().trim()
}),
query: z.object({
offset: z.number(),
limit: z.number()
})
});
export const RollbackSecretVersionV1 = z.object({
params: z.object({
secretId: z.string().trim()
}),
body: z.object({
version: z.number()
})
});
export const PushSecretsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
secrets: z.object({}).array(),
keys: z.object({}).array(),
environment: z.string().trim(),
channel: z.string().trim()
})
});
export const PullSecretsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
channel: z.string().optional(),
environment: z.string().trim()
})
});
export const PullSecretsServiceTokenV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
channel: z.string().optional(),
environment: z.string().trim()
})
});
const batchUpdateRequestV2 = z.object({
_id: z.string(),
folderId: z.string().trim().optional(),
type: z.enum(["shared", "personal"]),
secretName: z.string().trim(),
secretKeyCiphertext: z.string().trim(),
secretKeyIV: z.string().trim(),
secretKeyTag: z.string().trim(),
secretValueCiphertext: z.string().trim(),
secretValueIV: z.string().trim(),
secretValueTag: z.string().trim(),
secretCommentCiphertext: z.string().trim().optional(),
secretCommentIV: z.string().trim().optional(),
secretCommentTag: z.string().trim().optional(),
tags: z.object({
_id: z.string().trim(),
name: z.string().trim(),
slug: z.string().trim()
})
});
export const BatchSecretsV2 = z.object({
body: z.object({
workspaceId: z.string().trim(),
folderId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().optional(),
requests: z
.discriminatedUnion("method", [
z.object({
method: z.literal("POST"),
secret: batchUpdateRequestV2.omit({ _id: true })
}),
z.object({
method: z.literal("PATCH"),
secret: batchUpdateRequestV2
}),
z.object({
method: z.literal("DELETE"),
secret: z.object({ _id: z.string().trim(), secretName: z.string().trim() })
})
])
.array()
})
});
export const GetSecretsV2 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
tagSlugs: z.string().trim().optional(),
folderId: z.string().trim().default("root"),
secretPath: z.string().trim().optional(),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
})
});
export const GetSecretsRawV3 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
folderId:z.string().trim().optional(),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
})
});
export const GetSecretByNameRawV3 = z.object({
params: z.object({
secretName: z.string().trim()
}),
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).optional()
})
});
export const CreateSecretRawV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretValue: z.string().trim(),
secretComment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
}),
params: z.object({
secretName: z.string().trim()
})
});
export const UpdateSecretByNameRawV3 = z.object({
params: z.object({
secretName: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretValue: z.string().trim(),
secretName: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
})
});
export const DeleteSecretByNameRawV3 = z.object({
params: z.object({
secretName: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
})
});
export const GetSecretsV3 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
folderId: z.string().trim().optional(),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
})
});
export const GetSecretByNameV3 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).optional()
}),
params: z.object({
secretName: z.string().trim()
})
});
export const CreateSecretV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/"),
secretName: z.string().trim(),
secretKeyCiphertext: z.string().trim(),
secretKeyIV: z.string().trim(),
secretKeyTag: z.string().trim(),
secretValueCiphertext: z.string().trim(),
secretValueIV: z.string().trim(),
secretValueTag: z.string().trim(),
secretCommentCiphertext: z.string().trim().optional(),
secretCommentIV: z.string().trim().optional(),
secretCommentTag: z.string().trim().optional(),
metadata: z.object({
source: z.string()
})
})
});
export const UpdateSecretByNameV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/"),
secretValueCiphertext: z.string().trim(),
secretValueIV: z.string().trim(),
secretValueTag: z.string().trim()
}),
params: z.object({
secretName: z.string()
})
});
export const DeleteSecretByNameV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/")
}),
params: z.object({
secretName: z.string()
})
});

View File

@@ -1,14 +1,10 @@
import { Types } from "mongoose";
import {
ISecret,
IServiceTokenData,
IUser,
ServiceTokenData,
} from "../models";
import { ISecret, IServiceTokenData, IUser, ServiceTokenData } from "../models";
import { ServiceTokenDataNotFoundError, UnauthorizedRequestError } from "../utils/errors";
import { validateUserClientForWorkspace } from "./user";
import { ActorType } from "../ee/models";
import { AuthData } from "../interfaces/middleware";
import { z } from "zod";
/**
* Validate authenticated clients for service token with id [serviceTokenId] based
@@ -31,10 +27,11 @@ export const validateClientForServiceTokenData = async ({
.select("+encryptedKey +iv +tag")
.populate<{ user: IUser }>("user");
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({
message: "Failed to find service token data"
});
if (!serviceTokenData)
throw ServiceTokenDataNotFoundError({
message: "Failed to find service token data"
});
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForWorkspace({
@@ -140,3 +137,28 @@ export const validateServiceTokenDataClientForSecrets = async ({
});
});
};
export const CreateServiceTokenV2 = z.object({
body: z.object({
name: z.string().trim(),
workspaceId: z.string().trim(),
scopes: z
.object({
environment: z.string().trim(),
secretPath: z.string().trim()
})
.array()
.min(1),
encryptedKey: z.string().trim(),
iv: z.string().trim(),
tag: z.string().trim(),
expiresIn: z.number(),
permissions: z.enum(["read", "write"]).array()
})
});
export const DeleteServiceTokenV2 = z.object({
params: z.object({
serviceTokenDataId: z.string().trim()
})
});

View File

@@ -0,0 +1,23 @@
import { z } from "zod";
export const GetWorkspaceTagsV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const DeleteWorkspaceTagsV2 = z.object({
params: z.object({
tagId: z.string().trim()
})
});
export const CreateWorkspaceTagsV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
name: z.string().trim(),
slug: z.string().trim()
})
});

View File

@@ -1,39 +1,30 @@
import fs from "fs";
import path from "path";
import { Types } from "mongoose";
import {
IOrganization,
ISecret,
IServiceAccount,
IUser,
Membership,
} from "../models";
import { IOrganization, ISecret, IServiceAccount, IUser, Membership } from "../models";
import { validateMembership } from "../helpers/membership";
import _ from "lodash";
import { BadRequestError, UnauthorizedRequestError, ValidationError } from "../utils/errors";
import {
validateMembershipOrg,
} from "../helpers/membershipOrg";
import {
PERMISSION_READ_SECRETS,
PERMISSION_WRITE_SECRETS,
} from "../variables";
import { validateMembershipOrg } from "../helpers/membershipOrg";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../variables";
import { AuthMethod } from "../models";
import { z } from "zod";
/**
* Validate that email [email] is not disposable
* @param email - email to validate
*/
export const validateUserEmail = (email: string) => {
const emailDomain = email.split("@")[1];
const disposableEmails = fs.readFileSync(
path.resolve(__dirname, "../data/" + "disposable_emails.txt"),
"utf8"
).split("\n");
if (disposableEmails.includes(emailDomain)) throw ValidationError({
message: "Failed to validate email as non-disposable",
});
}
const emailDomain = email.split("@")[1];
const disposableEmails = fs
.readFileSync(path.resolve(__dirname, "../data/" + "disposable_emails.txt"), "utf8")
.split("\n");
if (disposableEmails.includes(emailDomain))
throw ValidationError({
message: "Failed to validate email as non-disposable"
});
};
/**
* Validate that user (client) can access workspace
@@ -46,48 +37,53 @@ export const validateUserEmail = (email: string) => {
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateUserClientForWorkspace = async ({
user,
workspaceId,
environment,
acceptedRoles,
requiredPermissions,
user,
workspaceId,
environment,
acceptedRoles,
requiredPermissions
}: {
user: IUser;
workspaceId: Types.ObjectId;
environment?: string;
acceptedRoles: Array<"admin" | "member">;
requiredPermissions?: string[];
user: IUser;
workspaceId: Types.ObjectId;
environment?: string;
acceptedRoles: Array<"admin" | "member">;
requiredPermissions?: string[];
}) => {
// validate user membership in workspace
const membership = await validateMembership({
userId: user._id,
workspaceId,
acceptedRoles,
});
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
case PERMISSION_READ_SECRETS:
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
break;
case PERMISSION_WRITE_SECRETS:
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
break;
default:
break;
}
if (runningIsDisallowed) {
throw UnauthorizedRequestError({
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`,
});
}
});
return membership;
}
// validate user membership in workspace
const membership = await validateMembership({
userId: user._id,
workspaceId,
acceptedRoles
});
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
case PERMISSION_READ_SECRETS:
runningIsDisallowed = _.some(membership.deniedPermissions, {
environmentSlug: environment,
ability: PERMISSION_READ_SECRETS
});
break;
case PERMISSION_WRITE_SECRETS:
runningIsDisallowed = _.some(membership.deniedPermissions, {
environmentSlug: environment,
ability: PERMISSION_WRITE_SECRETS
});
break;
default:
break;
}
if (runningIsDisallowed) {
throw UnauthorizedRequestError({
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
});
}
});
return membership;
};
/**
* Validate that user (client) can access secret [secret]
@@ -98,32 +94,35 @@ export const validateUserClientForWorkspace = async ({
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateUserClientForSecret = async ({
user,
secret,
acceptedRoles,
requiredPermissions,
user,
secret,
acceptedRoles,
requiredPermissions
}: {
user: IUser;
secret: ISecret;
acceptedRoles?: Array<"admin" | "member">;
requiredPermissions?: string[];
user: IUser;
secret: ISecret;
acceptedRoles?: Array<"admin" | "member">;
requiredPermissions?: string[];
}) => {
const membership = await validateMembership({
userId: user._id,
workspaceId: secret.workspace,
acceptedRoles,
});
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
const membership = await validateMembership({
userId: user._id,
workspaceId: secret.workspace,
acceptedRoles
});
if (isDisallowed) {
throw UnauthorizedRequestError({
message: "You do not have the required permissions to perform this action",
});
}
}
}
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
const isDisallowed = _.some(membership.deniedPermissions, {
environmentSlug: secret.environment,
ability: PERMISSION_WRITE_SECRETS
});
if (isDisallowed) {
throw UnauthorizedRequestError({
message: "You do not have the required permissions to perform this action"
});
}
}
};
/**
* Validate that user (client) can access secrets [secrets]
@@ -134,41 +133,44 @@ export const validateUserClientForSecret = async ({
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateUserClientForSecrets = async ({
user,
secrets,
requiredPermissions,
user,
secrets,
requiredPermissions
}: {
user: IUser;
secrets: ISecret[];
requiredPermissions?: string[];
user: IUser;
secrets: ISecret[];
requiredPermissions?: string[];
}) => {
// TODO: add acceptedRoles?
// TODO: add acceptedRoles?
const userMemberships = await Membership.find({ user: user._id })
const userMembershipById = _.keyBy(userMemberships, "workspace");
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
const userMemberships = await Membership.find({ user: user._id });
const userMembershipById = _.keyBy(userMemberships, "workspace");
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
// for each secret check if the secret belongs to a workspace the user is a member of
secrets.forEach((secret: ISecret) => {
if (!workspaceIdsSet.has(secret.workspace.toString())) {
throw BadRequestError({
message: "Failed authorization for the secret",
});
}
// for each secret check if the secret belongs to a workspace the user is a member of
secrets.forEach((secret: ISecret) => {
if (!workspaceIdsSet.has(secret.workspace.toString())) {
throw BadRequestError({
message: "Failed authorization for the secret"
});
}
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
const deniedMembershipPermissions =
userMembershipById[secret.workspace.toString()].deniedPermissions;
const isDisallowed = _.some(deniedMembershipPermissions, {
environmentSlug: secret.environment,
ability: PERMISSION_WRITE_SECRETS
});
if (isDisallowed) {
throw UnauthorizedRequestError({
message: "You do not have the required permissions to perform this action",
});
}
}
});
}
if (isDisallowed) {
throw UnauthorizedRequestError({
message: "You do not have the required permissions to perform this action"
});
}
}
});
};
/**
* Validate that user (client) can access service account [serviceAccount]
@@ -179,25 +181,25 @@ export const validateUserClientForSecrets = async ({
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateUserClientForServiceAccount = async ({
user,
serviceAccount,
requiredPermissions,
user,
serviceAccount,
requiredPermissions
}: {
user: IUser;
serviceAccount: IServiceAccount;
requiredPermissions?: string[];
user: IUser;
serviceAccount: IServiceAccount;
requiredPermissions?: string[];
}) => {
if (!serviceAccount.user.equals(user._id)) {
// case: user who created service account is not the
// same user that is on the request
await validateMembershipOrg({
userId: user._id,
organizationId: serviceAccount.organization,
acceptedRoles: [],
acceptedStatuses: [],
});
}
}
if (!serviceAccount.user.equals(user._id)) {
// case: user who created service account is not the
// same user that is on the request
await validateMembershipOrg({
userId: user._id,
organizationId: serviceAccount.organization,
acceptedRoles: [],
acceptedStatuses: []
});
}
};
/**
* Validate that user (client) can access organization [organization]
@@ -206,22 +208,54 @@ export const validateUserClientForServiceAccount = async ({
* @param {Organization} obj.organization - organization to validate against
*/
export const validateUserClientForOrganization = async ({
user,
organization,
acceptedRoles,
acceptedStatuses,
user,
organization,
acceptedRoles,
acceptedStatuses
}: {
user: IUser;
organization: IOrganization;
acceptedRoles: Array<"owner" | "admin" | "member">;
acceptedStatuses: Array<"invited" | "accepted">;
user: IUser;
organization: IOrganization;
acceptedRoles: Array<"owner" | "admin" | "member">;
acceptedStatuses: Array<"invited" | "accepted">;
}) => {
const membershipOrg = await validateMembershipOrg({
userId: user._id,
organizationId: organization._id,
acceptedRoles,
acceptedStatuses,
});
return membershipOrg;
}
const membershipOrg = await validateMembershipOrg({
userId: user._id,
organizationId: organization._id,
acceptedRoles,
acceptedStatuses
});
return membershipOrg;
};
export const UpdateMyMfaEnabledV2 = z.object({
body: z.object({
isMfaEnabled: z.boolean()
})
});
export const UpdateNameV2 = z.object({
body: z.object({
firstName: z.string().trim(),
lastName: z.string().trim()
})
});
export const UpdateAuthMethodsV2 = z.object({
body: z.object({
authMethods: z.nativeEnum(AuthMethod).array().min(1)
})
});
export const CreateApiKeyV2 = z.object({
body: z.object({
name: z.string().trim(),
expiresIn: z.number()
})
});
export const DeleteApiKeyV2 = z.object({
params: z.object({
apiKeyDataId: z.string().trim()
})
});

View File

@@ -0,0 +1,40 @@
import { z } from "zod";
export const CreateWebhookV1 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
webhookUrl: z.string().url().trim(),
webhookSecretKey: z.string().trim().optional(),
secretPath: z.string().trim().default("/")
})
});
export const UpdateWebhookV1 = z.object({
params: z.object({
webhookId: z.string().trim()
}),
body: z.object({
isDisabled: z.boolean().default(false)
})
});
export const TestWebhookV1 = z.object({
params: z.object({
webhookId: z.string().trim()
})
});
export const DeleteWebhookV1 = z.object({
params: z.object({
webhookId: z.string().trim()
})
});
export const ListWebhooksV1 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().optional()
})
});

View File

@@ -9,6 +9,7 @@ import { BotService } from "../services";
import { AuthData } from "../interfaces/middleware";
import { extractIPDetails } from "../utils/ip";
import { z } from "zod";
import { EventType, UserAgentType } from "../ee/models";
/**
* Validate authenticated clients for workspace with id [workspaceId] based
@@ -122,9 +123,241 @@ export const validateClientForWorkspace = async ({
}
};
export const CreateWorkspacev1 = z.object({
export const GetWorkspaceSecretSnapshotsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
environment: z.string().trim(),
folderId: z.string().trim().default("root"),
offset: z.number(),
limit: z.number()
})
});
export const GetWorkspaceSecretSnapshotsCountV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
environment: z.string().trim(),
folderId: z.string().trim().default("root")
})
});
export const RollbackWorkspaceSecretSnapshotV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
environment: z.string().trim(),
folderId: z.string().trim().default("root"),
version: z.number()
})
});
export const GetWorkspaceLogsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
offset: z.number(),
limit: z.number(),
sortBy: z.string().trim().optional(),
userId: z.string().trim().optional(),
actionNames: z.string().trim().optional()
})
});
export const GetWorkspaceAuditLogsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
eventType: z.nativeEnum(EventType).nullable().optional(),
userAgentType: z.nativeEnum(UserAgentType).nullable().optional(),
startDate: z.string().datetime().nullable().optional(),
endDate: z.string().datetime().nullable().optional(),
offset: z.number(),
limit: z.number(),
actor: z.string().nullish().optional()
})
});
export const GetWorkspaceAuditLogActorFilterOptsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceTrustedIpsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const AddWorkspaceTrustedIpV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
ipAddress: z.string().trim(),
comment: z.string().trim().default(""),
isActive: z.boolean()
})
});
export const UpdateWorkspaceTrustedIpV1 = z.object({
params: z.object({
workspaceId: z.string().trim(),
trustedIpId: z.string().trim()
}),
body: z.object({
ipAddress: z.string().trim(),
comment: z.string().trim().default("")
})
});
export const DeleteWorkspaceTrustedIpV1 = z.object({
params: z.object({
workspaceId: z.string().trim(),
trustedIpId: z.string().trim()
})
});
export const GetWorkspacePublicKeysV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceMembershipsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const CreateWorkspaceV1 = z.object({
body: z.object({
workspaceName: z.string().trim(),
organizationId: z.string().trim()
})
});
export const DeleteWorkspaceV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const ChangeWorkspaceNameV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
name: z.string().trim()
})
});
export const InviteUserToWorkspaceV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
email: z.string().trim()
})
});
export const GetWorkspaceIntegrationsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceIntegrationAuthorizationsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceServiceTokensV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceServiceTokenDataV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceKeyV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceMembershipsV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const UpdateWorkspaceMembershipsV2 = z.object({
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
}),
body: z.object({
role: z.string().trim()
})
});
export const DeleteWorkspaceMembershipsV2 = z.object({
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
})
});
export const ToggleAutoCapitalizationV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
autoCapitalization: z.string().trim()
})
});
export const GetWorkspaceBlinkIndexStatusV3 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const GetWorkspaceSecretsV3 = z.object({
params: z.object({
workspaceId: z.string().trim()
})
});
export const NameWorkspaceSecretsV3 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
secretsToUpdate: z
.object({
secretName: z.string().trim(),
_id: z.string().trim()
})
.array()
})
});