feat: fixing more and more ts

This commit is contained in:
=
2025-10-10 00:09:46 +05:30
parent 5a8bad3556
commit fd98465dec
47 changed files with 1369 additions and 1161 deletions
@@ -1,46 +1,22 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { AccessScope, ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { NotFoundError } from "@app/lib/errors";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
import { TIdentityProjectDALFactory } from "./identity-project-dal";
import { TIdentityProjectMembershipRoleDALFactory } from "./identity-project-membership-role-dal";
import {
TCreateProjectIdentityDTO,
TDeleteProjectIdentityDTO,
TGetProjectIdentityByIdentityIdDTO,
TGetProjectIdentityByMembershipIdDTO,
TListProjectIdentityDTO,
TUpdateProjectIdentityDTO
TListProjectIdentityDTO
} from "./identity-project-types";
type TIdentityProjectServiceFactoryDep = {
identityProjectDAL: TIdentityProjectDALFactory;
orgDAL: Pick<TOrgDALFactory, "findById">;
identityProjectMembershipRoleDAL: Pick<
TIdentityProjectMembershipRoleDALFactory,
"create" | "transaction" | "insertMany" | "delete"
>;
projectDAL: Pick<TProjectDALFactory, "findById">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getProjectPermissionByRoles" | "invalidateProjectPermissionCache"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
membershipIdentityDAL: TMembershipIdentityDALFactory;
};
export type TIdentityProjectServiceFactory = ReturnType<typeof identityProjectServiceFactory>;
@@ -48,275 +24,8 @@ export type TIdentityProjectServiceFactory = ReturnType<typeof identityProjectSe
export const identityProjectServiceFactory = ({
identityProjectDAL,
permissionService,
identityOrgMembershipDAL,
identityProjectMembershipRoleDAL,
projectDAL,
projectRoleDAL,
orgDAL
membershipIdentityDAL
}: TIdentityProjectServiceFactoryDep) => {
const createProjectIdentity = async ({
identityId,
actor,
actorId,
actorOrgId,
actorAuthMethod,
projectId,
roles
}: TCreateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Create,
subject(ProjectPermissionSub.Identity, {
identityId
})
);
const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId });
if (existingIdentity)
throw new BadRequestError({
message: `Identity with ID ${identityId} already exists in project with ID ${projectId}`
});
const project = await projectDAL.findById(projectId);
const identityOrgMembership = await identityOrgMembershipDAL.findOne({
identityId,
orgId: project.orgId
});
if (!identityOrgMembership)
throw new NotFoundError({
message: `Failed to find identity with ID ${identityId}`
});
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,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to assign to role",
shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
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,
$in: { slug: customInputRoles.map(({ role }) => role) }
})
: [];
if (customRoles.length !== customInputRoles.length)
throw new NotFoundError({ message: "One or more custom project roles not found" });
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
const projectIdentity = await identityProjectDAL.transaction(async (tx) => {
const identityProjectMembership = await identityProjectDAL.create(
{
identityId,
projectId: project.id
},
tx
);
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
if (!inputRole.isTemporary) {
return {
projectMembershipId: identityProjectMembership.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: identityProjectMembership.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 identityRoles = await identityProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
return { ...identityProjectMembership, roles: identityRoles };
});
await permissionService.invalidateProjectPermissionCache(projectId);
return projectIdentity;
};
const updateProjectIdentity = async ({
projectId,
identityId,
roles,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TUpdateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId });
if (!projectIdentity)
throw new NotFoundError({
message: `Identity with ID ${identityId} doesn't exists in project with ID ${projectId}`
});
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,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to change role",
shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
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.Admin)
);
const hasCustomRole = Boolean(customInputRoles.length);
const customRoles = hasCustomRole
? await projectRoleDAL.find({
projectId,
$in: { slug: customInputRoles.map(({ role }) => role) }
})
: [];
if (customRoles.length !== customInputRoles.length)
throw new NotFoundError({ message: "One or more custom project 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: projectIdentity.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: projectIdentity.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 identityProjectMembershipRoleDAL.transaction(async (tx) => {
await identityProjectMembershipRoleDAL.delete({ projectMembershipId: projectIdentity.id }, tx);
return identityProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
});
await permissionService.invalidateProjectPermissionCache(projectId);
return updatedRoles;
};
const deleteProjectIdentity = async ({
identityId,
actorId,
actor,
actorOrgId,
actorAuthMethod,
projectId
}: TDeleteProjectIdentityDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership) {
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Delete,
subject(ProjectPermissionSub.Identity, { identityId })
);
const [deletedIdentity] = await identityProjectDAL.delete({ identityId, projectId });
await permissionService.invalidateProjectPermissionCache(projectId);
return deletedIdentity;
};
const listProjectIdentities = async ({
projectId,
actor,
@@ -392,9 +101,13 @@ export const identityProjectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TGetProjectIdentityByMembershipIdDTO) => {
const membership = await identityProjectDAL.findOne({ id: identityMembershipId });
const membership = await membershipIdentityDAL.findOne({
id: identityMembershipId,
scope: AccessScope.Project,
scopeOrgId: actorOrgId
});
if (!membership) {
if (!membership || !membership.scopeProjectId || !membership.actorIdentityId) {
throw new NotFoundError({
message: `Project membership with ID '${identityMembershipId}' not found`
});
@@ -403,7 +116,7 @@ export const identityProjectServiceFactory = ({
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: membership.projectId,
projectId: membership.scopeProjectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
@@ -411,20 +124,17 @@ export const identityProjectServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: membership.identityId })
subject(ProjectPermissionSub.Identity, { identityId: membership.actorIdentityId })
);
const [identityMembership] = await identityProjectDAL.findByProjectId(membership.projectId, {
identityId: membership.identityId
const [identityMembership] = await identityProjectDAL.findByProjectId(membership.scopeProjectId, {
identityId: membership.actorIdentityId
});
return identityMembership;
};
return {
createProjectIdentity,
updateProjectIdentity,
deleteProjectIdentity,
listProjectIdentities,
getProjectIdentityByIdentityId,
getProjectIdentityByMembershipId