feat(rbac): fixed broken invite and role missing in dropdown

This commit is contained in:
Akhil Mohan
2023-09-04 20:59:00 +05:30
parent cb9ee00ed3
commit 6bbdc4a405
9 changed files with 42 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IUser, Key, Membership, MembershipOrg, User } from "../../models"; import { IUser, Key, Membership, MembershipOrg, User, Workspace } from "../../models";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership"; import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
import { sendMail } from "../../helpers/nodemailer"; import { sendMail } from "../../helpers/nodemailer";
@@ -17,6 +17,7 @@ import {
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import Role from "../../models/role"; import Role from "../../models/role";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { InviteUserToWorkspaceV1 } from "../../validation/workspace";
/** /**
* Check that user is a member of workspace with id [workspaceId] * Check that user is a member of workspace with id [workspaceId]
@@ -182,8 +183,15 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const inviteUserToWorkspace = async (req: Request, res: Response) => { export const inviteUserToWorkspace = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const {
const { email }: { email: string } = req.body; params: { workspaceId },
body: { email }
} = await validateRequest(InviteUserToWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
const invitee = await User.findOne({ const invitee = await User.findOne({
email email
@@ -200,11 +208,13 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
if (inviteeMembership) throw new Error("Failed to add existing member of workspace"); if (inviteeMembership) throw new Error("Failed to add existing member of workspace");
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw new Error("Failed to find workspace");
// validate invitee's organization membership - ensure that only // validate invitee's organization membership - ensure that only
// (accepted) organization members can be added to the workspace // (accepted) organization members can be added to the workspace
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
user: invitee._id, user: invitee._id,
organization: req.membership.workspace.organization, organization: workspace.organization,
status: ACCEPTED status: ACCEPTED
}); });
@@ -232,7 +242,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
substitutions: { substitutions: {
inviterFirstName: req.user.firstName, inviterFirstName: req.user.firstName,
inviterEmail: req.user.email, inviterEmail: req.user.email,
workspaceName: req.membership.workspace.name, workspaceName: workspace.name,
callback_url: (await getSiteURL()) + "/login" callback_url: (await getSiteURL()) + "/login"
} }
}); });

View File

@@ -368,7 +368,7 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
*/ */
export const getSecrets = async (req: Request, res: Response) => { export const getSecrets = async (req: Request, res: Response) => {
const { const {
query: { secretPath, environment, workspaceId, include_imports: includeImports,folderId } query: { secretPath, environment, workspaceId, include_imports: includeImports, folderId }
} = await validateRequest(reqValidator.GetSecretsV3, req); } = await validateRequest(reqValidator.GetSecretsV3, req);
if (req.user?._id) { if (req.user?._id) {
@@ -468,7 +468,6 @@ export const createSecret = async (req: Request, res: Response) => {
const { const {
body: { body: {
workspaceId, workspaceId,
secretName,
secretPath, secretPath,
environment, environment,
metadata, metadata,
@@ -482,7 +481,8 @@ export const createSecret = async (req: Request, res: Response) => {
secretKeyCiphertext, secretKeyCiphertext,
secretValueCiphertext, secretValueCiphertext,
secretCommentCiphertext secretCommentCiphertext
} },
params: { secretName }
} = await validateRequest(reqValidator.CreateSecretV3, req); } = await validateRequest(reqValidator.CreateSecretV3, req);
if (req.user?._id) { if (req.user?._id) {

View File

@@ -1,8 +1,7 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body, param } from "express-validator"; import { requireAuth } from "../../middleware";
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import { AuthMode } from "../../variables";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { membershipController, workspaceController } from "../../controllers/v1"; import { membershipController, workspaceController } from "../../controllers/v1";
router.get( router.get(
@@ -42,7 +41,7 @@ router.post(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
workspaceController.deleteWorkspace workspaceController.createWorkspace
); );
router.post( router.post(
@@ -50,13 +49,6 @@ router.post(
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
}), }),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params"
}),
param("workspaceId").exists().trim(),
body("name").exists().trim().notEmpty(),
validateRequest,
workspaceController.changeWorkspaceName workspaceController.changeWorkspaceName
); );

View File

@@ -9,7 +9,7 @@ export const BeginEmailSignUpV1 = z.object({
export const VerifyEmailSignUpV1 = z.object({ export const VerifyEmailSignUpV1 = z.object({
body: z.object({ body: z.object({
email: z.string().email().trim(), email: z.string().email().trim(),
code: z.string().email().trim() code: z.string().trim()
}) })
}); });

View File

@@ -228,7 +228,7 @@ export const GetSecretsRawV3 = z.object({
workspaceId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
secretPath: z.string().trim().default("/"), secretPath: z.string().trim().default("/"),
folderId:z.string().trim().optional(), folderId: z.string().trim().optional(),
include_imports: z include_imports: z
.enum(["true", "false"]) .enum(["true", "false"])
.default("false") .default("false")
@@ -319,7 +319,6 @@ export const CreateSecretV3 = z.object({
environment: z.string().trim(), environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]), type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/"), secretPath: z.string().trim().default("/"),
secretName: z.string().trim(),
secretKeyCiphertext: z.string().trim(), secretKeyCiphertext: z.string().trim(),
secretKeyIV: z.string().trim(), secretKeyIV: z.string().trim(),
secretKeyTag: z.string().trim(), secretKeyTag: z.string().trim(),
@@ -332,6 +331,9 @@ export const CreateSecretV3 = z.object({
metadata: z.object({ metadata: z.object({
source: z.string() source: z.string()
}) })
}),
params: z.object({
secretName: z.string().trim()
}) })
}); });

View File

@@ -23,7 +23,7 @@ export const MembersPage = withPermission(
const orgId = currentOrg?._id || ""; const orgId = currentOrg?._id || "";
const { data: roles } = useGetRoles({ const { data: roles, isLoading: isRolesLoading } = useGetRoles({
orgId orgId
}); });
@@ -48,11 +48,17 @@ export const MembersPage = withPermission(
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<OrgMembersTable roles={roles as TRole<undefined>[]} /> <OrgMembersTable
roles={roles as TRole<undefined>[]}
isRolesLoading={isRolesLoading}
/>
</motion.div> </motion.div>
</TabPanel> </TabPanel>
<TabPanel value={TabSections.Roles}> <TabPanel value={TabSections.Roles}>
<OrgRoleTabSection roles={roles as TRole<undefined>[]} /> <OrgRoleTabSection
roles={roles as TRole<undefined>[]}
isRolesLoading={isRolesLoading}
/>
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</div> </div>

View File

@@ -66,6 +66,7 @@ import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
type Props = { type Props = {
roles?: TRole<undefined>[]; roles?: TRole<undefined>[];
isRolesLoading?: boolean;
}; };
const addMemberFormSchema = yup.object({ const addMemberFormSchema = yup.object({
@@ -74,7 +75,7 @@ const addMemberFormSchema = yup.object({
type TAddMemberForm = yup.InferType<typeof addMemberFormSchema>; type TAddMemberForm = yup.InferType<typeof addMemberFormSchema>;
export const OrgMembersTable = ({ roles = [] }: Props) => { export const OrgMembersTable = ({ roles = [], isRolesLoading }: Props) => {
const router = useRouter(); const router = useRouter();
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
@@ -292,7 +293,7 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
setInviteLinkCopied.on(); setInviteLinkCopied.on();
}; };
const isLoading = isMembersLoading || IsWsMembershipLoading; const isLoading = isMembersLoading || IsWsMembershipLoading || isRolesLoading;
return ( return (
<div className="w-full"> <div className="w-full">

View File

@@ -47,7 +47,7 @@ export const MembersPage = withProjectPermission(
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<MemberListTab roles={roles as TRole<string>[]} /> <MemberListTab roles={roles as TRole<string>[]} isRolesLoading={isRolesLoading} />
</motion.div> </motion.div>
</TabPanel> </TabPanel>
<TabPanel value={TabSections.Roles}> <TabPanel value={TabSections.Roles}>

View File

@@ -54,6 +54,7 @@ import { TRole } from "@app/hooks/api/roles/types";
type Props = { type Props = {
roles?: TRole<string>[]; roles?: TRole<string>[];
isRolesLoading?: boolean;
}; };
const addMemberFormSchema = z.object({ const addMemberFormSchema = z.object({
@@ -62,7 +63,7 @@ const addMemberFormSchema = z.object({
type TAddMemberForm = z.infer<typeof addMemberFormSchema>; type TAddMemberForm = z.infer<typeof addMemberFormSchema>;
export const MemberListTab = ({ roles = [] }: Props) => { export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -227,7 +228,7 @@ export const MemberListTab = ({ roles = [] }: Props) => {
} }
}; };
const isLoading = isMembersLoading; const isLoading = isMembersLoading || isRolesLoading;
return ( return (
<div className="w-full"> <div className="w-full">