mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(rbac): fixed broken invite and role missing in dropdown
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user