feat: fixing ts

This commit is contained in:
=
2025-10-10 00:09:45 +05:30
parent 0ddf98ec15
commit 5a8bad3556
38 changed files with 716 additions and 2437 deletions
@@ -1,12 +1,12 @@
import { Knex } from "knex";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { AccessScope, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type";
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
import { TUserDALFactory } from "../user/user-dal";
import { TTokenDALFactory } from "./auth-token-dal";
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
@@ -14,7 +14,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOne">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "findOne">;
};
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
@@ -80,7 +80,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
}
};
export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAuthTokenServiceFactoryDep) => {
export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId, aliasId, payload }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
@@ -208,9 +208,10 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
if (token.organizationId) {
const orgMembership = await orgMembershipDAL.findOne({
userId: user.id,
orgId: token.organizationId
const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id,
scopeOrgId: token.organizationId,
scope: AccessScope.Organization
});
if (!orgMembership) {
+45 -21
View File
@@ -1,6 +1,13 @@
import { Knex } from "knex";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TUsers, UserDeviceSchema } from "@app/db/schemas";
import {
AccessScope,
OrgMembershipRole,
OrgMembershipStatus,
TableName,
TUsers,
UserDeviceSchema
} from "@app/db/schemas";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
import { getConfig } from "@app/lib/config/env";
@@ -14,11 +21,12 @@ import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
import { TNotificationServiceFactory } from "../notification/notification-service";
import { NotificationType } from "../notification/notification-types";
import { TOrgDALFactory } from "../org/org-dal";
import { getDefaultOrgMembershipRole } from "../org/org-role-fns";
import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { LoginMethod } from "../super-admin/super-admin-types";
import { TTotpServiceFactory } from "../totp/totp-service";
@@ -48,7 +56,8 @@ type TAuthLoginServiceFactoryDep = {
smtpService: TSmtpService;
totpService: Pick<TTotpServiceFactory, "verifyUserTotp" | "verifyWithUserRecoveryCode">;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
orgMembershipDAL: TOrgMembershipDALFactory;
membershipUserDAL: TMembershipUserDALFactory;
membershipRoleDAL: TMembershipRoleDALFactory;
notificationService: Pick<TNotificationServiceFactory, "createUserNotifications">;
};
@@ -58,10 +67,11 @@ export const authLoginServiceFactory = ({
tokenService,
smtpService,
orgDAL,
orgMembershipDAL,
totpService,
auditLogService,
notificationService
notificationService,
membershipUserDAL,
membershipRoleDAL
}: TAuthLoginServiceFactoryDep) => {
/*
* Private
@@ -163,8 +173,8 @@ export const authLoginServiceFactory = ({
if (organizationId) {
const org = await orgDAL.findById(organizationId);
if (org) {
await orgMembershipDAL.update(
{ userId: user.id, orgId: org.id },
await membershipUserDAL.update(
{ actorUserId: user.id, scopeOrgId: org.id, scope: AccessScope.Organization },
{ lastLoginAuthMethod: authMethod, lastLoginTime: new Date() }
);
if (org.userTokenExpiration) {
@@ -858,21 +868,34 @@ export const authLoginServiceFactory = ({
}
orgId = defaultOrg.id;
const [orgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
});
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(defaultOrg.defaultMembershipRole);
await orgMembershipDAL.create({
userId: user.id,
inviteEmail: email,
orgId,
role,
roleId,
status: OrgMembershipStatus.Accepted,
isActive: true
await membershipUserDAL.transaction(async (tx) => {
const membership = await membershipUserDAL.create(
{
actorUserId: user?.id,
inviteEmail: email,
scopeOrgId: orgId,
scope: AccessScope.Organization,
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
});
}
}
@@ -895,10 +918,11 @@ export const authLoginServiceFactory = ({
if (org) {
// checks for the membership and only sets the orgId / orgName if the user is a member of the specified org
const orgMembership = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
[`${TableName.OrgMembership}.orgId` as "orgId"]: org.id,
[`${TableName.OrgMembership}.isActive` as "isActive"]: true,
[`${TableName.OrgMembership}.status` as "status"]: OrgMembershipStatus.Accepted
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: org.id,
[`${TableName.Membership}.isActive` as "isActive"]: true,
[`${TableName.Membership}.status` as "status"]: OrgMembershipStatus.Accepted,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
});
if (orgMembership) {
@@ -1,4 +1,4 @@
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
import { AccessScope, OrgMembershipStatus, TableName } from "@app/db/schemas";
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -8,17 +8,15 @@ import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { getMinExpiresIn } from "@app/lib/fn";
import { isDisposableEmail } from "@app/lib/validator";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TMembershipGroupDALFactory } from "../membership-group/membership-group-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TOrgServiceFactory } from "../org/org-service";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { getServerCfg } from "../super-admin/super-admin-service";
import { TUserDALFactory } from "../user/user-dal";
@@ -42,14 +40,12 @@ type TAuthSignupDep = {
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findProjectById" | "findById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
orgService: Pick<TOrgServiceFactory, "createOrganization" | "findOrganizationById">;
orgDAL: TOrgDALFactory;
tokenService: TAuthTokenServiceFactory;
smtpService: TSmtpService;
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "transaction" | "insertMany">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
membershipGroupDAL: TMembershipGroupDALFactory;
};
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
@@ -60,11 +56,11 @@ export const authSignupServiceFactory = ({
projectKeyDAL,
projectDAL,
projectBotDAL,
groupProjectDAL,
tokenService,
smtpService,
orgService,
orgDAL,
membershipGroupDAL,
licenseService
}: TAuthSignupDep) => {
// first step of signup. create user and send email
@@ -200,9 +196,10 @@ export const authSignupServiceFactory = ({
organizationId
) {
const [pendingOrgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
status: OrgMembershipStatus.Invited,
[`${TableName.OrgMembership}.orgId` as "orgId"]: organizationId
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: organizationId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
});
if (pendingOrgMembership) {
@@ -241,18 +238,18 @@ export const authSignupServiceFactory = ({
}
const updatedMembersips = await orgDAL.updateMembership(
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
{ userId: user.id, status: OrgMembershipStatus.Accepted }
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited, scope: AccessScope.Organization },
{ actorUserId: user.id, status: OrgMembershipStatus.Accepted }
);
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
const uniqueOrgId = [...new Set(updatedMembersips.map(({ scopeOrgId }) => scopeOrgId))];
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId)));
await convertPendingGroupAdditionsToGroupMemberships({
userIds: [user.id],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL,
membershipGroupDAL,
projectDAL,
projectBotDAL
});
@@ -351,21 +348,21 @@ export const authSignupServiceFactory = ({
);
const updatedMembersips = await orgDAL.updateMembership(
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
{ userId: us.id, status: OrgMembershipStatus.Accepted },
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited, scope: AccessScope.Organization },
{ actorUserId: us.id, status: OrgMembershipStatus.Accepted },
tx
);
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
const uniqueOrgId = [...new Set(updatedMembersips.map(({ scopeOrgId }) => scopeOrgId))];
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId, tx)));
await convertPendingGroupAdditionsToGroupMemberships({
userIds: [user.id],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
membershipGroupDAL,
tx
});
@@ -1,19 +1,19 @@
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
import { OrgMembershipRole, TRoles } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
import { TRoleDALFactory } from "../role/role-dal";
import { TExternalGroupOrgMembershipRoleMappingDTO } from "./external-group-org-role-mapping-types";
export const constructGroupOrgMembershipRoleMappings = async ({
mappingsDTO,
orgId,
orgRoleDAL,
roleDAL,
licenseService
}: {
mappingsDTO: TExternalGroupOrgMembershipRoleMappingDTO[];
orgRoleDAL: TOrgRoleDALFactory;
roleDAL: TRoleDALFactory;
licenseService: TLicenseServiceFactory;
orgId: string;
}) => {
@@ -30,9 +30,9 @@ export const constructGroupOrgMembershipRoleMappings = async ({
.filter((mapping) => isCustomOrgRole(mapping.roleSlug))
.map((mapping) => mapping.roleSlug);
let customRolesMap: Map<string, TOrgRoles> = new Map();
let customRolesMap: Map<string, TRoles> = new Map();
if (customRoleSlugs.length > 0) {
const customRoles = await orgRoleDAL.find({
const customRoles = await roleDAL.find({
orgId,
$in: {
slug: customRoleSlugs
@@ -6,15 +6,15 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { OrgServiceActor } from "@app/lib/types";
import { constructGroupOrgMembershipRoleMappings } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-fns";
import { TSyncExternalGroupOrgMembershipRoleMappingsDTO } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-types";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { TRoleDALFactory } from "../role/role-dal";
import { TExternalGroupOrgRoleMappingDALFactory } from "./external-group-org-role-mapping-dal";
type TExternalGroupOrgRoleMappingServiceFactoryDep = {
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
permissionService: TPermissionServiceFactory;
licenseService: TLicenseServiceFactory;
orgRoleDAL: TOrgRoleDALFactory;
roleDAL: TRoleDALFactory;
};
export type TExternalGroupOrgRoleMappingServiceFactory = ReturnType<typeof externalGroupOrgRoleMappingServiceFactory>;
@@ -23,7 +23,7 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
externalGroupOrgRoleMappingDAL,
licenseService,
permissionService,
orgRoleDAL
roleDAL
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
const listExternalGroupOrgRoleMappings = async (actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission(
@@ -61,7 +61,7 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
const mappings = await constructGroupOrgMembershipRoleMappings({
mappingsDTO: dto.mappings,
orgRoleDAL,
roleDAL,
licenseService,
orgId: actor.orgId
});
@@ -1,504 +1,27 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, SecretKeyEncoding, TGroups } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionGroupActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
import { NotFoundError } from "@app/lib/errors";
import { TGroupDALFactory } from "../../ee/services/group/group-dal";
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
import { TGroupProjectDALFactory } from "./group-project-dal";
import { TGroupProjectMembershipRoleDALFactory } from "./group-project-membership-role-dal";
import {
TCreateProjectGroupDTO,
TDeleteProjectGroupDTO,
TGetGroupInProjectDTO,
TListProjectGroupDTO,
TUpdateProjectGroupDTO
} from "./group-project-types";
type TGroupProjectServiceFactoryDep = {
groupProjectDAL: Pick<TGroupProjectDALFactory, "findOne" | "transaction" | "create" | "delete" | "findByProjectId">;
groupProjectMembershipRoleDAL: Pick<
TGroupProjectMembershipRoleDALFactory,
"create" | "transaction" | "insertMany" | "delete"
>;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findGroupMembersNotInProject" | "find">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "findProjectGhostUser" | "findById">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany" | "transaction">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
projectBotDAL: TProjectBotDALFactory;
groupDAL: Pick<TGroupDALFactory, "findOne" | "findAllGroupPossibleMembers">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles" | "invalidateProjectPermissionCache">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "findProjectGhostUser" | "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
};
export type TGroupProjectServiceFactory = ReturnType<typeof groupProjectServiceFactory>;
export const groupProjectServiceFactory = ({
groupDAL,
groupProjectDAL,
groupProjectMembershipRoleDAL,
userGroupMembershipDAL,
projectDAL,
projectKeyDAL,
projectBotDAL,
projectRoleDAL,
permissionService
}: TGroupProjectServiceFactoryDep) => {
const addGroupToProject = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
roles,
projectId,
groupIdOrName
}: TCreateProjectGroupDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
if (project.version < 2) throw new BadRequestError({ message: `Failed to add group to E2EE project` });
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Create, ProjectPermissionSub.Groups);
let group: TGroups | null = null;
if (isUuidV4(groupIdOrName)) {
group = await groupDAL.findOne({ orgId: actorOrgId, id: groupIdOrName });
}
if (!group) {
group = await groupDAL.findOne({ orgId: actorOrgId, name: groupIdOrName });
}
if (!group) throw new NotFoundError({ message: `Failed to find group with ID or name ${groupIdOrName}` });
const existingGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (existingGroup)
throw new BadRequestError({
message: `Group with ID ${group.id} already exists in project with id ${project.id}`
});
for await (const { role: requestedRoleChange } of roles) {
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
requestedRoleChange,
project.id
);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to assign group to role",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
// validate custom roles input
const customInputRoles = roles.filter(
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
);
const hasCustomRole = Boolean(customInputRoles.length);
const customRoles = hasCustomRole
? await projectRoleDAL.find({
projectId: project.id,
$in: { slug: customInputRoles.map(({ role }) => role) }
})
: [];
if (customRoles.length !== customInputRoles.length) {
const customRoleSlugs = customRoles.map((customRole) => customRole.slug);
const missingInputRoles = customInputRoles
.filter((inputRole) => !customRoleSlugs.includes(inputRole.role))
.map((role) => role.role);
throw new NotFoundError({
message: `Custom role/s not found: ${missingInputRoles.join(", ")}`
});
}
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
const projectGroup = await groupProjectDAL.transaction(async (tx) => {
const groupProjectMembership = await groupProjectDAL.create(
{
groupId: group!.id,
projectId: project.id
},
tx
);
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
if (!inputRole.isTemporary) {
return {
projectMembershipId: groupProjectMembership.id,
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: groupProjectMembership.id,
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)
};
});
await groupProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
// share project key with users in group that have not
// individually been added to the project and that are not part of
// other groups that are in the project
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group!.id, project.id, tx);
if (groupMembers.length && (project.version === ProjectVersion.V1 || project.version === ProjectVersion.V2)) {
const ghostUser = await projectDAL.findProjectGhostUser(project.id, tx);
if (!ghostUser) {
throw new NotFoundError({
message: `Failed to find project owner of project with name ${project.name}`
});
}
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, project.id, tx);
if (!ghostUserLatestKey) {
throw new NotFoundError({
message: `Failed to find project owner's latest key in project with name ${project.name}`
});
}
if (!ghostUserLatestKey.sender.publicKey) {
throw new NotFoundError({
message: `Failed to find project owner's latest key in project with name ${project.name}`
});
}
const bot = await projectBotDAL.findOne({ projectId: project.id }, tx);
if (!bot) {
throw new NotFoundError({
message: `Failed to find project bot in project with name ${project.name}`
});
}
const botPrivateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({
ciphertext: ghostUserLatestKey.encryptedKey,
nonce: ghostUserLatestKey.nonce,
publicKey: ghostUserLatestKey.sender.publicKey,
privateKey: botPrivateKey
});
const projectKeyData = groupMembers.map(({ user: { publicKey, id } }) => {
if (!publicKey) {
throw new NotFoundError({
message: `Failed to find user's public key in project with name ${project.name}`
});
}
const { ciphertext: encryptedKey, nonce } = crypto
.encryption()
.asymmetric()
.encrypt(plaintextProjectKey, publicKey, botPrivateKey);
return {
encryptedKey,
nonce,
senderId: ghostUser.id,
receiverId: id,
projectId: project.id
};
});
await projectKeyDAL.insertMany(projectKeyData, tx);
}
return groupProjectMembership;
});
await permissionService.invalidateProjectPermissionCache(projectId);
return projectGroup;
};
const updateGroupInProject = async ({
projectId,
groupId,
roles,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TUpdateProjectGroupDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Edit, ProjectPermissionSub.Groups);
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
if (!group) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
const projectGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (!projectGroup) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
for await (const { role: requestedRoleChange } of roles) {
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
requestedRoleChange,
project.id
);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to assign group to role",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
// validate custom roles input
const customInputRoles = roles.filter(
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
);
const hasCustomRole = Boolean(customInputRoles.length);
const customRoles = hasCustomRole
? await projectRoleDAL.find({
projectId: project.id,
$in: { slug: customInputRoles.map(({ role }) => role) }
})
: [];
if (customRoles.length !== customInputRoles.length) {
const customRoleSlugs = customRoles.map((customRole) => customRole.slug);
const missingInputRoles = customInputRoles
.filter((inputRole) => !customRoleSlugs.includes(inputRole.role))
.map((role) => role.role);
throw new NotFoundError({
message: `Custom role/s not found: ${missingInputRoles.join(", ")}`
});
}
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
if (!inputRole.isTemporary) {
return {
projectMembershipId: projectGroup.id,
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: projectGroup.id,
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 groupProjectMembershipRoleDAL.transaction(async (tx) => {
await groupProjectMembershipRoleDAL.delete({ projectMembershipId: projectGroup.id }, tx);
return groupProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
});
await permissionService.invalidateProjectPermissionCache(projectId);
return updatedRoles;
};
const removeGroupFromProject = async ({
projectId,
groupId,
actorId,
actor,
actorOrgId,
actorAuthMethod
}: TDeleteProjectGroupDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
if (!group) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
const groupProjectMembership = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (!groupProjectMembership) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Delete, ProjectPermissionSub.Groups);
const deletedProjectGroup = await groupProjectDAL.transaction(async (tx) => {
const groupMembersNotInProject = await userGroupMembershipDAL.findGroupMembersNotInProject(
group.id,
project.id,
tx
);
if (groupMembersNotInProject.length) {
await projectKeyDAL.delete(
{
projectId: project.id,
$in: {
receiverId: groupMembersNotInProject.map(({ user: { id } }) => id)
}
},
tx
);
}
const [projectGroup] = await groupProjectDAL.delete({ groupId: group.id, projectId: project.id }, tx);
return projectGroup;
});
await permissionService.invalidateProjectPermissionCache(projectId);
return deletedProjectGroup;
};
const listGroupsInProject = async ({
projectId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TListProjectGroupDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) {
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
const groupMemberships = await groupProjectDAL.findByProjectId(project.id);
return groupMemberships;
};
const getGroupInProject = async ({
actor,
actorId,
actorAuthMethod,
actorOrgId,
groupId,
projectId
}: TGetGroupInProjectDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) {
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
const [groupMembership] = await groupProjectDAL.findByProjectId(project.id, {
groupId
});
if (!groupMembership) {
throw new NotFoundError({
message: `Group membership with ID ${groupId} not found in project with ID ${projectId}`
});
}
return groupMembership;
};
const listProjectGroupUsers = async ({
id,
projectId,
@@ -542,11 +65,6 @@ export const groupProjectServiceFactory = ({
};
return {
addGroupToProject,
updateGroupInProject,
removeGroupFromProject,
listGroupsInProject,
getGroupInProject,
listProjectGroupUsers
};
};
@@ -217,8 +217,8 @@ export const membershipGroupServiceFactory = ({
},
tx
);
await membershipRoleDAL.insertMany(roleDocs, tx);
return doc;
const roles = await membershipRoleDAL.insertMany(roleDocs, tx);
return { ...doc, roles };
});
return { membership: membershipDoc };
@@ -276,7 +276,7 @@ export const membershipGroupServiceFactory = ({
: undefined
}
});
return memberships;
return { memberships: memberships.data, totalCount: memberships.totalCount };
};
const getMembershipByGroupId = async (dto: TGetMembershipGroupByGroupIdDTO) => {
@@ -290,7 +290,7 @@ export const membershipGroupServiceFactory = ({
});
if (!membership) throw new NotFoundError({ message: `Group membership not found` });
return membership;
return { membership };
};
return {
@@ -54,14 +54,11 @@ export type TUpdateMembershipGroupDTO = {
export type TListMembershipGroupDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
groupId: string;
};
data: {
limit?: number;
offset?: number;
groupName?: string;
roles: string[];
roles?: string[];
};
};
@@ -23,6 +23,7 @@ type TProjectMembershipGroupScopeFactoryDep = {
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "findOne">;
};
// TODO(simp): check for group in org
export const newProjectMembershipGroupFactory = ({
permissionService,
orgDAL,
+23 -34
View File
@@ -1,20 +1,22 @@
import { AccessScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
import { BadRequestError } from "@app/lib/errors";
import { TOrgDALFactory } from "@app/services/org/org-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 { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
type TDeleteOrgMembership = {
orgMembershipId: string;
orgId: string;
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipById" | "transaction">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "delete" | "findProjectMembershipsByUserId">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
userId?: string;
};
@@ -22,11 +24,11 @@ type TDeleteOrgMemberships = {
orgMembershipIds: string[];
orgId: string;
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipsById" | "transaction">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "delete" | "findProjectMembershipsByUserIds">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserIds">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
userId?: string;
};
@@ -35,46 +37,41 @@ export const deleteOrgMembershipFn = async ({
orgId,
orgDAL,
projectMembershipDAL,
projectUserAdditionalPrivilegeDAL,
projectKeyDAL,
userAliasDAL,
licenseService,
userId
userId,
membershipUserDAL
}: TDeleteOrgMembership) => {
const deletedMembership = await orgDAL.transaction(async (tx) => {
const orgMembership = await orgDAL.deleteMembershipById(orgMembershipId, orgId, tx);
if (userId && orgMembership.userId === userId) {
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" });
}
if (!orgMembership.userId) {
const deletedUserId = orgMembership.actorUserId;
if (!deletedUserId) {
await licenseService.updateSubscriptionOrgMemberCount(orgId);
return orgMembership;
}
await userAliasDAL.delete(
{
userId: orgMembership.userId,
userId: deletedUserId,
orgId
},
tx
);
await projectUserAdditionalPrivilegeDAL.delete(
{
userId: orgMembership.userId
},
tx
);
// Get all the project memberships of the user in the organization
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, orgMembership.userId);
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, deletedUserId);
// Delete all the project memberships of the user in the organization
await projectMembershipDAL.delete(
await membershipUserDAL.delete(
{
scope: AccessScope.Project,
$in: {
id: projectMemberships.map((membership) => membership.id)
}
@@ -87,7 +84,7 @@ export const deleteOrgMembershipFn = async ({
$in: {
projectId: projectMemberships.map((membership) => membership.projectId)
},
receiverId: orgMembership.userId
receiverId: deletedUserId
});
// Delete all the project keys of the user in the organization
@@ -112,18 +109,18 @@ export const deleteOrgMembershipsFn = async ({
orgId,
orgDAL,
projectMembershipDAL,
projectUserAdditionalPrivilegeDAL,
projectKeyDAL,
userAliasDAL,
licenseService,
userId
userId,
membershipUserDAL
}: TDeleteOrgMemberships) => {
const deletedMemberships = await orgDAL.transaction(async (tx) => {
const orgMemberships = await orgDAL.deleteMembershipsById(orgMembershipIds, orgId, tx);
const membershipUserIds = orgMemberships
.filter((member) => Boolean(member.userId))
.map((member) => member.userId) as string[];
.filter((member) => Boolean(member.actorUserId))
.map((member) => member.actorUserId) as string[];
if (userId && membershipUserIds.includes(userId)) {
// scott: this is temporary, we will add a leave org endpoint with proper handling to ensure org isn't abandoned/broken
@@ -145,21 +142,13 @@ export const deleteOrgMembershipsFn = async ({
tx
);
await projectUserAdditionalPrivilegeDAL.delete(
{
$in: {
userId: membershipUserIds
}
},
tx
);
// 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
await projectMembershipDAL.delete(
await membershipUserDAL.delete(
{
scope: AccessScope.Project,
$in: {
id: projectMemberships.map((membership) => membership.id)
}
@@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TProjectUserMembershipRoleDALFactory = ReturnType<typeof projectUserMembershipRoleDALFactory>;
export const projectUserMembershipRoleDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.ProjectUserMembershipRole);
return orm;
};