diff --git a/backend/src/services/membership-user/membership-user-service.ts b/backend/src/services/membership-user/membership-user-service.ts index 4b14ee771..59772a603 100644 --- a/backend/src/services/membership-user/membership-user-service.ts +++ b/backend/src/services/membership-user/membership-user-service.ts @@ -188,16 +188,29 @@ export const membershipUserServiceFactory = ({ }); if (existingMemberships.length === users.length) return { memberships: [] }; + const orgDetails = await orgDAL.findById(dto.permission.orgId); + const isSubOrganization = Boolean(orgDetails.rootOrgId); const newMembershipUsers = users.filter((user) => !existingMemberships?.find((el) => el.actorUserId === user.id)); await factory.onCreateMembershipUserGuard(dto, newMembershipUsers); - const newMemberships = newMembershipUsers.map((user) => ({ - scope: scopeData.scope, - ...scopeDatabaseFields, - actorUserId: user.id, - status: scopeData.scope === AccessScope.Organization ? OrgMembershipStatus.Invited : undefined, - inviteEmail: scopeData.scope === AccessScope.Organization ? user.email : undefined - })); + const newMemberships = newMembershipUsers.map((user) => { + let status: OrgMembershipStatus | undefined; + if (scopeData.scope === AccessScope.Organization) { + if (isSubOrganization) { + status = OrgMembershipStatus.Accepted; + } else { + status = OrgMembershipStatus.Invited; + } + } + + return { + scope: scopeData.scope, + ...scopeDatabaseFields, + actorUserId: user.id, + status, + inviteEmail: status === OrgMembershipStatus.Invited ? user.email : undefined + }; + }); const customInputRoles = data.roles.filter((el) => factory.isCustomRole(el.role)); const hasCustomRole = customInputRoles.length > 0; diff --git a/backend/src/services/membership-user/org/org-membership-user-factory.ts b/backend/src/services/membership-user/org/org-membership-user-factory.ts index 6fdfab590..2668ed665 100644 --- a/backend/src/services/membership-user/org/org-membership-user-factory.ts +++ b/backend/src/services/membership-user/org/org-membership-user-factory.ts @@ -1,6 +1,6 @@ import { ForbiddenError } from "@casl/ability"; -import { AccessScope, OrganizationActionScope } from "@app/db/schemas"; +import { AccessScope, OrganizationActionScope, OrgMembershipStatus } from "@app/db/schemas"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; @@ -122,16 +122,33 @@ export const newOrgMembershipUserFactory = ({ const signUpTokens: { email: string; link: string }[] = []; const orgDetails = await orgDAL.findById(dto.permission.orgId); if (orgDetails.rootOrgId) { - const emails = newUsers.map((el) => el.email).filter(Boolean); - await smtpService.sendMail({ - template: SmtpTemplates.SubOrgInvite, - subjectLine: "Infisical sub-organization invitation", - recipients: emails as string[], - substitutions: { - subOrganizationName: orgDetails.slug, - callback_url: `${appCfg.SITE_URL}/organizations/${dto.permission.orgId}/projects` + // checking if the users have accepted the invitation in the root organization to send the email + const orgMembershipAccepted = await membershipUserDAL.find({ + scope: AccessScope.Organization, + scopeOrgId: orgDetails.rootOrgId, + status: OrgMembershipStatus.Accepted, + $in: { + actorUserId: newUsers.map((el) => el.id) } }); + + const orgMembershipAcceptedUserIds = orgMembershipAccepted.map((el) => el.actorUserId as string); + + const emails = newUsers + .filter((el) => Boolean(el?.email) && orgMembershipAcceptedUserIds.includes(el.id)) + .map((el) => el?.email as string); + + if (emails.length) { + await smtpService.sendMail({ + template: SmtpTemplates.SubOrgInvite, + subjectLine: "Infisical sub-organization invitation", + recipients: emails, + substitutions: { + subOrganizationName: orgDetails.slug, + callback_url: `${appCfg.SITE_URL}/organizations/${dto.permission.orgId}/projects` + } + }); + } } else { await Promise.allSettled( newUsers.map(async (el) => {