mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 02:27:37 +00:00
feat: almost done with most ts stuff
This commit is contained in:
@@ -58,7 +58,6 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
"create" | "find" | "findOne" | "transaction" | "delete"
|
"create" | "find" | "findOne" | "transaction" | "delete"
|
||||||
>;
|
>;
|
||||||
groupDAL: Pick<TGroupDALFactory, "findAllGroupPossibleMembers">;
|
groupDAL: Pick<TGroupDALFactory, "findAllGroupPossibleMembers">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
userDAL: Pick<
|
userDAL: Pick<
|
||||||
TUserDALFactory,
|
TUserDALFactory,
|
||||||
|
|||||||
@@ -25,13 +25,12 @@ import { TMembershipRoleDALFactory } from "@app/services/membership/membership-r
|
|||||||
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||||
import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
|
import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
|
import { deleteOrgMembershipsFn } from "@app/services/org/org-fns";
|
||||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { OrgAuthMethod } from "@app/services/org/org-types";
|
import { OrgAuthMethod } from "@app/services/org/org-types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
@@ -65,7 +64,6 @@ type TScimServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
membershipUserDAL: TMembershipUserDALFactory;
|
membershipUserDAL: TMembershipUserDALFactory;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser" | "findById">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId">;
|
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
| "create"
|
| "create"
|
||||||
@@ -78,7 +76,7 @@ type TScimServiceFactoryDep = {
|
|||||||
| "update"
|
| "update"
|
||||||
>;
|
>;
|
||||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create" | "update">;
|
membershipRoleDAL: TMembershipRoleDALFactory;
|
||||||
userGroupMembershipDAL: Pick<
|
userGroupMembershipDAL: Pick<
|
||||||
TUserGroupMembershipDALFactory,
|
TUserGroupMembershipDALFactory,
|
||||||
| "find"
|
| "find"
|
||||||
@@ -104,7 +102,6 @@ export const scimServiceFactory = ({
|
|||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
groupDAL,
|
groupDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
@@ -667,15 +664,16 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteOrgMembershipFn({
|
await deleteOrgMembershipsFn({
|
||||||
orgMembershipId: membership.id,
|
orgMembershipIds: [membership.id],
|
||||||
orgId: membership.scopeOrgId,
|
orgId: membership.scopeOrgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
membershipUserDAL
|
membershipUserDAL,
|
||||||
|
membershipRoleDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
return {}; // intentionally return empty object upon success
|
return {}; // intentionally return empty object upon success
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ import { certificateTemplateDALFactory } from "@app/services/certificate-templat
|
|||||||
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
||||||
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
||||||
|
import { convertorServiceFactory } from "@app/services/convertor/convertor-service";
|
||||||
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||||
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
|
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
|
||||||
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
|
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
|
||||||
@@ -184,6 +185,7 @@ import { folderCommitChangesDALFactory } from "@app/services/folder-commit-chang
|
|||||||
import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal";
|
import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal";
|
||||||
import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal";
|
import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal";
|
||||||
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
|
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { identityDALFactory } from "@app/services/identity/identity-dal";
|
import { identityDALFactory } from "@app/services/identity/identity-dal";
|
||||||
import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
||||||
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
||||||
@@ -209,6 +211,7 @@ import { identityOciAuthServiceFactory } from "@app/services/identity-oci-auth/i
|
|||||||
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
|
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
|
||||||
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
||||||
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
import { identityTlsCertAuthDALFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-dal";
|
import { identityTlsCertAuthDALFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-dal";
|
||||||
import { identityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-service";
|
import { identityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-service";
|
||||||
import { identityTokenAuthDALFactory } from "@app/services/identity-token-auth/identity-token-auth-dal";
|
import { identityTokenAuthDALFactory } from "@app/services/identity-token-auth/identity-token-auth-dal";
|
||||||
@@ -340,9 +343,6 @@ import { initializeOauthConfigSync } from "./v1/sso-router";
|
|||||||
import { registerV2Routes } from "./v2";
|
import { registerV2Routes } from "./v2";
|
||||||
import { registerV3Routes } from "./v3";
|
import { registerV3Routes } from "./v3";
|
||||||
import { registerV4Routes } from "./v4";
|
import { registerV4Routes } from "./v4";
|
||||||
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
|
||||||
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
|
||||||
import { convertorServiceFactory } from "@app/services/convertor/convertor-service";
|
|
||||||
|
|
||||||
const histogram = monitorEventLoopDelay({ resolution: 20 });
|
const histogram = monitorEventLoopDelay({ resolution: 20 });
|
||||||
histogram.enable();
|
histogram.enable();
|
||||||
@@ -560,6 +560,8 @@ export const registerRoutes = async (
|
|||||||
projectDAL
|
projectDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL });
|
||||||
|
|
||||||
const membershipUserService = membershipUserServiceFactory({
|
const membershipUserService = membershipUserServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
membershipRoleDAL,
|
membershipRoleDAL,
|
||||||
@@ -567,7 +569,13 @@ export const registerRoutes = async (
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
roleDAL,
|
roleDAL,
|
||||||
userDAL
|
userDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
smtpService,
|
||||||
|
tokenService,
|
||||||
|
userAliasDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const membershipIdentityService = membershipIdentityServiceFactory({
|
const membershipIdentityService = membershipIdentityServiceFactory({
|
||||||
@@ -581,7 +589,9 @@ export const registerRoutes = async (
|
|||||||
const membershipGroupService = membershipGroupServiceFactory({
|
const membershipGroupService = membershipGroupServiceFactory({
|
||||||
membershipGroupDAL,
|
membershipGroupDAL,
|
||||||
membershipRoleDAL,
|
membershipRoleDAL,
|
||||||
roleDAL
|
roleDAL,
|
||||||
|
permissionService,
|
||||||
|
orgDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const roleService = roleServiceFactory({
|
const roleService = roleServiceFactory({
|
||||||
@@ -663,7 +673,6 @@ export const registerRoutes = async (
|
|||||||
userDAL,
|
userDAL,
|
||||||
secretApprovalRequestDAL
|
secretApprovalRequestDAL
|
||||||
});
|
});
|
||||||
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL });
|
|
||||||
|
|
||||||
const samlService = samlConfigServiceFactory({
|
const samlService = samlConfigServiceFactory({
|
||||||
identityMetadataDAL,
|
identityMetadataDAL,
|
||||||
@@ -746,7 +755,6 @@ export const registerRoutes = async (
|
|||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
@@ -882,7 +890,8 @@ export const registerRoutes = async (
|
|||||||
reminderService,
|
reminderService,
|
||||||
membershipRoleDAL,
|
membershipRoleDAL,
|
||||||
membershipUserDAL,
|
membershipUserDAL,
|
||||||
roleDAL
|
roleDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
});
|
});
|
||||||
const signupService = authSignupServiceFactory({
|
const signupService = authSignupServiceFactory({
|
||||||
tokenService,
|
tokenService,
|
||||||
@@ -970,7 +979,6 @@ export const registerRoutes = async (
|
|||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL,
|
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
@@ -979,7 +987,9 @@ export const registerRoutes = async (
|
|||||||
secretReminderRecipientsDAL,
|
secretReminderRecipientsDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
notificationService,
|
notificationService,
|
||||||
membershipDAL
|
membershipUserDAL,
|
||||||
|
additionalPrivilegeDAL,
|
||||||
|
membershipRoleDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectKeyService = projectKeyServiceFactory({
|
const projectKeyService = projectKeyServiceFactory({
|
||||||
@@ -1438,7 +1448,9 @@ export const registerRoutes = async (
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
accessApprovalRequestDAL,
|
accessApprovalRequestDAL,
|
||||||
accessApprovalRequestReviewerDAL
|
accessApprovalRequestReviewerDAL,
|
||||||
|
additionalPrivilegeDAL,
|
||||||
|
membershipUserDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
||||||
@@ -1456,7 +1468,8 @@ export const registerRoutes = async (
|
|||||||
groupDAL,
|
groupDAL,
|
||||||
microsoftTeamsService,
|
microsoftTeamsService,
|
||||||
projectMicrosoftTeamsConfigDAL,
|
projectMicrosoftTeamsConfigDAL,
|
||||||
notificationService
|
notificationService,
|
||||||
|
additionalPrivilegeDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretReplicationService = secretReplicationServiceFactory({
|
const secretReplicationService = secretReplicationServiceFactory({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgMembershipRole, ProjectMembershipRole, UsersSchema } from "@app/db/schemas";
|
import { AccessScope, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
|
||||||
import { inviteUserRateLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
|
import { inviteUserRateLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -23,13 +23,6 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
.array()
|
.array()
|
||||||
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
|
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
projects: z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
projectRoleSlug: z.string().array().default([ProjectMembershipRole.Member])
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.optional(),
|
|
||||||
organizationRoleSlug: z.string().default(OrgMembershipRole.Member)
|
organizationRoleSlug: z.string().default(OrgMembershipRole.Member)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -50,15 +43,16 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
if (req.auth.actor !== ActorType.USER) return;
|
if (req.auth.actor !== ActorType.USER) return;
|
||||||
|
|
||||||
const { signupTokens: completeInviteLinks } = await server.services.org.inviteUserToOrganization({
|
const { signUpTokens: completeInviteLinks } = await server.services.membershipUser.createMembership({
|
||||||
orgId: req.body.organizationId,
|
permission: req.permission,
|
||||||
actor: req.permission.type,
|
scopeData: {
|
||||||
actorId: req.permission.id,
|
scope: AccessScope.Organization,
|
||||||
inviteeEmails: req.body.inviteeEmails,
|
orgId: req.permission.orgId
|
||||||
projects: req.body.projects,
|
},
|
||||||
organizationRoleSlug: req.body.organizationRoleSlug,
|
data: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
usernames: req.body.inviteeEmails,
|
||||||
actorOrgId: req.permission.orgId
|
roles: [{ isTemporary: false, role: req.body.organizationRoleSlug }]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.telemetry.sendPostHogEvents({
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
|
|||||||
@@ -435,13 +435,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.REMOVE_PROJECT_MEMBER,
|
type: EventType.REMOVE_PROJECT_MEMBER,
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: membership.userId,
|
userId: membership.actorUserId as string,
|
||||||
email: ""
|
email: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { memberships };
|
return {
|
||||||
|
memberships: memberships.map((el) => ({
|
||||||
|
...el,
|
||||||
|
userId: el.actorUserId as string,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -534,7 +540,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectId: req.params.projectId
|
projectId: req.params.projectId
|
||||||
});
|
});
|
||||||
return { membership };
|
return {
|
||||||
|
membership: {
|
||||||
|
...membership,
|
||||||
|
userId: membership.actorUserId as string,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AccessScope,
|
||||||
IdentitiesSchema,
|
IdentitiesSchema,
|
||||||
IdentityProjectMembershipsSchema,
|
IdentityProjectMembershipsSchema,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUserMembershipRolesSchema
|
ProjectUserMembershipRolesSchema,
|
||||||
|
TemporaryPermissionMode
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
|
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
@@ -14,7 +16,6 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
|
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
|
||||||
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
|
||||||
|
|
||||||
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
|
|||||||
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||||
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||||
temporaryMode: z
|
temporaryMode: z
|
||||||
.nativeEnum(ProjectUserMembershipTemporaryMode)
|
.nativeEnum(TemporaryPermissionMode)
|
||||||
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||||
temporaryRange: z
|
temporaryRange: z
|
||||||
.string()
|
.string()
|
||||||
@@ -82,16 +83,22 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
|
|||||||
const { role, roles } = req.body;
|
const { role, roles } = req.body;
|
||||||
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
|
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
|
||||||
|
|
||||||
const identityMembership = await server.services.identityProject.createProjectIdentity({
|
const { membership } = await server.services.membershipIdentity.createMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
scopeData: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
scope: AccessScope.Project,
|
||||||
actorOrgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId,
|
projectId: req.params.projectId
|
||||||
projectId: req.params.projectId,
|
},
|
||||||
roles: roles || [{ role }]
|
data: {
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
roles: roles || [{ role, isTemporary: false }]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { identityMembership };
|
|
||||||
|
return {
|
||||||
|
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,7 +137,7 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
|
|||||||
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
|
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||||
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
|
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
|
||||||
temporaryMode: z
|
temporaryMode: z
|
||||||
.nativeEnum(ProjectUserMembershipTemporaryMode)
|
.nativeEnum(TemporaryPermissionMode)
|
||||||
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
|
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
|
||||||
temporaryRange: z
|
temporaryRange: z
|
||||||
.string()
|
.string()
|
||||||
@@ -153,16 +160,24 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const roles = await server.services.identityProject.updateProjectIdentity({
|
const { membership } = await server.services.membershipIdentity.updateMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
scopeData: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
scope: AccessScope.Project,
|
||||||
actorOrgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId,
|
projectId: req.params.projectId
|
||||||
projectId: req.params.projectId,
|
},
|
||||||
roles: req.body.roles
|
selector: {
|
||||||
|
identityId: req.params.identityId
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
roles: req.body.roles
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { roles };
|
|
||||||
|
return {
|
||||||
|
roles: membership.roles.map((el) => ({ ...el, projectMembershipId: membership.id }))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -193,15 +208,21 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityMembership = await server.services.identityProject.deleteProjectIdentity({
|
const { membership } = await server.services.membershipIdentity.deleteMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
scopeData: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
scope: AccessScope.Project,
|
||||||
actorOrgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId,
|
projectId: req.params.projectId
|
||||||
projectId: req.params.projectId
|
},
|
||||||
|
selector: {
|
||||||
|
identityId: req.params.identityId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { identityMembership };
|
|
||||||
|
return {
|
||||||
|
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgMembershipRole, ProjectMembershipRole, ProjectMembershipsSchema } from "@app/db/schemas";
|
import { AccessScope, ProjectMembershipRole, ProjectMembershipsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags, PROJECT_USERS } from "@app/lib/api-docs";
|
import { ApiDocsTags, PROJECT_USERS } from "@app/lib/api-docs";
|
||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -51,20 +51,17 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
|
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
|
||||||
const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({
|
const { memberships } = await server.services.membershipUser.createMembership({
|
||||||
actorAuthMethod: req.permission.authMethod,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
scopeData: {
|
||||||
actorOrgId: req.permission.orgId,
|
scope: AccessScope.Project,
|
||||||
actor: req.permission.type,
|
orgId: req.permission.orgId,
|
||||||
inviteeEmails: usernamesAndEmails,
|
projectId: req.params.projectId
|
||||||
orgId: req.permission.orgId,
|
},
|
||||||
organizationRoleSlug: OrgMembershipRole.NoAccess,
|
data: {
|
||||||
projects: [
|
usernames: usernamesAndEmails,
|
||||||
{
|
roles: (req.body.roleSlugs || [ProjectMembershipRole.Member]).map((role) => ({ isTemporary: false, role }))
|
||||||
id: req.params.projectId,
|
}
|
||||||
projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -72,15 +69,21 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
|
|||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
event: {
|
event: {
|
||||||
type: EventType.ADD_BATCH_PROJECT_MEMBER,
|
type: EventType.ADD_BATCH_PROJECT_MEMBER,
|
||||||
metadata: memberships.map(({ userId, id }) => ({
|
metadata: memberships.map(({ actorUserId, id }) => ({
|
||||||
userId: userId || "",
|
userId: actorUserId || "",
|
||||||
membershipId: id,
|
membershipId: id,
|
||||||
email: ""
|
email: ""
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { memberships };
|
return {
|
||||||
|
memberships: memberships.map((el) => ({
|
||||||
|
...el,
|
||||||
|
userId: el.actorUserId as string,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,13 +146,19 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.REMOVE_PROJECT_MEMBER,
|
type: EventType.REMOVE_PROJECT_MEMBER,
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: membership.userId,
|
userId: membership.actorUserId as string,
|
||||||
email: ""
|
email: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { memberships };
|
return {
|
||||||
|
memberships: memberships.map((el) => ({
|
||||||
|
...el,
|
||||||
|
userId: el.actorUserId as string,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -220,7 +220,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
return { membership };
|
return {
|
||||||
|
membership: {
|
||||||
|
...membership,
|
||||||
|
role: "",
|
||||||
|
orgId: req.params.organizationId
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -260,7 +266,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
membershipId: req.params.membershipId,
|
membershipId: req.params.membershipId,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
return { membership };
|
return {
|
||||||
|
membership: {
|
||||||
|
...membership,
|
||||||
|
role: "",
|
||||||
|
orgId: req.params.organizationId
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -302,7 +314,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
membershipIds: req.body.membershipIds,
|
membershipIds: req.body.membershipIds,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
return { memberships };
|
return {
|
||||||
|
memberships: memberships.map((el) => ({
|
||||||
|
...el,
|
||||||
|
role: "",
|
||||||
|
orgId: req.params.organizationId
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -193,62 +193,62 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
})
|
})
|
||||||
.leftJoin<TIdentityUniversalAuths>(
|
.leftJoin<TIdentityUniversalAuths>(
|
||||||
TableName.IdentityUniversalAuth,
|
TableName.IdentityUniversalAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityUniversalAuth}.identityId`
|
`${TableName.IdentityUniversalAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityGcpAuths>(
|
.leftJoin<TIdentityGcpAuths>(
|
||||||
TableName.IdentityGcpAuth,
|
TableName.IdentityGcpAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityGcpAuth}.identityId`
|
`${TableName.IdentityGcpAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityAlicloudAuths>(
|
.leftJoin<TIdentityAlicloudAuths>(
|
||||||
TableName.IdentityAliCloudAuth,
|
TableName.IdentityAliCloudAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityAwsAuths>(
|
.leftJoin<TIdentityAwsAuths>(
|
||||||
TableName.IdentityAwsAuth,
|
TableName.IdentityAwsAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityAwsAuth}.identityId`
|
`${TableName.IdentityAwsAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityKubernetesAuths>(
|
.leftJoin<TIdentityKubernetesAuths>(
|
||||||
TableName.IdentityKubernetesAuth,
|
TableName.IdentityKubernetesAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityOciAuths>(
|
.leftJoin<TIdentityOciAuths>(
|
||||||
TableName.IdentityOciAuth,
|
TableName.IdentityOciAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityOciAuth}.identityId`
|
`${TableName.IdentityOciAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityOidcAuths>(
|
.leftJoin<TIdentityOidcAuths>(
|
||||||
TableName.IdentityOidcAuth,
|
TableName.IdentityOidcAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityOidcAuth}.identityId`
|
`${TableName.IdentityOidcAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityAzureAuths>(
|
.leftJoin<TIdentityAzureAuths>(
|
||||||
TableName.IdentityAzureAuth,
|
TableName.IdentityAzureAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityAzureAuth}.identityId`
|
`${TableName.IdentityAzureAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityTokenAuths>(
|
.leftJoin<TIdentityTokenAuths>(
|
||||||
TableName.IdentityTokenAuth,
|
TableName.IdentityTokenAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityTokenAuth}.identityId`
|
`${TableName.IdentityTokenAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityJwtAuths>(
|
.leftJoin<TIdentityJwtAuths>(
|
||||||
TableName.IdentityJwtAuth,
|
TableName.IdentityJwtAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityJwtAuth}.identityId`
|
`${TableName.IdentityJwtAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityLdapAuths>(
|
.leftJoin<TIdentityLdapAuths>(
|
||||||
TableName.IdentityLdapAuth,
|
TableName.IdentityLdapAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityLdapAuth}.identityId`
|
`${TableName.IdentityLdapAuth}.identityId`
|
||||||
)
|
)
|
||||||
.leftJoin<TIdentityTlsCertAuths>(
|
.leftJoin<TIdentityTlsCertAuths>(
|
||||||
TableName.IdentityTlsCertAuth,
|
TableName.IdentityTlsCertAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.actorIdentityId",
|
||||||
`${TableName.IdentityTlsCertAuth}.identityId`
|
`${TableName.IdentityTlsCertAuth}.identityId`
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { ProjectMembershipRole, TemporaryPermissionMode, TMembershipRolesInsert } from "@app/db/schemas";
|
import { AccessScope, ProjectMembershipRole, TemporaryPermissionMode, TMembershipRolesInsert } from "@app/db/schemas";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { SearchResourceOperators } from "@app/lib/search-resource/search";
|
import { SearchResourceOperators } from "@app/lib/search-resource/search";
|
||||||
|
|
||||||
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||||
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
import { TRoleDALFactory } from "../role/role-dal";
|
import { TRoleDALFactory } from "../role/role-dal";
|
||||||
import { TMembershipGroupDALFactory } from "./membership-group-dal";
|
import { TMembershipGroupDALFactory } from "./membership-group-dal";
|
||||||
import {
|
import {
|
||||||
@@ -12,14 +14,18 @@ import {
|
|||||||
TDeleteMembershipGroupDTO,
|
TDeleteMembershipGroupDTO,
|
||||||
TGetMembershipGroupByGroupIdDTO,
|
TGetMembershipGroupByGroupIdDTO,
|
||||||
TListMembershipGroupDTO,
|
TListMembershipGroupDTO,
|
||||||
TMembershipGroupScopeFactory,
|
|
||||||
TUpdateMembershipGroupDTO
|
TUpdateMembershipGroupDTO
|
||||||
} from "./membership-group-types";
|
} from "./membership-group-types";
|
||||||
|
import { newNamespaceMembershipGroupFactory } from "./namespace/namespace-membership-group-factory";
|
||||||
|
import { newOrgMembershipGroupFactory } from "./org/org-membership-group-factory";
|
||||||
|
import { newProjectMembershipGroupFactory } from "./project/project-membership-group-factory";
|
||||||
|
|
||||||
type TMembershipGroupServiceFactoryDep = {
|
type TMembershipGroupServiceFactoryDep = {
|
||||||
membershipGroupDAL: TMembershipGroupDALFactory;
|
membershipGroupDAL: TMembershipGroupDALFactory;
|
||||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
|
||||||
roleDAL: Pick<TRoleDALFactory, "find">;
|
roleDAL: Pick<TRoleDALFactory, "find">;
|
||||||
|
permissionService: TPermissionServiceFactory;
|
||||||
|
orgDAL: TOrgDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TMembershipGroupServiceFactory = ReturnType<typeof membershipGroupServiceFactory>;
|
export type TMembershipGroupServiceFactory = ReturnType<typeof membershipGroupServiceFactory>;
|
||||||
@@ -27,9 +33,22 @@ export type TMembershipGroupServiceFactory = ReturnType<typeof membershipGroupSe
|
|||||||
export const membershipGroupServiceFactory = ({
|
export const membershipGroupServiceFactory = ({
|
||||||
membershipGroupDAL,
|
membershipGroupDAL,
|
||||||
roleDAL,
|
roleDAL,
|
||||||
membershipRoleDAL
|
membershipRoleDAL,
|
||||||
|
orgDAL,
|
||||||
|
permissionService
|
||||||
}: TMembershipGroupServiceFactoryDep) => {
|
}: TMembershipGroupServiceFactoryDep) => {
|
||||||
const scopeFactory: Record<string, TMembershipGroupScopeFactory> = {};
|
const scopeFactory = {
|
||||||
|
[AccessScope.Organization]: newOrgMembershipGroupFactory({
|
||||||
|
orgDAL,
|
||||||
|
permissionService
|
||||||
|
}),
|
||||||
|
[AccessScope.Namespace]: newNamespaceMembershipGroupFactory({}),
|
||||||
|
[AccessScope.Project]: newProjectMembershipGroupFactory({
|
||||||
|
membershipGroupDAL,
|
||||||
|
orgDAL,
|
||||||
|
permissionService
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
const createMembership = async (dto: TCreateMembershipGroupDTO) => {
|
const createMembership = async (dto: TCreateMembershipGroupDTO) => {
|
||||||
const { scopeData, data } = dto;
|
const { scopeData, data } = dto;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AccessScope, ProjectMembershipRole, TemporaryPermissionMode, TMembershipRolesInsert } from "@app/db/schemas";
|
import { AccessScope, ProjectMembershipRole, TemporaryPermissionMode, TMembershipRolesInsert } 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 { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -7,10 +8,16 @@ import { ms } from "@app/lib/ms";
|
|||||||
import { SearchResourceOperators } from "@app/lib/search-resource/search";
|
import { SearchResourceOperators } from "@app/lib/search-resource/search";
|
||||||
|
|
||||||
import { AuthMethod } from "../auth/auth-type";
|
import { AuthMethod } from "../auth/auth-type";
|
||||||
|
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||||
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
|
import { deleteOrgMembershipsFn } from "../org/org-fns";
|
||||||
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||||
import { TRoleDALFactory } from "../role/role-dal";
|
import { TRoleDALFactory } from "../role/role-dal";
|
||||||
|
import { TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
|
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
|
||||||
import { TMembershipUserDALFactory } from "./membership-user-dal";
|
import { TMembershipUserDALFactory } from "./membership-user-dal";
|
||||||
import {
|
import {
|
||||||
TCreateMembershipUserDTO,
|
TCreateMembershipUserDTO,
|
||||||
@@ -26,23 +33,20 @@ import { newProjectMembershipUserFactory } from "./project/project-membership-us
|
|||||||
type TMembershipUserServiceFactoryDep = {
|
type TMembershipUserServiceFactoryDep = {
|
||||||
membershipUserDAL: TMembershipUserDALFactory;
|
membershipUserDAL: TMembershipUserDALFactory;
|
||||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
orgDAL: Pick<TOrgDALFactory, "findById" | "transaction">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
|
||||||
roleDAL: Pick<TRoleDALFactory, "find">;
|
roleDAL: Pick<TRoleDALFactory, "find">;
|
||||||
userDAL: Pick<
|
userDAL: TUserDALFactory;
|
||||||
TUserDALFactory,
|
|
||||||
| "findUserByUsername"
|
|
||||||
| "find"
|
|
||||||
| "transaction"
|
|
||||||
| "create"
|
|
||||||
| "createUserEncryption"
|
|
||||||
| "findUserByEmail"
|
|
||||||
| "findUserEncKeyByUserId"
|
|
||||||
>;
|
|
||||||
permissionService: Pick<
|
permissionService: Pick<
|
||||||
TPermissionServiceFactory,
|
TPermissionServiceFactory,
|
||||||
"getProjectPermission" | "getProjectPermissionByRoles" | "getOrgPermission"
|
"getProjectPermission" | "getProjectPermissionByRoles" | "getOrgPermission"
|
||||||
>;
|
>;
|
||||||
|
licenseService: TLicenseServiceFactory;
|
||||||
|
projectKeyDAL: TProjectKeyDALFactory;
|
||||||
|
userAliasDAL: TUserAliasDALFactory;
|
||||||
|
smtpService: TSmtpService;
|
||||||
|
tokenService: TAuthTokenServiceFactory;
|
||||||
|
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
||||||
|
projectDAL: TProjectDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TMembershipUserServiceFactory = ReturnType<typeof membershipUserServiceFactory>;
|
export type TMembershipUserServiceFactory = ReturnType<typeof membershipUserServiceFactory>;
|
||||||
@@ -50,20 +54,35 @@ export type TMembershipUserServiceFactory = ReturnType<typeof membershipUserServ
|
|||||||
export const membershipUserServiceFactory = ({
|
export const membershipUserServiceFactory = ({
|
||||||
membershipUserDAL,
|
membershipUserDAL,
|
||||||
roleDAL,
|
roleDAL,
|
||||||
licenseService,
|
|
||||||
membershipRoleDAL,
|
membershipRoleDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL
|
orgDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
userAliasDAL,
|
||||||
|
licenseService,
|
||||||
|
smtpService,
|
||||||
|
tokenService,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
projectDAL
|
||||||
}: TMembershipUserServiceFactoryDep) => {
|
}: TMembershipUserServiceFactoryDep) => {
|
||||||
const scopeFactory = {
|
const scopeFactory = {
|
||||||
[AccessScope.Organization]: newOrgMembershipUserFactory({
|
[AccessScope.Organization]: newOrgMembershipUserFactory({
|
||||||
permissionService
|
permissionService,
|
||||||
|
licenseService,
|
||||||
|
smtpService,
|
||||||
|
orgDAL,
|
||||||
|
tokenService,
|
||||||
|
userDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
}),
|
}),
|
||||||
[AccessScope.Namespace]: newNamespaceMembershipUserFactory({}),
|
[AccessScope.Namespace]: newNamespaceMembershipUserFactory({}),
|
||||||
[AccessScope.Project]: newProjectMembershipUserFactory({
|
[AccessScope.Project]: newProjectMembershipUserFactory({
|
||||||
orgDAL,
|
orgDAL,
|
||||||
permissionService
|
permissionService,
|
||||||
|
membershipUserDAL,
|
||||||
|
projectDAL,
|
||||||
|
smtpService
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,11 +176,11 @@ export const membershipUserServiceFactory = ({
|
|||||||
actorUserId: users.map((el) => el.id)
|
actorUserId: users.map((el) => el.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// TODO(simp): do we really do this - check it out
|
|
||||||
if (existingMemberships.length === users.length) return { memberships: [] };
|
if (existingMemberships.length === users.length) return { memberships: [] };
|
||||||
|
|
||||||
await factory.onCreateMembershipUserGuard(dto);
|
|
||||||
const newMembershipUsers = users.filter((user) => !existingMemberships?.find((el) => el.actorUserId === user.id));
|
const newMembershipUsers = users.filter((user) => !existingMemberships?.find((el) => el.actorUserId === user.id));
|
||||||
|
await factory.onCreateMembershipUserGuard(dto, newMembershipUsers);
|
||||||
const newMemberships = newMembershipUsers.map((user) => ({
|
const newMemberships = newMembershipUsers.map((user) => ({
|
||||||
scope: scopeData.scope,
|
scope: scopeData.scope,
|
||||||
...scopeDatabaseFields,
|
...scopeDatabaseFields,
|
||||||
@@ -230,7 +249,8 @@ export const membershipUserServiceFactory = ({
|
|||||||
return docs;
|
return docs;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { memberships: membershipDoc };
|
const { signUpTokens } = await factory.onCreateMembershipComplete(dto, newMembershipUsers);
|
||||||
|
return { memberships: membershipDoc, signUpTokens };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateMembership = async (dto: TUpdateMembershipUserDTO) => {
|
const updateMembership = async (dto: TUpdateMembershipUserDTO) => {
|
||||||
@@ -364,10 +384,26 @@ export const membershipUserServiceFactory = ({
|
|||||||
|
|
||||||
if (existingMembership.actorUserId === dto.permission.id)
|
if (existingMembership.actorUserId === dto.permission.id)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "You can delete you own membership"
|
message: "You can't delete you own membership"
|
||||||
});
|
});
|
||||||
|
|
||||||
const membershipDoc = await membershipUserDAL.transaction(async (tx) => {
|
const membershipDoc = await membershipUserDAL.transaction(async (tx) => {
|
||||||
|
if (dto.scopeData.scope === AccessScope.Organization) {
|
||||||
|
const [doc] = await deleteOrgMembershipsFn({
|
||||||
|
orgMembershipIds: [],
|
||||||
|
orgId: dto.permission.orgId,
|
||||||
|
orgDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
userAliasDAL,
|
||||||
|
licenseService,
|
||||||
|
userId: dto.permission.id,
|
||||||
|
membershipUserDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
membershipRoleDAL
|
||||||
|
});
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
await membershipRoleDAL.delete({ membershipId: existingMembership.id }, tx);
|
await membershipRoleDAL.delete({ membershipId: existingMembership.id }, tx);
|
||||||
const doc = await membershipUserDAL.deleteById(existingMembership.id, tx);
|
const doc = await membershipUserDAL.deleteById(existingMembership.id, tx);
|
||||||
return doc;
|
return doc;
|
||||||
|
|||||||
@@ -2,11 +2,18 @@ import { AccessScopeData, TemporaryPermissionMode } from "@app/db/schemas";
|
|||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
export interface TMembershipUserScopeFactory {
|
export interface TMembershipUserScopeFactory {
|
||||||
onCreateMembershipUserGuard: (arg: TCreateMembershipUserDTO) => Promise<void>;
|
onCreateMembershipUserGuard: (
|
||||||
onCreateMembershipComplete: (arg: { id: string; email: string }[]) => Promise<void>;
|
arg: TCreateMembershipUserDTO,
|
||||||
|
newMembers: { id: string; email?: string | null }[]
|
||||||
|
) => Promise<void>;
|
||||||
|
onCreateMembershipComplete: (
|
||||||
|
arg: TCreateMembershipUserDTO,
|
||||||
|
newMembers: { id: string; email?: string | null }[]
|
||||||
|
) => Promise<{ signUpTokens: { email: string; link: string }[] }>;
|
||||||
|
|
||||||
onUpdateMembershipUserGuard: (arg: TUpdateMembershipUserDTO) => Promise<void>;
|
onUpdateMembershipUserGuard: (arg: TUpdateMembershipUserDTO) => Promise<void>;
|
||||||
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<void>;
|
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO | TBulkDeleteMembershipByUsernameDTO) => Promise<void>;
|
||||||
|
|
||||||
onListMembershipUserGuard: (arg: TListMembershipUserDTO) => Promise<void>;
|
onListMembershipUserGuard: (arg: TListMembershipUserDTO) => Promise<void>;
|
||||||
onGetMembershipUserByUserIdGuard: (arg: TGetMembershipUserByUserIdDTO) => Promise<void>;
|
onGetMembershipUserByUserIdGuard: (arg: TGetMembershipUserByUserIdDTO) => Promise<void>;
|
||||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||||
@@ -71,6 +78,14 @@ export type TDeleteMembershipUserDTO = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TBulkDeleteMembershipByUsernameDTO = {
|
||||||
|
permission: OrgServiceActor;
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
data: {
|
||||||
|
usernames: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type TGetMembershipUserByUserIdDTO = {
|
export type TGetMembershipUserByUserIdDTO = {
|
||||||
permission: OrgServiceActor;
|
permission: OrgServiceActor;
|
||||||
scopeData: AccessScopeData;
|
scopeData: AccessScopeData;
|
||||||
|
|||||||
@@ -1,19 +1,39 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { AccessScope } from "@app/db/schemas";
|
import { AccessScope } 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";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { InternalServerError } from "@app/lib/errors";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
||||||
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import { TMembershipUserScopeFactory } from "../membership-user-types";
|
import { TMembershipUserScopeFactory } from "../membership-user-types";
|
||||||
|
|
||||||
type TOrgMembershipUserScopeFactoryDep = {
|
type TOrgMembershipUserScopeFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||||
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
|
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||||
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newOrgMembershipUserFactory = ({
|
export const newOrgMembershipUserFactory = ({
|
||||||
permissionService
|
permissionService,
|
||||||
|
tokenService,
|
||||||
|
userDAL,
|
||||||
|
orgDAL,
|
||||||
|
smtpService,
|
||||||
|
licenseService
|
||||||
}: TOrgMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
|
}: TOrgMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
|
||||||
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
|
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
|
||||||
if (dto.scope === AccessScope.Organization) {
|
if (dto.scope === AccessScope.Organization) {
|
||||||
@@ -40,11 +60,75 @@ export const newOrgMembershipUserFactory = ({
|
|||||||
dto.permission.orgId
|
dto.permission.orgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(dto.permission.orgId);
|
||||||
|
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
|
throw new BadRequestError({
|
||||||
|
name: "InviteUser",
|
||||||
|
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const org = await orgDAL.findById(dto.permission.orgId);
|
||||||
|
if (org?.authEnforced) {
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
name: "InviteUser",
|
||||||
|
message: "Failed to invite user due to org-level auth enforced for organization"
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async () => {
|
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async (
|
||||||
// TODO(simp): fix this
|
dto,
|
||||||
throw new InternalServerError({ message: "Org membership user create complete not implemented" });
|
newUsers
|
||||||
|
) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const actorDetails =
|
||||||
|
dto.permission.type === ActorType.USER
|
||||||
|
? await userDAL.findById(dto.permission.id)
|
||||||
|
: {
|
||||||
|
firstName: "Platform Identity",
|
||||||
|
email: "identity"
|
||||||
|
};
|
||||||
|
|
||||||
|
const signUpTokens: { email: string; link: string }[] = [];
|
||||||
|
const orgDetails = await orgDAL.findById(dto.permission.orgId);
|
||||||
|
|
||||||
|
await Promise.allSettled(
|
||||||
|
newUsers.map(async (el) => {
|
||||||
|
const token = await tokenService.createTokenForUser({
|
||||||
|
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
||||||
|
userId: el.id,
|
||||||
|
orgId: dto.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (el.email) {
|
||||||
|
signUpTokens.push({
|
||||||
|
email: el.email,
|
||||||
|
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${el.email}&organization_id=${dto.permission.orgId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
await smtpService.sendMail({
|
||||||
|
template: SmtpTemplates.OrgInvite,
|
||||||
|
subjectLine: "Infisical organization invitation",
|
||||||
|
recipients: [el.email],
|
||||||
|
substitutions: {
|
||||||
|
inviterFirstName: actorDetails?.firstName,
|
||||||
|
inviterUsername: actorDetails?.email,
|
||||||
|
organizationName: orgDetails?.name,
|
||||||
|
email: el.email,
|
||||||
|
organizationId: orgDetails?.id.toString(),
|
||||||
|
token,
|
||||||
|
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return { signUpTokens };
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
|
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
|
||||||
|
|||||||
@@ -11,19 +11,29 @@ import {
|
|||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
|
|
||||||
|
import { TMembershipUserDALFactory } from "../membership-user-dal";
|
||||||
import { TMembershipUserScopeFactory } from "../membership-user-types";
|
import { TMembershipUserScopeFactory } from "../membership-user-types";
|
||||||
|
|
||||||
type TProjectMembershipUserScopeFactoryDep = {
|
type TProjectMembershipUserScopeFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
|
membershipUserDAL: Pick<TMembershipUserDALFactory, "find">;
|
||||||
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newProjectMembershipUserFactory = ({
|
export const newProjectMembershipUserFactory = ({
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL
|
orgDAL,
|
||||||
|
projectDAL,
|
||||||
|
membershipUserDAL,
|
||||||
|
smtpService
|
||||||
}: TProjectMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
|
}: TProjectMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
|
||||||
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
|
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
|
||||||
if (dto.scope === AccessScope.Project) {
|
if (dto.scope === AccessScope.Project) {
|
||||||
@@ -41,7 +51,10 @@ export const newProjectMembershipUserFactory = ({
|
|||||||
|
|
||||||
const isCustomRole: TMembershipUserScopeFactory["isCustomRole"] = (role) => isCustomProjectRole(role);
|
const isCustomRole: TMembershipUserScopeFactory["isCustomRole"] = (role) => isCustomProjectRole(role);
|
||||||
|
|
||||||
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (dto) => {
|
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (
|
||||||
|
dto,
|
||||||
|
newUsers
|
||||||
|
) => {
|
||||||
const scope = getScopeField(dto.scopeData);
|
const scope = getScopeField(dto.scopeData);
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor: dto.permission.type,
|
actor: dto.permission.type,
|
||||||
@@ -53,6 +66,21 @@ export const newProjectMembershipUserFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
// TODO(namespace): this becomes tricky in namespace due to group flow
|
||||||
|
const orgMemberships = await membershipUserDAL.find({
|
||||||
|
scope: AccessScope.Organization,
|
||||||
|
scopeOrgId: dto.permission.orgId,
|
||||||
|
$in: {
|
||||||
|
actorUserId: newUsers.map((el) => el.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (orgMemberships.length !== newUsers.length) {
|
||||||
|
const missingUsers = newUsers
|
||||||
|
.filter((el) => !orgMemberships.find((memb) => memb.actorUserId === el.id))
|
||||||
|
.map((el) => el.email);
|
||||||
|
throw new BadRequestError({ message: `Users ${missingUsers.join(",")} not part of organization` });
|
||||||
|
}
|
||||||
|
|
||||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||||
const permissionRoles = await permissionService.getProjectPermissionByRoles(
|
const permissionRoles = await permissionService.getProjectPermissionByRoles(
|
||||||
dto.data.roles.filter((el) => el.role !== ProjectMembershipRole.NoAccess).map((el) => el.role),
|
dto.data.roles.filter((el) => el.role !== ProjectMembershipRole.NoAccess).map((el) => el.role),
|
||||||
@@ -80,8 +108,24 @@ export const newProjectMembershipUserFactory = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async () => {
|
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async (
|
||||||
throw new InternalServerError({ message: "Project membership user create complete not implemented" });
|
dto,
|
||||||
|
newMembers
|
||||||
|
) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const scope = getScopeField(dto.scopeData);
|
||||||
|
const project = await projectDAL.findById(scope.value);
|
||||||
|
const emails = newMembers.filter((el) => Boolean(el?.email)).map((el) => el?.email as string);
|
||||||
|
await smtpService.sendMail({
|
||||||
|
template: SmtpTemplates.WorkspaceInvite,
|
||||||
|
subjectLine: "Infisical project invitation",
|
||||||
|
recipients: emails,
|
||||||
|
substitutions: {
|
||||||
|
workspaceName: project.name,
|
||||||
|
callback_url: `${appCfg.SITE_URL}/login`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { signUpTokens: [] };
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
|
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const count = await db
|
const count = await db
|
||||||
.replicaNode()(TableName.Membership)
|
.replicaNode()(TableName.Membership)
|
||||||
.where(`${TableName.Membership}.orgId`, orgId)
|
.where(`${TableName.Membership}.scopeOrgId`, orgId)
|
||||||
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
||||||
.whereNotNull(`${TableName.Membership}.actorUserId`)
|
.whereNotNull(`${TableName.Membership}.actorUserId`)
|
||||||
.count("*")
|
.count("*")
|
||||||
@@ -376,7 +376,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
conn.ref("id").withSchema(TableName.Membership),
|
conn.ref("id").withSchema(TableName.Membership),
|
||||||
conn.ref("inviteEmail").withSchema(TableName.Membership),
|
conn.ref("inviteEmail").withSchema(TableName.Membership),
|
||||||
conn.ref("orgId").withSchema(TableName.Membership),
|
conn.ref("scopeOrgId").withSchema(TableName.Membership).as("orgId"),
|
||||||
db.ref("role").withSchema(TableName.MembershipRole),
|
db.ref("role").withSchema(TableName.MembershipRole),
|
||||||
db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
|
db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
|
||||||
conn.ref("status").withSchema(TableName.Membership),
|
conn.ref("status").withSchema(TableName.Membership),
|
||||||
@@ -416,7 +416,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
conn.ref("id").withSchema(TableName.Membership),
|
conn.ref("id").withSchema(TableName.Membership),
|
||||||
conn.ref("inviteEmail").withSchema(TableName.Membership),
|
conn.ref("inviteEmail").withSchema(TableName.Membership),
|
||||||
conn.ref("orgId").withSchema(TableName.Membership),
|
conn.ref("scopeOrgId").withSchema(TableName.Membership).as("orgId"),
|
||||||
conn.ref("role").withSchema(TableName.MembershipRole),
|
conn.ref("role").withSchema(TableName.MembershipRole),
|
||||||
conn.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
|
conn.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
|
||||||
conn.ref("status").withSchema(TableName.Membership),
|
conn.ref("status").withSchema(TableName.Membership),
|
||||||
|
|||||||
@@ -1,122 +1,59 @@
|
|||||||
import { AccessScope } from "@app/db/schemas";
|
import { AccessScope } 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 { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
|
||||||
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
|
|
||||||
|
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||||
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
|
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
|
||||||
|
|
||||||
type TDeleteOrgMembership = {
|
|
||||||
orgMembershipId: string;
|
|
||||||
orgId: string;
|
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipById" | "transaction">;
|
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId">;
|
|
||||||
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete">;
|
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
|
||||||
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
|
|
||||||
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
|
||||||
userId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TDeleteOrgMemberships = {
|
type TDeleteOrgMemberships = {
|
||||||
orgMembershipIds: string[];
|
orgMembershipIds: string[];
|
||||||
orgId: string;
|
orgId: string;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipsById" | "transaction">;
|
orgDAL: Pick<TOrgDALFactory, "transaction">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserIds">;
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">;
|
||||||
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete">;
|
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete" | "find">;
|
||||||
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "delete">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
||||||
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
|
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteOrgMembershipFn = async ({
|
|
||||||
orgMembershipId,
|
|
||||||
orgId,
|
|
||||||
orgDAL,
|
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
|
||||||
userAliasDAL,
|
|
||||||
licenseService,
|
|
||||||
userId,
|
|
||||||
membershipUserDAL
|
|
||||||
}: TDeleteOrgMembership) => {
|
|
||||||
const deletedMembership = await orgDAL.transaction(async (tx) => {
|
|
||||||
const orgMembership = await orgDAL.deleteMembershipById(orgMembershipId, orgId, tx);
|
|
||||||
|
|
||||||
if (userId && orgMembership.actorUserId === userId) {
|
|
||||||
// scott: this is temporary, we will add a leave org endpoint with proper handling to ensure org isn't abandoned/broken
|
|
||||||
throw new BadRequestError({ message: "You cannot remove yourself from an organization" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedUserId = orgMembership.actorUserId;
|
|
||||||
if (!deletedUserId) {
|
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
|
||||||
return orgMembership;
|
|
||||||
}
|
|
||||||
|
|
||||||
await userAliasDAL.delete(
|
|
||||||
{
|
|
||||||
userId: deletedUserId,
|
|
||||||
orgId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all the project memberships of the user in the organization
|
|
||||||
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, deletedUserId);
|
|
||||||
|
|
||||||
// Delete all the project memberships of the user in the organization
|
|
||||||
await membershipUserDAL.delete(
|
|
||||||
{
|
|
||||||
scope: AccessScope.Project,
|
|
||||||
$in: {
|
|
||||||
id: projectMemberships.map((membership) => membership.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all the project keys of the user in the organization
|
|
||||||
const projectKeys = await projectKeyDAL.find({
|
|
||||||
$in: {
|
|
||||||
projectId: projectMemberships.map((membership) => membership.projectId)
|
|
||||||
},
|
|
||||||
receiverId: deletedUserId
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete all the project keys of the user in the organization
|
|
||||||
await projectKeyDAL.delete(
|
|
||||||
{
|
|
||||||
$in: {
|
|
||||||
id: projectKeys.map((key) => key.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
|
||||||
return orgMembership;
|
|
||||||
});
|
|
||||||
|
|
||||||
return deletedMembership;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteOrgMembershipsFn = async ({
|
export const deleteOrgMembershipsFn = async ({
|
||||||
orgMembershipIds,
|
orgMembershipIds,
|
||||||
orgId,
|
orgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
userId,
|
userId,
|
||||||
membershipUserDAL
|
membershipUserDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
membershipRoleDAL
|
||||||
}: TDeleteOrgMemberships) => {
|
}: TDeleteOrgMemberships) => {
|
||||||
const deletedMemberships = await orgDAL.transaction(async (tx) => {
|
const deletedMemberships = await orgDAL.transaction(async (tx) => {
|
||||||
const orgMemberships = await orgDAL.deleteMembershipsById(orgMembershipIds, orgId, tx);
|
await membershipRoleDAL.delete(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
membershipId: orgMembershipIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const orgMemberships = await membershipUserDAL.delete(
|
||||||
|
{
|
||||||
|
scopeOrgId: orgId,
|
||||||
|
scope: AccessScope.Organization,
|
||||||
|
$in: {
|
||||||
|
id: orgMembershipIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
const membershipUserIds = orgMemberships
|
const membershipUserIds = orgMemberships
|
||||||
.filter((member) => Boolean(member.actorUserId))
|
.filter((member) => Boolean(member.actorUserId))
|
||||||
@@ -143,32 +80,44 @@ export const deleteOrgMembershipsFn = async ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Get all the project memberships of the users in the organization
|
// Get all the project memberships of the users in the organization
|
||||||
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserIds(orgId, membershipUserIds);
|
|
||||||
|
|
||||||
// Delete all the project memberships of the users in the organization
|
// Delete all the project memberships of the users in the organization
|
||||||
await membershipUserDAL.delete(
|
const otherMemberships = await membershipUserDAL.delete(
|
||||||
{
|
{
|
||||||
scope: AccessScope.Project,
|
scopeOrgId: orgId,
|
||||||
$in: {
|
$in: {
|
||||||
id: projectMemberships.map((membership) => membership.id)
|
actorUserId: membershipUserIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get all the project keys of the user in the organization
|
const orgGroups = await membershipUserDAL.find({
|
||||||
const projectKeys = await projectKeyDAL.find({
|
scopeOrgId: orgId,
|
||||||
$in: {
|
$notNull: ["actorGroupId"]
|
||||||
projectId: projectMemberships.map((membership) => membership.projectId),
|
|
||||||
receiverId: membershipUserIds
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const groupIds = orgGroups.filter((el) => el.actorGroupId).map((el) => el.actorGroupId as string);
|
||||||
|
|
||||||
|
await userGroupMembershipDAL.delete(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
userId: membershipUserIds,
|
||||||
|
groupId: groupIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const projectIds = otherMemberships
|
||||||
|
.filter((el) => el.scope === AccessScope.Project && el.scopeProjectId)
|
||||||
|
.map((el) => el.scopeProjectId as string);
|
||||||
|
|
||||||
// Delete all the project keys of the user in the organization
|
// Delete all the project keys of the user in the organization
|
||||||
await projectKeyDAL.delete(
|
await projectKeyDAL.delete(
|
||||||
{
|
{
|
||||||
$in: {
|
$in: {
|
||||||
id: projectKeys.map((key) => key.id)
|
projectId: projectIds,
|
||||||
|
receiverId: membershipUserIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
|
|||||||
@@ -1,237 +0,0 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
|
||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
|
|
||||||
import { TOrgRolesInsert, TOrgRolesUpdate } from "@app/db/schemas";
|
|
||||||
import {
|
|
||||||
orgAdminPermissions,
|
|
||||||
orgMemberPermissions,
|
|
||||||
orgNoAccessPermissions,
|
|
||||||
OrgPermissionActions,
|
|
||||||
OrgPermissionSubjects
|
|
||||||
} from "@app/ee/services/permission/org-permission";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|
||||||
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
|
||||||
|
|
||||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
|
||||||
|
|
||||||
type TOrgRoleServiceFactoryDep = {
|
|
||||||
orgRoleDAL: TOrgRoleDALFactory;
|
|
||||||
permissionService: TPermissionServiceFactory;
|
|
||||||
orgDAL: TOrgDALFactory;
|
|
||||||
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TOrgRoleServiceFactory = ReturnType<typeof orgRoleServiceFactory>;
|
|
||||||
|
|
||||||
export const orgRoleServiceFactory = ({
|
|
||||||
orgRoleDAL,
|
|
||||||
orgDAL,
|
|
||||||
permissionService,
|
|
||||||
externalGroupOrgRoleMappingDAL
|
|
||||||
}: TOrgRoleServiceFactoryDep) => {
|
|
||||||
const createRole = async (
|
|
||||||
userId: string,
|
|
||||||
orgId: string,
|
|
||||||
data: Omit<TOrgRolesInsert, "orgId">,
|
|
||||||
actorAuthMethod: ActorAuthMethod,
|
|
||||||
actorOrgId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
|
|
||||||
const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId });
|
|
||||||
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
|
|
||||||
const role = await orgRoleDAL.create({
|
|
||||||
...data,
|
|
||||||
orgId,
|
|
||||||
permissions: JSON.stringify(data.permissions)
|
|
||||||
});
|
|
||||||
return role;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRole = async (
|
|
||||||
userId: string,
|
|
||||||
orgId: string,
|
|
||||||
roleId: string,
|
|
||||||
actorAuthMethod: ActorAuthMethod,
|
|
||||||
actorOrgId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
|
||||||
|
|
||||||
switch (roleId) {
|
|
||||||
case "b11b49a9-09a9-4443-916a-4246f9ff2c69": {
|
|
||||||
return {
|
|
||||||
id: roleId,
|
|
||||||
orgId,
|
|
||||||
name: "Admin",
|
|
||||||
slug: "admin",
|
|
||||||
description: "Complete administration access over the organization",
|
|
||||||
permissions: packRules(orgAdminPermissions),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "b11b49a9-09a9-4443-916a-4246f9ff2c70": {
|
|
||||||
return {
|
|
||||||
id: roleId,
|
|
||||||
orgId,
|
|
||||||
name: "Member",
|
|
||||||
slug: "member",
|
|
||||||
description: "Non-administrative role in an organization",
|
|
||||||
permissions: packRules(orgMemberPermissions),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "b10d49a9-09a9-4443-916a-4246f9ff2c72": {
|
|
||||||
return {
|
|
||||||
id: "b10d49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response
|
|
||||||
orgId,
|
|
||||||
name: "No Access",
|
|
||||||
slug: "no-access",
|
|
||||||
description: "No access to any resources in the organization",
|
|
||||||
permissions: packRules(orgNoAccessPermissions),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
const role = await orgRoleDAL.findOne({ id: roleId, orgId });
|
|
||||||
if (!role) throw new NotFoundError({ message: `Organization role with ID '${roleId}' not found` });
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateRole = async (
|
|
||||||
userId: string,
|
|
||||||
orgId: string,
|
|
||||||
roleId: string,
|
|
||||||
data: Omit<TOrgRolesUpdate, "orgId">,
|
|
||||||
actorAuthMethod: ActorAuthMethod,
|
|
||||||
actorOrgId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role);
|
|
||||||
if (data?.slug) {
|
|
||||||
const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId });
|
|
||||||
if (existingRole && existingRole.id !== roleId)
|
|
||||||
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
|
|
||||||
}
|
|
||||||
const [updatedRole] = await orgRoleDAL.update(
|
|
||||||
{ id: roleId, orgId },
|
|
||||||
{ ...data, permissions: data.permissions ? JSON.stringify(data.permissions) : undefined }
|
|
||||||
);
|
|
||||||
if (!updatedRole) throw new NotFoundError({ message: `Organization role with ID '${roleId}' not found` });
|
|
||||||
return updatedRole;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteRole = async (
|
|
||||||
userId: string,
|
|
||||||
orgId: string,
|
|
||||||
roleId: string,
|
|
||||||
actorAuthMethod: ActorAuthMethod,
|
|
||||||
actorOrgId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
|
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
|
||||||
|
|
||||||
if (!org)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Organization with ID '${orgId}' not found`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (org.defaultMembershipRole === roleId)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Cannot delete default org membership role. Please re-assign and try again."
|
|
||||||
});
|
|
||||||
|
|
||||||
const externalGroupMapping = await externalGroupOrgRoleMappingDAL.findOne({
|
|
||||||
orgId,
|
|
||||||
roleId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (externalGroupMapping)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message:
|
|
||||||
"Cannot delete role assigned to external group organization role mapping. Please re-assign external mapping and try again."
|
|
||||||
});
|
|
||||||
|
|
||||||
const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId });
|
|
||||||
if (!deletedRole)
|
|
||||||
throw new NotFoundError({ message: `Organization role with ID '${roleId}' not found`, name: "UpdateRole" });
|
|
||||||
|
|
||||||
return deletedRole;
|
|
||||||
};
|
|
||||||
|
|
||||||
const listRoles = async (
|
|
||||||
userId: string,
|
|
||||||
orgId: string,
|
|
||||||
actorAuthMethod: ActorAuthMethod,
|
|
||||||
actorOrgId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
|
||||||
const customRoles = await orgRoleDAL.find({ orgId });
|
|
||||||
const roles = [
|
|
||||||
{
|
|
||||||
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // dummy userid
|
|
||||||
orgId,
|
|
||||||
name: "Admin",
|
|
||||||
slug: "admin",
|
|
||||||
description: "Complete administration access over the organization",
|
|
||||||
permissions: packRules(orgAdminPermissions),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "b11b49a9-09a9-4443-916a-4246f9ff2c70", // dummy user for zod validation in response
|
|
||||||
orgId,
|
|
||||||
name: "Member",
|
|
||||||
slug: "member",
|
|
||||||
description: "Non-administrative role in an organization",
|
|
||||||
permissions: packRules(orgMemberPermissions),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "b10d49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response
|
|
||||||
orgId,
|
|
||||||
name: "No Access",
|
|
||||||
slug: "no-access",
|
|
||||||
description: "No access to any resources in the organization",
|
|
||||||
permissions: packRules(orgNoAccessPermissions),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
},
|
|
||||||
...(customRoles || []).map(({ permissions, ...data }) => ({
|
|
||||||
...data,
|
|
||||||
permissions
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
return roles;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUserPermission = async (
|
|
||||||
userId: string,
|
|
||||||
orgId: string,
|
|
||||||
actorAuthMethod: ActorAuthMethod,
|
|
||||||
actorOrgId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { permission, memberships } = await permissionService.getOrgPermission(
|
|
||||||
ActorType.USER,
|
|
||||||
userId,
|
|
||||||
orgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
);
|
|
||||||
return { permissions: packRules(permission.rules), memberships };
|
|
||||||
};
|
|
||||||
|
|
||||||
return { createRole, getRole, updateRole, deleteRole, listRoles, getUserPermission };
|
|
||||||
};
|
|
||||||
@@ -4,19 +4,14 @@ import { Knex } from "knex";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AccessScope,
|
AccessScope,
|
||||||
ActionProjectType,
|
|
||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
OrgMembershipStatus,
|
OrgMembershipStatus,
|
||||||
ProjectMembershipRole,
|
|
||||||
ProjectVersion,
|
|
||||||
TableName,
|
TableName,
|
||||||
TOidcConfigs,
|
TOidcConfigs,
|
||||||
TProjectMemberships,
|
TSamlConfigs
|
||||||
TProjectUserMembershipRolesInsert,
|
|
||||||
TSamlConfigs,
|
|
||||||
TUsers
|
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TLdapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
import { TLdapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||||
@@ -26,29 +21,16 @@ import {
|
|||||||
OrgPermissionSecretShareAction,
|
OrgPermissionSecretShareAction,
|
||||||
OrgPermissionSubjects
|
OrgPermissionSubjects
|
||||||
} from "@app/ee/services/permission/org-permission";
|
} from "@app/ee/services/permission/org-permission";
|
||||||
import {
|
|
||||||
constructPermissionErrorMessage,
|
|
||||||
validatePrivilegeChangeOperation
|
|
||||||
} from "@app/ee/services/permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
|
||||||
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
||||||
import { applyJitter } from "@app/lib/dates";
|
import { applyJitter } from "@app/lib/dates";
|
||||||
import { delay as delayMs } from "@app/lib/delay";
|
import { delay as delayMs } from "@app/lib/delay";
|
||||||
import {
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
BadRequestError,
|
|
||||||
ForbiddenRequestError,
|
|
||||||
NotFoundError,
|
|
||||||
PermissionBoundaryError,
|
|
||||||
UnauthorizedError
|
|
||||||
} from "@app/lib/errors";
|
|
||||||
import { groupBy } from "@app/lib/fn";
|
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
import { getDefaultOrgMembershipRoleForUpdateOrg } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRoleForUpdateOrg } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
@@ -76,7 +58,7 @@ import { TUserDALFactory } from "../user/user-dal";
|
|||||||
import { TIncidentContactsDALFactory } from "./incident-contacts-dal";
|
import { TIncidentContactsDALFactory } from "./incident-contacts-dal";
|
||||||
import { TOrgBotDALFactory } from "./org-bot-dal";
|
import { TOrgBotDALFactory } from "./org-bot-dal";
|
||||||
import { TOrgDALFactory } from "./org-dal";
|
import { TOrgDALFactory } from "./org-dal";
|
||||||
import { deleteOrgMembershipFn, deleteOrgMembershipsFn } from "./org-fns";
|
import { deleteOrgMembershipsFn } from "./org-fns";
|
||||||
import {
|
import {
|
||||||
TDeleteOrgMembershipDTO,
|
TDeleteOrgMembershipDTO,
|
||||||
TDeleteOrgMembershipsDTO,
|
TDeleteOrgMembershipsDTO,
|
||||||
@@ -84,7 +66,6 @@ import {
|
|||||||
TFindOrgMembersByEmailDTO,
|
TFindOrgMembersByEmailDTO,
|
||||||
TGetOrgGroupsDTO,
|
TGetOrgGroupsDTO,
|
||||||
TGetOrgMembershipDTO,
|
TGetOrgMembershipDTO,
|
||||||
TInviteUserToOrgDTO,
|
|
||||||
TListProjectMembershipsByOrgMembershipIdDTO,
|
TListProjectMembershipsByOrgMembershipIdDTO,
|
||||||
TResendOrgMemberInvitationDTO,
|
TResendOrgMemberInvitationDTO,
|
||||||
TUpdateOrgDTO,
|
TUpdateOrgDTO,
|
||||||
@@ -130,6 +111,7 @@ type TOrgServiceFactoryDep = {
|
|||||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
loginService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
loginService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
||||||
reminderService: Pick<TReminderServiceFactory, "deleteReminderBySecretId">;
|
reminderService: Pick<TReminderServiceFactory, "deleteReminderBySecretId">;
|
||||||
|
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
||||||
@@ -161,7 +143,8 @@ export const orgServiceFactory = ({
|
|||||||
loginService,
|
loginService,
|
||||||
reminderService,
|
reminderService,
|
||||||
membershipRoleDAL,
|
membershipRoleDAL,
|
||||||
membershipUserDAL
|
membershipUserDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
}: TOrgServiceFactoryDep) => {
|
}: TOrgServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Get organization details by the organization id
|
* Get organization details by the organization id
|
||||||
@@ -898,369 +881,6 @@ export const orgServiceFactory = ({
|
|||||||
return { signupToken: undefined };
|
return { signupToken: undefined };
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* Invite user to organization
|
|
||||||
*/
|
|
||||||
const inviteUserToOrganization = async ({
|
|
||||||
orgId,
|
|
||||||
actorId,
|
|
||||||
actor,
|
|
||||||
inviteeEmails,
|
|
||||||
organizationRoleSlug,
|
|
||||||
projects: invitedProjects,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
}: TInviteUserToOrgDTO) => {
|
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
|
||||||
|
|
||||||
const invitingUser = await userDAL.findOne({ id: actorId });
|
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
|
||||||
|
|
||||||
const isEmailInvalid = await isDisposableEmail(inviteeEmails);
|
|
||||||
if (isEmailInvalid) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Disposable emails are not allowed",
|
|
||||||
name: "InviteUser"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
|
||||||
const isCustomOrgRole = !Object.values(OrgMembershipRole).includes(organizationRoleSlug as OrgMembershipRole);
|
|
||||||
if (isCustomOrgRole) {
|
|
||||||
if (!plan?.rbac)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectsToInvite = invitedProjects?.length
|
|
||||||
? await projectDAL.find({
|
|
||||||
orgId,
|
|
||||||
$in: {
|
|
||||||
id: invitedProjects?.map(({ id }) => id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (projectsToInvite.length !== invitedProjects?.length) {
|
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
message: "Access denied to one or more of the specified projects"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectsToInvite.some((el) => el.version !== ProjectVersion.V3)) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "One or more selected projects are not compatible with this operation. Please upgrade your projects."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const mailsForOrgInvitation: { email: string; userId: string; firstName: string; lastName: string }[] = [];
|
|
||||||
const mailsForProjectInvitation: { email: string[]; projectName: string }[] = [];
|
|
||||||
const newProjectMemberships: TProjectMemberships[] = [];
|
|
||||||
|
|
||||||
await orgDAL.transaction(async (tx) => {
|
|
||||||
const users: Pick<TUsers, "id" | "firstName" | "lastName" | "email" | "username">[] = [];
|
|
||||||
|
|
||||||
for await (const inviteeEmail of inviteeEmails) {
|
|
||||||
const usersByUsername = await userDAL.findUserByUsername(inviteeEmail, tx);
|
|
||||||
let inviteeUser =
|
|
||||||
usersByUsername?.length > 1
|
|
||||||
? usersByUsername.find((el) => el.username === inviteeEmail)
|
|
||||||
: usersByUsername?.[0];
|
|
||||||
|
|
||||||
// if the user doesn't exist we create the user with the email
|
|
||||||
if (!inviteeUser) {
|
|
||||||
// TODO(carlos): will be removed once the function receives usernames instead of emails
|
|
||||||
const usersByEmail = await userDAL.findUserByEmail(inviteeEmail, tx);
|
|
||||||
if (usersByEmail?.length === 1) {
|
|
||||||
[inviteeUser] = usersByEmail;
|
|
||||||
} else {
|
|
||||||
inviteeUser = await userDAL.create(
|
|
||||||
{
|
|
||||||
isAccepted: false,
|
|
||||||
email: inviteeEmail,
|
|
||||||
username: inviteeEmail,
|
|
||||||
authMethods: [AuthMethod.EMAIL],
|
|
||||||
isGhost: false
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inviteeUserId = inviteeUser?.id;
|
|
||||||
const existingEncrytionKey = await userDAL.findUserEncKeyByUserId(inviteeUserId, tx);
|
|
||||||
|
|
||||||
// when user is missing the encrytion keys
|
|
||||||
// this could happen either if user doesn't exist or user didn't find step 3 of generating the encryption keys of srp
|
|
||||||
// So what we do is we generate a random secure password and then encrypt it with a random pub-private key
|
|
||||||
// Then when user sign in (as login is not possible as isAccepted is false) we rencrypt the private key with the user password
|
|
||||||
if (!inviteeUser || (inviteeUser && !inviteeUser?.isAccepted && !existingEncrytionKey)) {
|
|
||||||
await userDAL.createUserEncryption(
|
|
||||||
{
|
|
||||||
userId: inviteeUserId,
|
|
||||||
encryptionVersion: 2
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [inviteeOrgMembership] = await orgDAL.findMembership(
|
|
||||||
{
|
|
||||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
|
||||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
|
|
||||||
[`${TableName.Membership}.id` as "id"]: inviteeUserId
|
|
||||||
},
|
|
||||||
{ tx }
|
|
||||||
);
|
|
||||||
|
|
||||||
// if there exist no org membership we set is as given by the request
|
|
||||||
if (!inviteeOrgMembership) {
|
|
||||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
|
||||||
throw new BadRequestError({
|
|
||||||
name: "InviteUser",
|
|
||||||
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (org?.authEnforced) {
|
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
name: "InviteUser",
|
|
||||||
message: "Failed to invite user due to org-level auth enforced for organization"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// as its used by project invite also
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
|
||||||
let roleId;
|
|
||||||
const orgRole = isCustomOrgRole ? OrgMembershipRole.Custom : organizationRoleSlug;
|
|
||||||
if (isCustomOrgRole) {
|
|
||||||
const customRole = await roleDAL.findOne({ slug: organizationRoleSlug, orgId });
|
|
||||||
if (!customRole) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
name: "InviteUser",
|
|
||||||
message: `Custom organization role with slug '${orgRole}' not found`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
roleId = customRole.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const membership = await orgDAL.createMembership(
|
|
||||||
{
|
|
||||||
actorUserId: inviteeUser.id,
|
|
||||||
inviteEmail: inviteeEmail,
|
|
||||||
scopeOrgId: orgId,
|
|
||||||
status: OrgMembershipStatus.Invited,
|
|
||||||
isActive: true,
|
|
||||||
scope: AccessScope.Organization
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
await membershipRoleDAL.create(
|
|
||||||
{
|
|
||||||
membershipId: membership.id,
|
|
||||||
role: orgRole,
|
|
||||||
customRoleId: roleId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
mailsForOrgInvitation.push({
|
|
||||||
email: inviteeEmail,
|
|
||||||
userId: inviteeUser.id,
|
|
||||||
firstName: inviteeUser?.firstName || "",
|
|
||||||
lastName: inviteeUser.lastName || ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
users.push(inviteeUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userIds = users.map(({ id }) => id);
|
|
||||||
const userEncryptionKeys = await userDAL.findUserEncKeyByUserIdsBatch({ userIds }, tx);
|
|
||||||
// we don't need to spam with email. Thus org invitation doesn't need project invitation again
|
|
||||||
const userIdsWithOrgInvitation = new Set(mailsForOrgInvitation.map((el) => el.userId));
|
|
||||||
|
|
||||||
// if there exist no project membership we set is as given by the request
|
|
||||||
for await (const project of projectsToInvite) {
|
|
||||||
const projectId = project.id;
|
|
||||||
const { permission: projectPermission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
|
||||||
ProjectPermissionMemberActions.Create,
|
|
||||||
ProjectPermissionSub.Member
|
|
||||||
);
|
|
||||||
const existingMembers = await membershipUserDAL.find(
|
|
||||||
{
|
|
||||||
scopeOrgId: project.orgId,
|
|
||||||
scope: AccessScope.Project,
|
|
||||||
scopeProjectId: project.id,
|
|
||||||
$in: { userId: userIds }
|
|
||||||
},
|
|
||||||
{ tx }
|
|
||||||
);
|
|
||||||
const existingMembersGroupByUserId = groupBy(existingMembers, (i) => i.actorUserId as string);
|
|
||||||
const userWithEncryptionKeyInvitedToProject = userEncryptionKeys.filter(
|
|
||||||
(user) => !existingMembersGroupByUserId?.[user.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
if (!userWithEncryptionKeyInvitedToProject.length) continue;
|
|
||||||
|
|
||||||
// validate custom project role
|
|
||||||
const invitedProjectRoles = invitedProjects.find((el) => el.id === project.id)?.projectRoleSlug || [
|
|
||||||
ProjectMembershipRole.Member
|
|
||||||
];
|
|
||||||
|
|
||||||
for await (const invitedRole of invitedProjectRoles) {
|
|
||||||
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
|
|
||||||
invitedRole,
|
|
||||||
projectId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (invitedRole !== ProjectMembershipRole.NoAccess) {
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member,
|
|
||||||
projectPermission,
|
|
||||||
rolePermission
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to invite user to the project",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const customProjectRoles = invitedProjectRoles.filter(
|
|
||||||
(role) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
|
||||||
);
|
|
||||||
const hasCustomRole = Boolean(customProjectRoles.length);
|
|
||||||
if (hasCustomRole) {
|
|
||||||
if (!plan?.rbac)
|
|
||||||
throw new BadRequestError({
|
|
||||||
name: "InviteUser",
|
|
||||||
message:
|
|
||||||
"Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const customRoles = hasCustomRole
|
|
||||||
? await projectRoleDAL.find({
|
|
||||||
projectId,
|
|
||||||
$in: { slug: customProjectRoles.map((role) => role) }
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
if (customRoles.length !== customProjectRoles.length) {
|
|
||||||
throw new NotFoundError({ name: "InviteUser", message: "Custom project role not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
|
||||||
|
|
||||||
const projectMemberships = await projectMembershipDAL.insertMany(
|
|
||||||
userWithEncryptionKeyInvitedToProject.map((userEnc) => ({
|
|
||||||
projectId,
|
|
||||||
userId: userEnc.userId
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
newProjectMemberships.push(...projectMemberships);
|
|
||||||
|
|
||||||
const sanitizedProjectMembershipRoles: TProjectUserMembershipRolesInsert[] = [];
|
|
||||||
invitedProjectRoles.forEach((projectRole) => {
|
|
||||||
const isCustomRole = Boolean(customRolesGroupBySlug?.[projectRole]?.[0]);
|
|
||||||
projectMemberships.forEach((membershipEntry) => {
|
|
||||||
sanitizedProjectMembershipRoles.push({
|
|
||||||
projectMembershipId: membershipEntry.id,
|
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : projectRole,
|
|
||||||
customRoleId: customRolesGroupBySlug[projectRole] ? customRolesGroupBySlug[projectRole][0].id : null
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await projectUserMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
|
||||||
|
|
||||||
mailsForProjectInvitation.push({
|
|
||||||
email: userWithEncryptionKeyInvitedToProject
|
|
||||||
.filter((el) => !userIdsWithOrgInvitation.has(el.userId))
|
|
||||||
.map((el) => el.email || el.username),
|
|
||||||
projectName: project.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return users;
|
|
||||||
});
|
|
||||||
|
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
|
||||||
const signupTokens: { email: string; link: string }[] = [];
|
|
||||||
// send org invite mail
|
|
||||||
await Promise.allSettled(
|
|
||||||
mailsForOrgInvitation.map(async (el) => {
|
|
||||||
const token = await tokenService.createTokenForUser({
|
|
||||||
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
|
||||||
userId: el.userId,
|
|
||||||
orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
signupTokens.push({
|
|
||||||
email: el.email,
|
|
||||||
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${el.email}&organization_id=${org?.id}`
|
|
||||||
});
|
|
||||||
|
|
||||||
return smtpService.sendMail({
|
|
||||||
template: SmtpTemplates.OrgInvite,
|
|
||||||
subjectLine: "Infisical organization invitation",
|
|
||||||
recipients: [el.email],
|
|
||||||
substitutions: {
|
|
||||||
inviterFirstName: invitingUser?.firstName,
|
|
||||||
inviterUsername: invitingUser?.email,
|
|
||||||
organizationName: org?.name,
|
|
||||||
email: el.email,
|
|
||||||
organizationId: org?.id.toString(),
|
|
||||||
token,
|
|
||||||
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.allSettled(
|
|
||||||
mailsForProjectInvitation
|
|
||||||
.filter((el) => Boolean(el.email.length))
|
|
||||||
.map(async (el) => {
|
|
||||||
return smtpService.sendMail({
|
|
||||||
template: SmtpTemplates.WorkspaceInvite,
|
|
||||||
subjectLine: "Infisical project invitation",
|
|
||||||
recipients: el.email,
|
|
||||||
substitutions: {
|
|
||||||
workspaceName: el.projectName,
|
|
||||||
callback_url: `${appCfg.SITE_URL}/login`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!appCfg.isSmtpConfigured) {
|
|
||||||
return { signupTokens, projectMemberships: newProjectMemberships };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { signupTokens: undefined, projectMemberships: newProjectMemberships };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Organization invitation step 2: Verify that code [code] was sent to email [email] as part of
|
* Organization invitation step 2: Verify that code [code] was sent to email [email] as part of
|
||||||
* magic link and issue a temporary signup token for user to complete setting up their account
|
* magic link and issue a temporary signup token for user to complete setting up their account
|
||||||
@@ -1370,16 +990,17 @@ export const orgServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
|
||||||
|
|
||||||
const deletedMembership = await deleteOrgMembershipFn({
|
const [deletedMembership] = await deleteOrgMembershipsFn({
|
||||||
orgMembershipId: membershipId,
|
orgMembershipIds: [membershipId],
|
||||||
orgId,
|
orgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
userId,
|
userId,
|
||||||
membershipUserDAL
|
membershipUserDAL,
|
||||||
|
membershipRoleDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
return deletedMembership;
|
return deletedMembership;
|
||||||
@@ -1409,12 +1030,13 @@ export const orgServiceFactory = ({
|
|||||||
orgMembershipIds: membershipIds,
|
orgMembershipIds: membershipIds,
|
||||||
orgId,
|
orgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
userId,
|
userId,
|
||||||
membershipUserDAL
|
membershipUserDAL,
|
||||||
|
membershipRoleDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
return deletedMemberships;
|
return deletedMemberships;
|
||||||
@@ -1572,7 +1194,6 @@ export const orgServiceFactory = ({
|
|||||||
findAllOrgMembers,
|
findAllOrgMembers,
|
||||||
findAllOrganizationOfUser,
|
findAllOrganizationOfUser,
|
||||||
findIdentityOrganization,
|
findIdentityOrganization,
|
||||||
inviteUserToOrganization,
|
|
||||||
verifyUserToOrg,
|
verifyUserToOrg,
|
||||||
updateOrg,
|
updateOrg,
|
||||||
findOrgMembersByUsername,
|
findOrgMembersByUsername,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
.join<TMemberships>(db(TableName.Membership).as("orgMembership"), (qb) => {
|
.join<TMemberships>(db(TableName.Membership).as("orgMembership"), (qb) => {
|
||||||
qb.on(`${TableName.Users}.id`, "=", `orgMembership.actorUserId`)
|
qb.on(`${TableName.Users}.id`, "=", `orgMembership.actorUserId`)
|
||||||
.andOn(`orgMembership.scopeOrgId`, "=", `${TableName.Project}.orgId`)
|
.andOn(`orgMembership.scopeOrgId`, "=", `${TableName.Project}.orgId`)
|
||||||
.andOn("orgMembership.scope", AccessScope.Organization);
|
.andOn("orgMembership.scope", db.raw("?", [AccessScope.Organization]));
|
||||||
})
|
})
|
||||||
.where((qb) => {
|
.where((qb) => {
|
||||||
if (filter.usernames) {
|
if (filter.usernames) {
|
||||||
|
|||||||
@@ -3,59 +3,42 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
|
|
||||||
import { AccessScope, ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
|
import { AccessScope, ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import {
|
|
||||||
constructPermissionErrorMessage,
|
|
||||||
validatePrivilegeChangeOperation
|
|
||||||
} from "@app/ee/services/permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { ms } from "@app/lib/ms";
|
|
||||||
|
|
||||||
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
|
||||||
|
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TGroupProjectDALFactory } from "../group-project/group-project-dal";
|
import { TGroupProjectDALFactory } from "../group-project/group-project-dal";
|
||||||
|
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||||
|
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
|
||||||
import { TNotificationServiceFactory } from "../notification/notification-service";
|
import { TNotificationServiceFactory } from "../notification/notification-service";
|
||||||
import { NotificationType } from "../notification/notification-types";
|
import { NotificationType } from "../notification/notification-types";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
|
||||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||||
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
|
||||||
import { TSecretReminderRecipientsDALFactory } from "../secret-reminder-recipients/secret-reminder-recipients-dal";
|
import { TSecretReminderRecipientsDALFactory } from "../secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
||||||
import {
|
import {
|
||||||
ProjectUserMembershipTemporaryMode,
|
|
||||||
TAddUsersToWorkspaceDTO,
|
TAddUsersToWorkspaceDTO,
|
||||||
TDeleteProjectMembershipOldDTO,
|
|
||||||
TDeleteProjectMembershipsDTO,
|
TDeleteProjectMembershipsDTO,
|
||||||
TGetProjectMembershipByIdDTO,
|
|
||||||
TGetProjectMembershipByUsernameDTO,
|
TGetProjectMembershipByUsernameDTO,
|
||||||
TGetProjectMembershipDTO,
|
TGetProjectMembershipDTO,
|
||||||
TLeaveProjectDTO,
|
TLeaveProjectDTO
|
||||||
TUpdateProjectMembershipDTO
|
|
||||||
} from "./project-membership-types";
|
} from "./project-membership-types";
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "./project-user-membership-role-dal";
|
|
||||||
import { TRoleDALFactory } from "../role/role-dal";
|
|
||||||
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
|
|
||||||
import { TMembershipDALFactory } from "../membership/membership-dal";
|
|
||||||
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
|
||||||
|
|
||||||
type TProjectMembershipServiceFactoryDep = {
|
type TProjectMembershipServiceFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles"| "invalidateProjectPermissionCache">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles"| "invalidateProjectPermissionCache">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
projectMembershipDAL: TProjectMembershipDALFactory;
|
projectMembershipDAL: TProjectMembershipDALFactory;
|
||||||
membershipDAL: TMembershipDALFactory;
|
membershipUserDAL: TMembershipUserDALFactory;
|
||||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "find" | "delete">;
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "find" | "delete">;
|
||||||
userDAL: Pick<TUserDALFactory, "findById" | "findOne" | "findUserByProjectMembershipId" | "find">;
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
||||||
roleDAL: Pick<TRoleDALFactory, "find" | "findOne">;
|
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "findOrgMembersByUsername" | "findById">;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser" | "transaction" | "findProjectById">;
|
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser" | "transaction" | "findProjectById">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
@@ -70,21 +53,17 @@ export type TProjectMembershipServiceFactory = ReturnType<typeof projectMembersh
|
|||||||
export const projectMembershipServiceFactory = ({
|
export const projectMembershipServiceFactory = ({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectUserMembershipRoleDAL,
|
|
||||||
smtpService,
|
smtpService,
|
||||||
orgDAL,
|
|
||||||
userDAL,
|
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
secretReminderRecipientsDAL,
|
secretReminderRecipientsDAL,
|
||||||
licenseService,
|
|
||||||
notificationService,
|
notificationService,
|
||||||
roleDAL,
|
|
||||||
membershipRoleDAL,
|
|
||||||
additionalPrivilegeDAL,
|
additionalPrivilegeDAL,
|
||||||
membershipDAL
|
membershipUserDAL,
|
||||||
|
userDAL,
|
||||||
|
membershipRoleDAL
|
||||||
}: TProjectMembershipServiceFactoryDep) => {
|
}: TProjectMembershipServiceFactoryDep) => {
|
||||||
const getProjectMemberships = async ({
|
const getProjectMemberships = async ({
|
||||||
actorId,
|
actorId,
|
||||||
@@ -153,30 +132,6 @@ export const projectMembershipServiceFactory = ({
|
|||||||
return membership;
|
return membership;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectMembershipById = async ({
|
|
||||||
actorId,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectId,
|
|
||||||
id
|
|
||||||
}: TGetProjectMembershipByIdDTO) => {
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { id });
|
|
||||||
if (!membership) throw new NotFoundError({ message: `Project membership not found for user ${id}` });
|
|
||||||
return membership;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(simp): cross-check akhil
|
|
||||||
const addUsersToProject = async ({
|
const addUsersToProject = async ({
|
||||||
projectId,
|
projectId,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -198,7 +153,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member);
|
||||||
const orgMembers = await membershipDAL.find({
|
const orgMembers = await membershipUserDAL.find({
|
||||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: project.orgId,
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: project.orgId,
|
||||||
scope: AccessScope.Organization,
|
scope: AccessScope.Organization,
|
||||||
$in: {
|
$in: {
|
||||||
@@ -208,41 +163,48 @@ export const projectMembershipServiceFactory = ({
|
|||||||
|
|
||||||
if (orgMembers.length !== members.length) throw new BadRequestError({ message: "Some users are not part of org" });
|
if (orgMembers.length !== members.length) throw new BadRequestError({ message: "Some users are not part of org" });
|
||||||
|
|
||||||
const existingMembers = await membershipDAL.find({
|
const existingMembers = await membershipUserDAL.find({
|
||||||
[`${TableName.Membership}.scopeProjectId` as "scopeProjectId"]: projectId,
|
[`${TableName.Membership}.scopeProjectId` as "scopeProjectId"]: projectId,
|
||||||
scope: AccessScope.Project,
|
scope: AccessScope.Project,
|
||||||
$in: { actorUserId: orgMembers.map(({ actorUserId }) => actorUserId).filter(Boolean) }
|
$in: { actorUserId: orgMembers.map(({ actorUserId }) => actorUserId).filter(Boolean) }
|
||||||
});
|
});
|
||||||
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
|
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
|
||||||
|
|
||||||
|
const orgMembershipUsernames = await userDAL.find({
|
||||||
|
$in: {
|
||||||
|
id: orgMembers.filter((el) => Boolean(el.actorUserId)).map((el) => el.actorUserId as string)
|
||||||
|
}
|
||||||
|
});
|
||||||
const userIdsToExcludeForProjectKeyAddition = new Set(
|
const userIdsToExcludeForProjectKeyAddition = new Set(
|
||||||
await userGroupMembershipDAL.findUserGroupMembershipsInProject(
|
await userGroupMembershipDAL.findUserGroupMembershipsInProject(
|
||||||
orgMembers.map(({ username }) => username),
|
orgMembershipUsernames.map(({ username }) => username),
|
||||||
projectId
|
projectId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await membershipDAL.transaction(async (tx) => {
|
await membershipUserDAL.transaction(async (tx) => {
|
||||||
const projectMemberships = await projectMembershipDAL.insertMany(
|
const projectMemberships = await membershipUserDAL.insertMany(
|
||||||
orgMembers.map(({ userId }) => ({
|
orgMembers.map(({ actorUserId }) => ({
|
||||||
projectId,
|
scopeProjectId: projectId,
|
||||||
userId
|
actorUserId,
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
scopeOrgId: project.orgId
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await projectUserMembershipRoleDAL.insertMany(
|
await membershipRoleDAL.insertMany(
|
||||||
projectMemberships.map(({ id }) => ({ projectMembershipId: id, role: ProjectMembershipRole.Member })),
|
projectMemberships.map(({ id }) => ({ membershipId: id, role: ProjectMembershipRole.Member })),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId);
|
const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId);
|
||||||
await projectKeyDAL.insertMany(
|
await projectKeyDAL.insertMany(
|
||||||
orgMembers
|
orgMembers
|
||||||
.filter(({ userId }) => !userIdsToExcludeForProjectKeyAddition.has(userId))
|
.filter(({ actorUserId }) => !userIdsToExcludeForProjectKeyAddition.has(actorUserId as string))
|
||||||
.map(({ userId, id }) => ({
|
.map(({ actorUserId, id }) => ({
|
||||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
||||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
||||||
senderId: actorId,
|
senderId: actorId,
|
||||||
receiverId: userId,
|
receiverId: actorUserId as string,
|
||||||
projectId
|
projectId
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
@@ -253,8 +215,8 @@ export const projectMembershipServiceFactory = ({
|
|||||||
|
|
||||||
if (sendEmails) {
|
if (sendEmails) {
|
||||||
await notificationService.createUserNotifications(
|
await notificationService.createUserNotifications(
|
||||||
orgMembers.map((member) => ({
|
orgMembershipUsernames.map((member) => ({
|
||||||
userId: member.userId,
|
userId: member.id,
|
||||||
orgId: project.orgId,
|
orgId: project.orgId,
|
||||||
type: NotificationType.PROJECT_INVITATION,
|
type: NotificationType.PROJECT_INVITATION,
|
||||||
title: "Project Invitation",
|
title: "Project Invitation",
|
||||||
@@ -266,7 +228,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.WorkspaceInvite,
|
template: SmtpTemplates.WorkspaceInvite,
|
||||||
subjectLine: "Infisical project invitation",
|
subjectLine: "Infisical project invitation",
|
||||||
recipients: orgMembers.filter((i) => i.email).map((i) => i.email as string),
|
recipients: orgMembershipUsernames.filter((i) => i.email).map((i) => i.email as string),
|
||||||
substitutions: {
|
substitutions: {
|
||||||
workspaceName: project.name,
|
workspaceName: project.name,
|
||||||
callback_url: `${appCfg.SITE_URL}/login`
|
callback_url: `${appCfg.SITE_URL}/login`
|
||||||
@@ -276,164 +238,6 @@ export const projectMembershipServiceFactory = ({
|
|||||||
return orgMembers;
|
return orgMembers;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProjectMembership = async ({
|
|
||||||
actorId,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectId,
|
|
||||||
membershipId,
|
|
||||||
roles
|
|
||||||
}: TUpdateProjectMembershipDTO) => {
|
|
||||||
const { permission, memberships } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
const membershipUser = await userDAL.findUserByProjectMembershipId(membershipId);
|
|
||||||
if (membershipUser?.isGhost || membershipUser?.projectId !== projectId) {
|
|
||||||
throw new ForbiddenRequestError({ message: "Forbidden member update" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const providedRolePermissionDetails = await permissionService.getProjectPermissionByRoles(
|
|
||||||
roles.map((el) => el.role).filter((el) => el !== ProjectMembershipRole.NoAccess),
|
|
||||||
projectId
|
|
||||||
);
|
|
||||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
|
||||||
for await (const { permission: rolePermission } of providedRolePermissionDetails) {
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member,
|
|
||||||
permission,
|
|
||||||
rolePermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to assign to role",
|
|
||||||
shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate custom roles input
|
|
||||||
const customInputRoles = roles.filter(
|
|
||||||
({ role }) =>
|
|
||||||
!Object.values(ProjectMembershipRole)
|
|
||||||
// we don't want to include custom in this check;
|
|
||||||
// this unintentionally enables setting slug to custom which is reserved
|
|
||||||
.filter((r) => r !== ProjectMembershipRole.Custom)
|
|
||||||
.includes(role as ProjectMembershipRole.Member)
|
|
||||||
);
|
|
||||||
const hasCustomRole = Boolean(customInputRoles.length);
|
|
||||||
if (hasCustomRole) {
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
|
||||||
if (!plan?.rbac)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const customRoles = hasCustomRole
|
|
||||||
? await roleDAL.find({
|
|
||||||
projectId,
|
|
||||||
$in: { slug: customInputRoles.map(({ role }) => role) }
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
if (customRoles.length !== customInputRoles.length) {
|
|
||||||
throw new NotFoundError({ message: "One or more custom roles not found" });
|
|
||||||
}
|
|
||||||
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
|
||||||
|
|
||||||
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
|
|
||||||
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
|
|
||||||
if (!inputRole.isTemporary) {
|
|
||||||
return {
|
|
||||||
projectMembershipId: membershipId,
|
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
|
||||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// check cron or relative here later for now its just relative
|
|
||||||
const relativeTimeInMs = ms(inputRole.temporaryRange);
|
|
||||||
return {
|
|
||||||
projectMembershipId: membershipId,
|
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
|
||||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null,
|
|
||||||
isTemporary: true,
|
|
||||||
temporaryMode: ProjectUserMembershipTemporaryMode.Relative,
|
|
||||||
temporaryRange: inputRole.temporaryRange,
|
|
||||||
temporaryAccessStartTime: new Date(inputRole.temporaryAccessStartTime),
|
|
||||||
temporaryAccessEndTime: new Date(new Date(inputRole.temporaryAccessStartTime).getTime() + relativeTimeInMs)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedRoles = await membershipDAL.transaction(async (tx) => {
|
|
||||||
await projectUserMembershipRoleDAL.delete({ projectMembershipId: membershipId }, tx);
|
|
||||||
return projectUserMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
|
||||||
|
|
||||||
return updatedRoles;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is old and should be removed later. Its not used anywhere, but it is exposed in our API. So to avoid breaking changes, we are keeping it for now.
|
|
||||||
const deleteProjectMembership = async ({
|
|
||||||
actorId,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectId,
|
|
||||||
membershipId
|
|
||||||
}: TDeleteProjectMembershipOldDTO) => {
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
const member = await userDAL.findUserByProjectMembershipId(membershipId);
|
|
||||||
|
|
||||||
if (member?.isGhost) {
|
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
message: "Forbidden membership deletion",
|
|
||||||
name: "DeleteProjectMembership"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const membership = await projectMembershipDAL.transaction(async (tx) => {
|
|
||||||
const [deletedMembership] = await projectMembershipDAL.delete({ projectId, id: membershipId }, tx);
|
|
||||||
await projectKeyDAL.delete({ receiverId: deletedMembership.userId, projectId }, tx);
|
|
||||||
await secretReminderRecipientsDAL.delete(
|
|
||||||
{
|
|
||||||
projectId,
|
|
||||||
userId: deletedMembership.userId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
return deletedMembership;
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
|
||||||
|
|
||||||
return membership;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteProjectMemberships = async ({
|
const deleteProjectMemberships = async ({
|
||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
@@ -485,20 +289,21 @@ export const projectMembershipServiceFactory = ({
|
|||||||
await userGroupMembershipDAL.findUserGroupMembershipsInProject(usernamesAndEmails, projectId)
|
await userGroupMembershipDAL.findUserGroupMembershipsInProject(usernamesAndEmails, projectId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const memberships = await projectMembershipDAL.transaction(async (tx) => {
|
const memberships = await membershipUserDAL.transaction(async (tx) => {
|
||||||
await projectUserAdditionalPrivilegeDAL.delete(
|
await additionalPrivilegeDAL.delete(
|
||||||
{
|
{
|
||||||
projectId,
|
projectId,
|
||||||
$in: {
|
$in: {
|
||||||
userId: projectMembers.map((membership) => membership.user.id)
|
actorUserId: projectMembers.map((membership) => membership.user.id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
const deletedMemberships = await projectMembershipDAL.delete(
|
const deletedMemberships = await membershipUserDAL.delete(
|
||||||
{
|
{
|
||||||
projectId,
|
scopeProjectId: projectId,
|
||||||
|
scope: AccessScope.Project,
|
||||||
$in: {
|
$in: {
|
||||||
id: projectMembers.map(({ id }) => id)
|
id: projectMembers.map(({ id }) => id)
|
||||||
}
|
}
|
||||||
@@ -571,11 +376,11 @@ export const projectMembershipServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedMembership = await projectMembershipDAL.transaction(async (tx) => {
|
const deletedMembership = await membershipUserDAL.transaction(async (tx) => {
|
||||||
await projectUserAdditionalPrivilegeDAL.delete(
|
await additionalPrivilegeDAL.delete(
|
||||||
{
|
{
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
userId: actorId
|
actorUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -591,10 +396,11 @@ export const projectMembershipServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const membership = (
|
const membership = (
|
||||||
await projectMembershipDAL.delete(
|
await membershipUserDAL.delete(
|
||||||
{
|
{
|
||||||
projectId: project.id,
|
scope: AccessScope.Project,
|
||||||
userId: actorId
|
scopeProjectId: project.id,
|
||||||
|
actorUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
)
|
)
|
||||||
@@ -612,11 +418,8 @@ export const projectMembershipServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
getProjectMemberships,
|
getProjectMemberships,
|
||||||
getProjectMembershipByUsername,
|
getProjectMembershipByUsername,
|
||||||
updateProjectMembership,
|
|
||||||
deleteProjectMemberships,
|
deleteProjectMemberships,
|
||||||
deleteProjectMembership, // TODO: Remove this
|
|
||||||
addUsersToProject,
|
addUsersToProject,
|
||||||
leaveProject,
|
leaveProject
|
||||||
getProjectMembershipById
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,285 +0,0 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
|
||||||
import { requestContext } from "@fastify/request-context";
|
|
||||||
|
|
||||||
import { ActionProjectType, ProjectMembershipRole, ProjectType, TableName, TProjects } from "@app/db/schemas";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
|
||||||
import {
|
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSet,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|
||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
|
||||||
|
|
||||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
|
||||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
|
||||||
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
|
||||||
import { TProjectRoleDALFactory } from "./project-role-dal";
|
|
||||||
import { getPredefinedRoles } from "./project-role-fns";
|
|
||||||
import {
|
|
||||||
ProjectRoleServiceIdentifierType,
|
|
||||||
TCreateRoleDTO,
|
|
||||||
TDeleteRoleDTO,
|
|
||||||
TGetRoleDetailsDTO,
|
|
||||||
TListRolesDTO,
|
|
||||||
TUpdateRoleDTO
|
|
||||||
} from "./project-role-types";
|
|
||||||
|
|
||||||
type TProjectRoleServiceFactoryDep = {
|
|
||||||
projectRoleDAL: TProjectRoleDALFactory;
|
|
||||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
|
||||||
userDAL: Pick<TUserDALFactory, "findById">;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findProjectById">;
|
|
||||||
permissionService: Pick<
|
|
||||||
TPermissionServiceFactory,
|
|
||||||
"getProjectPermission" | "getUserProjectPermission" | "invalidateProjectPermissionCache"
|
|
||||||
>;
|
|
||||||
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
|
|
||||||
projectUserMembershipRoleDAL: TProjectUserMembershipRoleDALFactory;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TProjectRoleServiceFactory = ReturnType<typeof projectRoleServiceFactory>;
|
|
||||||
|
|
||||||
const unpackPermissions = (permissions: unknown) =>
|
|
||||||
UnpackedPermissionSchema.array().parse(
|
|
||||||
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
|
||||||
);
|
|
||||||
|
|
||||||
export const projectRoleServiceFactory = ({
|
|
||||||
projectRoleDAL,
|
|
||||||
permissionService,
|
|
||||||
identityProjectMembershipRoleDAL,
|
|
||||||
projectUserMembershipRoleDAL,
|
|
||||||
projectDAL,
|
|
||||||
identityDAL,
|
|
||||||
userDAL
|
|
||||||
}: TProjectRoleServiceFactoryDep) => {
|
|
||||||
const createRole = async ({ data, actor, actorId, actorAuthMethod, actorOrgId, filter }: TCreateRoleDTO) => {
|
|
||||||
let projectId = "";
|
|
||||||
if (filter.type === ProjectRoleServiceIdentifierType.SLUG) {
|
|
||||||
const project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
|
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
|
||||||
projectId = project.id;
|
|
||||||
} else {
|
|
||||||
projectId = filter.projectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
|
||||||
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
|
|
||||||
if (existingRole) {
|
|
||||||
throw new BadRequestError({ name: "Create Role", message: "Project role with same slug already exists" });
|
|
||||||
}
|
|
||||||
|
|
||||||
validateHandlebarTemplate("Project Role Create", JSON.stringify(data.permissions || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
const role = await projectRoleDAL.create({
|
|
||||||
...data,
|
|
||||||
projectId
|
|
||||||
});
|
|
||||||
return { ...role, permissions: unpackPermissions(role.permissions) };
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRoleBySlug = async ({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
roleSlug,
|
|
||||||
filter
|
|
||||||
}: TGetRoleDetailsDTO) => {
|
|
||||||
let project: TProjects;
|
|
||||||
if (filter.type === ProjectRoleServiceIdentifierType.SLUG) {
|
|
||||||
project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
|
|
||||||
} else {
|
|
||||||
project = await projectDAL.findProjectById(filter.projectId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: project.id,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
|
||||||
if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) {
|
|
||||||
const [predefinedRole] = getPredefinedRoles({
|
|
||||||
projectId: project.id,
|
|
||||||
projectType: project.type as ProjectType,
|
|
||||||
roleFilter: roleSlug as ProjectMembershipRole
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!predefinedRole) throw new NotFoundError({ message: `Default role with slug '${roleSlug}' not found` });
|
|
||||||
|
|
||||||
return { ...predefinedRole, permissions: UnpackedPermissionSchema.array().parse(predefinedRole.permissions) };
|
|
||||||
}
|
|
||||||
|
|
||||||
const customRole = await projectRoleDAL.findOne({ slug: roleSlug, projectId: project.id });
|
|
||||||
if (!customRole) throw new NotFoundError({ message: `Project role with slug '${roleSlug}' not found` });
|
|
||||||
return { ...customRole, permissions: unpackPermissions(customRole.permissions) };
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateRole = async ({ roleId, actorOrgId, actorAuthMethod, actorId, actor, data }: TUpdateRoleDTO) => {
|
|
||||||
const projectRole = await projectRoleDAL.findById(roleId);
|
|
||||||
if (!projectRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: projectRole.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
|
||||||
|
|
||||||
if (data?.slug) {
|
|
||||||
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId: projectRole.projectId });
|
|
||||||
if (existingRole && existingRole.id !== roleId)
|
|
||||||
throw new BadRequestError({ name: "Update Role", message: "Project role with the same slug already exists" });
|
|
||||||
}
|
|
||||||
validateHandlebarTemplate("Project Role Update", JSON.stringify(data.permissions || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedRole = await projectRoleDAL.updateById(projectRole.id, {
|
|
||||||
...data,
|
|
||||||
permissions: data.permissions ? data.permissions : undefined
|
|
||||||
});
|
|
||||||
if (!updatedRole) throw new NotFoundError({ message: "Project role not found", name: "Update role" });
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectRole.projectId);
|
|
||||||
|
|
||||||
return { ...updatedRole, permissions: unpackPermissions(updatedRole.permissions) };
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteRole = async ({ actor, actorId, actorAuthMethod, actorOrgId, roleId }: TDeleteRoleDTO) => {
|
|
||||||
const projectRole = await projectRoleDAL.findById(roleId);
|
|
||||||
if (!projectRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: projectRole.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
|
|
||||||
|
|
||||||
const identityRole = await identityProjectMembershipRoleDAL.findOne({ customRoleId: roleId });
|
|
||||||
const projectUserRole = await projectUserMembershipRoleDAL.findOne({ customRoleId: roleId });
|
|
||||||
|
|
||||||
if (identityRole) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "The role is assigned to one or more identities. Make sure to unassign them before deleting the role.",
|
|
||||||
name: "Delete role"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (projectUserRole) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "The role is assigned to one or more users. Make sure to unassign them before deleting the role.",
|
|
||||||
name: "Delete role"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedRole = await projectRoleDAL.deleteById(roleId);
|
|
||||||
if (!deletedRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectRole.projectId);
|
|
||||||
|
|
||||||
return { ...deletedRole, permissions: unpackPermissions(deletedRole.permissions) };
|
|
||||||
};
|
|
||||||
|
|
||||||
const listRoles = async ({ actorOrgId, actorAuthMethod, actorId, actor, filter }: TListRolesDTO) => {
|
|
||||||
let project: TProjects;
|
|
||||||
if (filter.type === ProjectRoleServiceIdentifierType.SLUG) {
|
|
||||||
project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
|
|
||||||
} else {
|
|
||||||
project = await projectDAL.findProjectById(filter.projectId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: project.id,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
|
||||||
const customRoles = await projectRoleDAL.find(
|
|
||||||
{ projectId: project.id },
|
|
||||||
{ sort: [[`${TableName.ProjectRoles}.slug` as "slug", "asc"]] }
|
|
||||||
);
|
|
||||||
const roles = [
|
|
||||||
...getPredefinedRoles({ projectId: project.id, projectType: project.type as ProjectType }),
|
|
||||||
...(customRoles || [])
|
|
||||||
];
|
|
||||||
|
|
||||||
return roles;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUserPermission = async (
|
|
||||||
userId: string,
|
|
||||||
projectId: string,
|
|
||||||
actorAuthMethod: ActorAuthMethod,
|
|
||||||
actorOrgId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { permission, memberships } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.USER,
|
|
||||||
actorId: userId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
// just to satisfy ts
|
|
||||||
|
|
||||||
const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails");
|
|
||||||
const isAssumingPrivilege = assumedPrivilegeDetailsCtx?.projectId === projectId;
|
|
||||||
const assumedPrivilegeDetails = isAssumingPrivilege
|
|
||||||
? {
|
|
||||||
actorId: assumedPrivilegeDetailsCtx?.actorId,
|
|
||||||
actorType: assumedPrivilegeDetailsCtx?.actorType,
|
|
||||||
actorName: "",
|
|
||||||
actorEmail: ""
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (assumedPrivilegeDetails?.actorType === ActorType.IDENTITY) {
|
|
||||||
const identityDetails = await identityDAL.findById(assumedPrivilegeDetails.actorId);
|
|
||||||
if (!identityDetails)
|
|
||||||
throw new NotFoundError({ message: `Identity with ID ${assumedPrivilegeDetails.actorId} not found` });
|
|
||||||
assumedPrivilegeDetails.actorName = identityDetails.name;
|
|
||||||
} else if (assumedPrivilegeDetails?.actorType === ActorType.USER) {
|
|
||||||
const userDetails = await userDAL.findById(assumedPrivilegeDetails?.actorId);
|
|
||||||
if (!userDetails)
|
|
||||||
throw new NotFoundError({ message: `User with ID ${assumedPrivilegeDetails.actorId} not found` });
|
|
||||||
assumedPrivilegeDetails.actorName = `${userDetails?.firstName} ${userDetails?.lastName || ""}`;
|
|
||||||
assumedPrivilegeDetails.actorEmail = userDetails?.email || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return { permissions: packRules(permission.rules), memberships, assumedPrivilegeDetails };
|
|
||||||
};
|
|
||||||
|
|
||||||
return { createRole, updateRole, deleteRole, listRoles, getUserPermission, getRoleBySlug };
|
|
||||||
};
|
|
||||||
@@ -167,6 +167,13 @@ export const roleServiceFactory = ({
|
|||||||
await factory.onGetRoleByIdGuard(dto);
|
await factory.onGetRoleByIdGuard(dto);
|
||||||
|
|
||||||
const scope = factory.getScopeField(scopeData);
|
const scope = factory.getScopeField(scopeData);
|
||||||
|
|
||||||
|
const predefinedRole = await factory.getPredefinedRoles(scopeData);
|
||||||
|
const selectedRole = predefinedRole.find((el) => el.id === dto.selector.id);
|
||||||
|
if (selectedRole) {
|
||||||
|
return { ...selectedRole, permissions: UnpackedPermissionSchema.array().parse(selectedRole.permissions) };
|
||||||
|
}
|
||||||
|
|
||||||
const role = await roleDAL.findOne({
|
const role = await roleDAL.findOne({
|
||||||
id: selector.id,
|
id: selector.id,
|
||||||
[scope.key]: scope.value
|
[scope.key]: scope.value
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ type TSuperAdminServiceFactoryDep = {
|
|||||||
authService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
authService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "encryptWithRootKey" | "decryptWithRootKey" | "updateEncryptionStrategy">;
|
kmsService: Pick<TKmsServiceFactory, "encryptWithRootKey" | "decryptWithRootKey" | "updateEncryptionStrategy">;
|
||||||
kmsRootConfigDAL: TKmsRootConfigDALFactory;
|
kmsRootConfigDAL: TKmsRootConfigDALFactory;
|
||||||
orgService: Pick<TOrgServiceFactory, "createOrganization" | "inviteUserToOrganization">;
|
orgService: Pick<TOrgServiceFactory, "createOrganization">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem" | "deleteItems">;
|
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem" | "deleteItems">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "updateSubscriptionOrgMemberCount">;
|
||||||
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "initializeTeamsBot">;
|
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "initializeTeamsBot">;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const useGetIdentityAuthTemplates = (dto: GetIdentityAuthTemplatesDTO) =>
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: Boolean(dto.organizationId)
|
enabled: Boolean(dto.organizationId && !dto.isDisabled)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export interface GetIdentityAuthTemplatesDTO {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
search?: string;
|
search?: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MachineAuthTemplateUsage {
|
export interface MachineAuthTemplateUsage {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
@@ -71,14 +70,10 @@ export const useCreateOrgRole = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<TOrgRole, object, TCreateOrgRoleDTO>({
|
return useMutation<TOrgRole, object, TCreateOrgRoleDTO>({
|
||||||
mutationFn: async ({ orgId, permissions, ...dto }: TCreateOrgRoleDTO) => {
|
mutationFn: async ({ orgId, ...dto }: TCreateOrgRoleDTO) => {
|
||||||
const {
|
const {
|
||||||
data: { role }
|
data: { role }
|
||||||
} = await apiRequest.post(`/api/v1/organization/${orgId}/roles`, {
|
} = await apiRequest.post(`/api/v1/organization/${orgId}/roles`, dto);
|
||||||
...dto,
|
|
||||||
// TODO(simp): removing packing
|
|
||||||
permissions: permissions.length ? packRules(permissions) : []
|
|
||||||
});
|
|
||||||
|
|
||||||
return role;
|
return role;
|
||||||
},
|
},
|
||||||
@@ -92,13 +87,10 @@ export const useUpdateOrgRole = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<TOrgRole, object, TUpdateOrgRoleDTO>({
|
return useMutation<TOrgRole, object, TUpdateOrgRoleDTO>({
|
||||||
mutationFn: async ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) => {
|
mutationFn: async ({ id, orgId, ...dto }: TUpdateOrgRoleDTO) => {
|
||||||
const {
|
const {
|
||||||
data: { role }
|
data: { role }
|
||||||
} = await apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, {
|
} = await apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, dto);
|
||||||
...dto,
|
|
||||||
permissions: permissions ? packRules(permissions) : undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
return role;
|
return role;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -80,10 +80,7 @@ const getOrgRoles = async (orgId: string) => {
|
|||||||
const { data } = await apiRequest.get<{
|
const { data } = await apiRequest.get<{
|
||||||
data: { roles: Array<Omit<TOrgRole, "permissions"> & { permissions: unknown }> };
|
data: { roles: Array<Omit<TOrgRole, "permissions"> & { permissions: unknown }> };
|
||||||
}>(`/api/v1/organization/${orgId}/roles`);
|
}>(`/api/v1/organization/${orgId}/roles`);
|
||||||
return data.data.roles.map(({ permissions, ...el }) => ({
|
return data.data.roles;
|
||||||
...el,
|
|
||||||
permissions: unpackRules(permissions as PackRule<TPermission>[])
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetOrgRoles = (orgId: string, enable = true) =>
|
export const useGetOrgRoles = (orgId: string, enable = true) =>
|
||||||
@@ -102,7 +99,7 @@ export const useGetOrgRole = (orgId: string, roleId: string) =>
|
|||||||
}>(`/api/v1/organization/${orgId}/roles/${roleId}`);
|
}>(`/api/v1/organization/${orgId}/roles/${roleId}`);
|
||||||
return {
|
return {
|
||||||
...data.role,
|
...data.role,
|
||||||
permissions: unpackRules(data.role.permissions as PackRule<TPermission>[])
|
permissions: data.role.permissions as PackRule<TPermission>[]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
enabled: Boolean(orgId && roleId)
|
enabled: Boolean(orgId && roleId)
|
||||||
|
|||||||
+12
-3
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
|
faBan,
|
||||||
faEdit,
|
faEdit,
|
||||||
faEllipsisV,
|
faEllipsisV,
|
||||||
faEye,
|
faEye,
|
||||||
@@ -30,7 +31,7 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { OrgPermissionSubjects, useOrganization } from "@app/context";
|
import { OrgPermissionSubjects, useOrganization, useSubscription } from "@app/context";
|
||||||
import { OrgPermissionMachineIdentityAuthTemplateActions } from "@app/context/OrgPermissionContext/types";
|
import { OrgPermissionMachineIdentityAuthTemplateActions } from "@app/context/OrgPermissionContext/types";
|
||||||
import {
|
import {
|
||||||
getUserTablePreference,
|
getUserTablePreference,
|
||||||
@@ -86,12 +87,14 @@ export const IdentityAuthTemplatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const organizationId = currentOrg?.id || "";
|
const organizationId = currentOrg?.id || "";
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const { data, isPending, isFetching } = useGetIdentityAuthTemplates({
|
const { data, isPending, isFetching } = useGetIdentityAuthTemplates({
|
||||||
organizationId,
|
organizationId,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
search: debouncedSearch
|
search: debouncedSearch,
|
||||||
|
isDisabled: !subscription.machineIdentityAuthTemplates
|
||||||
});
|
});
|
||||||
|
|
||||||
const { templates = [], totalCount = 0 } = data ?? {};
|
const { templates = [], totalCount = 0 } = data ?? {};
|
||||||
@@ -172,7 +175,10 @@ export const IdentityAuthTemplatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{isPending && <TableSkeleton columns={4} innerKey="identity-auth-templates" />}
|
{subscription.machineIdentityAuthTemplates && isPending && (
|
||||||
|
<TableSkeleton columns={4} innerKey="identity-auth-templates" />
|
||||||
|
)}
|
||||||
|
|
||||||
{!isPending &&
|
{!isPending &&
|
||||||
templates?.map((template) => (
|
templates?.map((template) => (
|
||||||
<Tr
|
<Tr
|
||||||
@@ -265,6 +271,9 @@ export const IdentityAuthTemplatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
onChangePerPage={handlePerPageChange}
|
onChangePerPage={handlePerPageChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!subscription.machineIdentityAuthTemplates && (
|
||||||
|
<EmptyState title="This feature is not yet activated for your license." icon={faBan} />
|
||||||
|
)}
|
||||||
{!isPending && templates.length === 0 && (
|
{!isPending && templates.length === 0 && (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
title={
|
title={
|
||||||
|
|||||||
Reference in New Issue
Block a user