Merge pull request #1152 from akhilmhdh/fix/key-rogue

fix: changed 2 fold operation of member workspace to one api call
This commit is contained in:
Maidul Islam
2023-11-04 12:33:08 -04:00
committed by GitHub
16 changed files with 327 additions and 128 deletions
+14 -12
View File
@@ -9,17 +9,19 @@ import * as secretsController from "./secretsController";
import * as serviceAccountsController from "./serviceAccountsController";
import * as environmentController from "./environmentController";
import * as tagController from "./tagController";
import * as membershipController from "./membershipController";
export {
authController,
signupController,
usersController,
organizationsController,
workspaceController,
serviceTokenDataController,
secretController,
secretsController,
serviceAccountsController,
environmentController,
tagController,
}
authController,
signupController,
usersController,
organizationsController,
workspaceController,
serviceTokenDataController,
secretController,
secretsController,
serviceAccountsController,
environmentController,
tagController,
membershipController
};
@@ -0,0 +1,103 @@
import { ForbiddenError } from "@casl/ability";
import { Request, Response } from "express";
import { Types } from "mongoose";
import { getSiteURL } from "../../config";
import { EventType } from "../../ee/models";
import { EEAuditLogService } from "../../ee/services";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { sendMail } from "../../helpers";
import { validateRequest } from "../../helpers/validation";
import { IUser, Key, Membership, MembershipOrg, Workspace } from "../../models";
import { BadRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/membership";
import { ACCEPTED, MEMBER } from "../../variables";
export const addUserToWorkspace = async (req: Request, res: Response) => {
const {
params: { workspaceId },
body: { members }
} = await validateRequest(reqValidator.AddUserToWorkspaceV2, req);
// check workspace
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw new Error("Failed to find workspace");
// check permission
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
// validate members are part of the organization
const orgMembers = await MembershipOrg.find({
status: ACCEPTED,
_id: { $in: members.map(({ orgMembershipId }) => orgMembershipId) },
organization: workspace.organization
})
.populate<{ user: IUser }>("user")
.select({ _id: 1, user: 1 })
.lean();
if (orgMembers.length !== members.length)
throw BadRequestError({ message: "Org member not found" });
const existingMember = await Membership.find({
workspace: workspaceId,
user: { $in: orgMembers.map(({ user }) => user) }
});
if (existingMember?.length)
throw BadRequestError({ message: "Some users are already part of workspace" });
await Membership.insertMany(
orgMembers.map(({ user }) => ({ user: user._id, workspace: workspaceId, role: MEMBER }))
);
const encKeyGroupedByOrgMemberId = members.reduce<Record<string, (typeof members)[number]>>(
(prev, curr) => ({ ...prev, [curr.orgMembershipId]: curr }),
{}
);
await Key.insertMany(
orgMembers.map(({ user, _id: id }) => ({
encryptedKey: encKeyGroupedByOrgMemberId[id.toString()].workspaceEncryptedKey,
nonce: encKeyGroupedByOrgMemberId[id.toString()].workspaceEncryptedNonce,
sender: req.user._id,
receiver: user._id,
workspace: workspaceId
}))
);
await sendMail({
template: "workspaceInvitation.handlebars",
subjectLine: "Infisical workspace invitation",
recipients: orgMembers.map(({ user }) => user.email),
substitutions: {
inviterFirstName: req.user.firstName,
inviterEmail: req.user.email,
workspaceName: workspace.name,
callback_url: (await getSiteURL()) + "/login"
}
});
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
metadata: orgMembers.map(({ user }) => ({
userId: user._id.toString(),
email: user.email
}))
},
{
workspaceId: new Types.ObjectId(workspaceId)
}
);
return res.status(200).send({
success: true,
data: orgMembers
});
};
+1
View File
@@ -39,6 +39,7 @@ export enum EventType {
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
ADD_WORKSPACE_MEMBER = "add-workspace-member",
ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members",
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
CREATE_FOLDER = "create-folder",
UPDATE_FOLDER = "update-folder",
+9
View File
@@ -287,6 +287,14 @@ interface AddWorkspaceMemberEvent {
};
}
interface AddBatchWorkspaceMemberEvent {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER;
metadata: Array<{
userId: string;
email: string;
}>;
}
interface RemoveWorkspaceMemberEvent {
type: EventType.REMOVE_WORKSPACE_MEMBER;
metadata: {
@@ -494,6 +502,7 @@ export type Event =
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent
| AddWorkspaceMemberEvent
| AddBatchWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent
| CreateFolderEvent
| UpdateFolderEvent
+11 -7
View File
@@ -67,7 +67,8 @@ import {
signup as v2SignupRouter,
tags as v2TagsRouter,
users as v2UsersRouter,
workspace as v2WorkspaceRouter
workspace as v2WorkspaceRouter,
membership as v2MembershipController
} from "./routes/v2";
import {
auth as v3AuthRouter,
@@ -87,7 +88,7 @@ import {
getSecretScanningPrivateKey,
getSecretScanningWebhookProxy,
getSecretScanningWebhookSecret,
getSiteURL,
getSiteURL
} from "./config";
import { setup } from "./utils/setup";
import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
@@ -106,11 +107,13 @@ const main = async () => {
const app = express();
app.enable("trust proxy");
app.use(httpLogger({
logger,
autoLogging: false
}));
app.use(
httpLogger({
logger,
autoLogging: false
})
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
@@ -230,6 +233,7 @@ const main = async () => {
app.use("/api/v2/auth", v2AuthRouter);
app.use("/api/v2/users", v2UsersRouter);
app.use("/api/v2/organizations", v2OrganizationsRouter);
app.use("/api/v2/workspace", v2MembershipController);
app.use("/api/v2/workspace", v2EnvironmentRouter);
app.use("/api/v2/workspace", v2TagsRouter);
app.use("/api/v2/workspace", v2WorkspaceRouter);
+20 -18
View File
@@ -1,25 +1,27 @@
import auth from "./auth";
import signup from "./signup";
import users from "./users";
import environment from "./environment";
import membership from "./membership";
import organizations from "./organizations";
import workspace from "./workspace";
import secret from "./secret"; // deprecated
import secrets from "./secrets";
import serviceTokenData from "./serviceTokenData";
import serviceAccounts from "./serviceAccounts";
import environment from "./environment"
import tags from "./tags"
import serviceTokenData from "./serviceTokenData";
import signup from "./signup";
import tags from "./tags";
import users from "./users";
import workspace from "./workspace";
export {
auth,
signup,
users,
organizations,
workspace,
secret,
secrets,
serviceTokenData,
serviceAccounts,
environment,
tags,
}
auth,
signup,
users,
organizations,
workspace,
secret,
secrets,
serviceTokenData,
serviceAccounts,
environment,
tags,
membership
};
+15
View File
@@ -0,0 +1,15 @@
import express from "express";
const router = express.Router();
import { membershipController } from "../../controllers/v2";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
router.post(
"/:workspaceId/memberships",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
membershipController.addUserToWorkspace
);
export default router;
+16
View File
@@ -78,3 +78,19 @@ export const DenyMembershipPermissionV1 = z.object({
permissions: z.object({}).array()
})
});
export const AddUserToWorkspaceV2 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
members: z
.object({
orgMembershipId: z.string().trim(),
workspaceEncryptedKey: z.string().trim(),
workspaceEncryptedNonce: z.string().trim()
})
.array()
.min(1)
})
});