mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-20 04:20:25 +00:00
Merge branch 'heads/main' into daniel/kms-sign-verify
This commit is contained in:
+69
-11
@@ -1,11 +1,20 @@
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FilterableSelect, FormControl, Modal, ModalContent } from "@app/components/v2";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
Button,
|
||||
FilterableSelect,
|
||||
FormControl,
|
||||
Modal,
|
||||
ModalContent
|
||||
} from "@app/components/v2";
|
||||
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
@@ -48,7 +57,12 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const navigate = useNavigate({ from: "" });
|
||||
const { permission } = useOrgPermission();
|
||||
const requesterEmail = useSearch({
|
||||
strict: false,
|
||||
select: (el) => el?.requesterEmail
|
||||
});
|
||||
|
||||
const orgId = currentOrg?.id || "";
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
@@ -62,6 +76,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { isSubmitting, errors }
|
||||
} = useForm<TAddMemberForm>({
|
||||
@@ -71,6 +86,12 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
|
||||
const { mutateAsync: addMembersToProject } = useAddUsersToOrg();
|
||||
|
||||
useEffect(() => {
|
||||
if (requesterEmail) {
|
||||
handlePopUpToggle("addMember", true);
|
||||
}
|
||||
}, [requesterEmail]);
|
||||
|
||||
const onAddMembers = async ({ orgMemberships, projectRoleSlugs }: TAddMemberForm) => {
|
||||
if (!currentWorkspace) return;
|
||||
if (!currentOrg?.id) return;
|
||||
@@ -126,12 +147,13 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
reset();
|
||||
};
|
||||
|
||||
const filteredOrgUsers = useMemo(() => {
|
||||
const { append } = useFieldArray<TAddMemberForm>({ control, name: "orgMemberships" });
|
||||
const projectInviteList = useMemo(() => {
|
||||
const wsUserUsernames = new Map();
|
||||
members?.forEach((member) => {
|
||||
wsUserUsernames.set(member.user.username, true);
|
||||
});
|
||||
return (orgUsers || [])
|
||||
const list = (orgUsers || [])
|
||||
.filter(({ user: u }) => !wsUserUsernames.has(u.username))
|
||||
.map(({ id, inviteEmail, user: { firstName, lastName, email } }) => ({
|
||||
value: id,
|
||||
@@ -140,13 +162,31 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
? `${firstName} ${lastName}`
|
||||
: firstName || lastName || email || inviteEmail
|
||||
}));
|
||||
const requesterStatus = { isProjectUser: wsUserUsernames.has(requesterEmail), userLabel: "" };
|
||||
if (!requesterStatus.isProjectUser) {
|
||||
const userDetails = orgUsers?.find((el) => el.user.username === requesterEmail);
|
||||
if (userDetails) {
|
||||
const { user } = userDetails;
|
||||
const label =
|
||||
user.firstName && user.lastName
|
||||
? `${user.firstName} ${user.lastName}`
|
||||
: user.firstName || user.lastName || (user.email as string);
|
||||
requesterStatus.userLabel = label;
|
||||
setValue("orgMemberships", [
|
||||
{
|
||||
value: userDetails.id,
|
||||
label
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return { list, requesterStatus };
|
||||
}, [orgUsers, members]);
|
||||
|
||||
const selectedOrgMemberships = watch("orgMemberships");
|
||||
const selectedRoleSlugs = watch("projectRoleSlugs");
|
||||
|
||||
const { append } = useFieldArray<TAddMemberForm>({ control, name: "orgMemberships" });
|
||||
|
||||
const canInviteNewMembers = permission.can(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Member
|
||||
@@ -155,7 +195,13 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addMember?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addMember", isOpen)}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (!isOpen)
|
||||
navigate({
|
||||
search: (prev) => ({ ...prev, requesterEmail: "" })
|
||||
});
|
||||
handlePopUpToggle("addMember", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
bodyClassName="overflow-visible"
|
||||
@@ -185,7 +231,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
noOptionsMessage={() => (
|
||||
<>
|
||||
<p>
|
||||
{!filteredOrgUsers.length && (
|
||||
{!projectInviteList.list.length && (
|
||||
<p>All organization members are already assigned to this project.</p>
|
||||
)}
|
||||
</p>
|
||||
@@ -221,7 +267,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
placeholder="Add one or more users..."
|
||||
isMulti
|
||||
name="members"
|
||||
options={filteredOrgUsers}
|
||||
options={projectInviteList.list}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
@@ -231,7 +277,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
placeholder="Add one or more users..."
|
||||
isMulti
|
||||
name="members"
|
||||
options={filteredOrgUsers}
|
||||
options={projectInviteList.list}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
@@ -239,7 +285,6 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="projectRoleSlugs"
|
||||
@@ -263,6 +308,19 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{requesterEmail && projectInviteList.requesterStatus.isProjectUser && (
|
||||
<Alert hideTitle variant="danger">
|
||||
<AlertDescription>Requested user is part of the project.</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{requesterEmail && !projectInviteList.requesterStatus.isProjectUser && (
|
||||
<Alert hideTitle>
|
||||
<AlertDescription>
|
||||
Assign a role to provide access to requesting user{" "}
|
||||
<b>{projectInviteList.requesterStatus.userLabel}</b>.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -7,7 +7,8 @@ import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
import { AccessControlPage } from "./AccessControlPage";
|
||||
|
||||
const AccessControlPageQuerySchema = z.object({
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
|
||||
requesterEmail: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -15,6 +16,9 @@ export const Route = createFileRoute(
|
||||
)({
|
||||
component: AccessControlPage,
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ requesterEmail: "" })]
|
||||
},
|
||||
beforeLoad: ({ context, params }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -7,7 +7,8 @@ import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
import { AccessControlPage } from "./AccessControlPage";
|
||||
|
||||
const AccessControlPageQuerySchema = z.object({
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
|
||||
requesterEmail: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -15,6 +16,9 @@ export const Route = createFileRoute(
|
||||
)({
|
||||
component: AccessControlPage,
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ requesterEmail: "" })]
|
||||
},
|
||||
beforeLoad: ({ context, params }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -7,7 +7,8 @@ import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
import { AccessControlPage } from "./AccessControlPage";
|
||||
|
||||
const AccessControlPageQuerySchema = z.object({
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
|
||||
requesterEmail: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -15,6 +16,9 @@ export const Route = createFileRoute(
|
||||
)({
|
||||
component: AccessControlPage,
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ requesterEmail: "" })]
|
||||
},
|
||||
beforeLoad: ({ context, params }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
|
||||
+2
-1
@@ -28,7 +28,8 @@ type Props = {
|
||||
type:
|
||||
| ProjectPermissionSub.DynamicSecrets
|
||||
| ProjectPermissionSub.SecretFolders
|
||||
| ProjectPermissionSub.SecretImports;
|
||||
| ProjectPermissionSub.SecretImports
|
||||
| ProjectPermissionSub.SecretRotation;
|
||||
};
|
||||
|
||||
export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }: Props) => {
|
||||
|
||||
+51
-5
@@ -17,6 +17,7 @@ import {
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSecretRotationActions,
|
||||
ProjectPermissionSecretSyncActions,
|
||||
TPermissionCondition,
|
||||
TPermissionConditionOperators
|
||||
@@ -68,6 +69,15 @@ const SecretSyncPolicyActionSchema = z.object({
|
||||
[ProjectPermissionSecretSyncActions.RemoveSecrets]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const SecretRotationPolicyActionSchema = z.object({
|
||||
[ProjectPermissionSecretRotationActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretRotationActions.ReadGeneratedCredentials]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretRotationActions.Create]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretRotationActions.Edit]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretRotationActions.Delete]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretRotationActions.RotateSecrets]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const KmipPolicyActionSchema = z.object({
|
||||
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
|
||||
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
|
||||
@@ -211,7 +221,12 @@ export const projectRoleFormSchema = z.object({
|
||||
[ProjectPermissionSub.SecretRollback]: SecretRollbackPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Project]: WorkspacePolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Tags]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretRotation]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretRotation]: SecretRotationPolicyActionSchema.extend({
|
||||
inverted: z.boolean().optional(),
|
||||
conditions: ConditionSchema
|
||||
})
|
||||
.array()
|
||||
.default([]),
|
||||
[ProjectPermissionSub.Kms]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Cmek]: CmekPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretSyncs]: SecretSyncPolicyActionSchema.array().default([]),
|
||||
@@ -228,6 +243,7 @@ type TConditionalFields =
|
||||
| ProjectPermissionSub.SecretFolders
|
||||
| ProjectPermissionSub.SecretImports
|
||||
| ProjectPermissionSub.DynamicSecrets
|
||||
| ProjectPermissionSub.SecretRotation
|
||||
| ProjectPermissionSub.Identity;
|
||||
|
||||
export const isConditionalSubjects = (
|
||||
@@ -237,6 +253,7 @@ export const isConditionalSubjects = (
|
||||
subject === ProjectPermissionSub.DynamicSecrets ||
|
||||
subject === ProjectPermissionSub.SecretImports ||
|
||||
subject === ProjectPermissionSub.SecretFolders ||
|
||||
subject === ProjectPermissionSub.SecretRotation ||
|
||||
subject === ProjectPermissionSub.Identity;
|
||||
|
||||
const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => {
|
||||
@@ -300,6 +317,30 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
if (isConditionalSubjects(subject)) {
|
||||
if (!formVal[subject]) formVal[subject] = [];
|
||||
|
||||
if (subject === ProjectPermissionSub.SecretRotation) {
|
||||
const canRead = action.includes(ProjectPermissionSecretRotationActions.Read);
|
||||
const canReadCredentials = action.includes(
|
||||
ProjectPermissionSecretRotationActions.ReadGeneratedCredentials
|
||||
);
|
||||
const canEdit = action.includes(ProjectPermissionSecretRotationActions.Edit);
|
||||
const canDelete = action.includes(ProjectPermissionSecretRotationActions.Delete);
|
||||
const canCreate = action.includes(ProjectPermissionSecretRotationActions.Create);
|
||||
const canRotate = action.includes(ProjectPermissionSecretRotationActions.RotateSecrets);
|
||||
|
||||
// from above statement we are sure it won't be undefined
|
||||
formVal[subject]!.push({
|
||||
[ProjectPermissionSecretRotationActions.Read]: canRead,
|
||||
[ProjectPermissionSecretRotationActions.ReadGeneratedCredentials]: canReadCredentials,
|
||||
[ProjectPermissionSecretRotationActions.Create]: canCreate,
|
||||
[ProjectPermissionSecretRotationActions.Edit]: canEdit,
|
||||
[ProjectPermissionSecretRotationActions.Delete]: canDelete,
|
||||
[ProjectPermissionSecretRotationActions.RotateSecrets]: canRotate,
|
||||
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||
inverted
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.DynamicSecrets) {
|
||||
const canRead = action.includes(ProjectPermissionDynamicSecretActions.ReadRootCredential);
|
||||
const canEdit = action.includes(ProjectPermissionDynamicSecretActions.EditRootCredential);
|
||||
@@ -898,10 +939,15 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
||||
[ProjectPermissionSub.SecretRotation]: {
|
||||
title: "Secret Rotation",
|
||||
actions: [
|
||||
{ label: "Read", value: "read" },
|
||||
{ label: "Create", value: "create" },
|
||||
{ label: "Modify", value: "edit" },
|
||||
{ label: "Remove", value: "delete" }
|
||||
{ label: "Read Config", value: ProjectPermissionSecretRotationActions.Read },
|
||||
{
|
||||
label: "Read Generated Credentials",
|
||||
value: ProjectPermissionSecretRotationActions.ReadGeneratedCredentials
|
||||
},
|
||||
{ label: "Create Config", value: ProjectPermissionSecretRotationActions.Create },
|
||||
{ label: "Modify Config", value: ProjectPermissionSecretRotationActions.Edit },
|
||||
{ label: "Remove Config", value: ProjectPermissionSecretRotationActions.Delete },
|
||||
{ label: "Rotate Secrets", value: ProjectPermissionSecretRotationActions.RotateSecrets }
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.SecretRollback]: {
|
||||
|
||||
Reference in New Issue
Block a user