feat: completed additional privilege factory

This commit is contained in:
=
2025-09-27 23:58:56 +05:30
parent a846831b26
commit c5f2a74d5a
12 changed files with 656 additions and 1 deletions

View File

@@ -310,6 +310,16 @@ export enum ActionProjectType {
Any = "any"
}
export enum TemporaryPermissionMode {
Relative = "relative"
}
export enum MembershipActors {
Group = "group",
User = "user",
Identity = "identity"
}
export enum SortDirection {
ASC = "asc",
DESC = "desc"

View File

@@ -292,6 +292,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
// TODO(simp): look into this in the new ap service
await accessApprovalRequestDAL.update(
{
privilegeId: userPrivilege.id

View File

@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TAdditionalPrivilegeDALFactory = ReturnType<typeof additionalPrivilegeDALFactory>;
export const additionalPrivilegeDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.AdditionalPrivilege);
return orm;
};

View File

@@ -0,0 +1,208 @@
import { RawRule } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
import { TMembershipDALFactory } from "../membership/membership-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TAdditionalPrivilegeDALFactory } from "./additional-privilege-dal";
import {
TAdditionalPrivilegesScopeFactory,
TCreateAdditionalPrivilegesDTO,
TDeleteAdditionalPrivilegesDTO,
TGetAdditionalPrivilegesByIdDTO,
TListAdditionalPrivilegesDTO,
TUpdateAdditionalPrivilegesDTO
} from "./additional-privilege-types";
import { newNamespaceAdditionalPrivilegesFactory } from "./namespace/namespace-additional-privilege-factory";
import { newOrgAdditionalPrivilegesFactory } from "./org/org-additional-privilege-factory";
import { newProjectAdditionalPrivilegesFactory } from "./project/project-additional-privilege-factory";
type TAdditionalPrivilegeServiceFactoryDep = {
additionalPrivilegeDAL: TAdditionalPrivilegeDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
};
export type TAdditionalPrivilegeServiceFactory = ReturnType<typeof additionalPrivilegeServiceFactory>;
export const additionalPrivilegeServiceFactory = ({
additionalPrivilegeDAL,
permissionService,
orgDAL,
membershipDAL
}: TAdditionalPrivilegeServiceFactoryDep) => {
const scopeFactory: Record<AccessScope, TAdditionalPrivilegesScopeFactory> = {
[AccessScope.Organization]: newOrgAdditionalPrivilegesFactory({}),
[AccessScope.Project]: newProjectAdditionalPrivilegesFactory({
membershipDAL,
orgDAL,
permissionService
}),
[AccessScope.Namespace]: newNamespaceAdditionalPrivilegesFactory({})
};
const createAdditionalPrivilege = async (dto: TCreateAdditionalPrivilegesDTO) => {
const { scopeData, data } = dto;
const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onCreateAdditionalPrivilegesGuard(dto);
const existingSlug = await additionalPrivilegeDAL.findOne({
name: data.name,
membershipId
});
if (existingSlug) throw new BadRequestError({ message: `Additional privilege with name ${data.name} exist` });
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
if (!data.isTemporary) {
const additionalPrivilege = await additionalPrivilegeDAL.create({
membershipId,
name: data.name,
isTemporary: data.isTemporary,
permissions: JSON.stringify(packRules(data.permissions as RawRule[]))
});
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } };
}
if (!data.temporaryAccessStartTime || !data.temporaryRange) {
throw new BadRequestError({ message: "Temporary mode expects start time and range" });
}
const relativeTempAllocatedTimeInMs = ms(data.temporaryRange);
const additionalPrivilege = await additionalPrivilegeDAL.create({
membershipId,
name: data.name,
isTemporary: data.isTemporary,
permissions: data.permissions,
temporaryAccessEndTime: new Date(
new Date(data.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs
),
temporaryAccessStartTime: new Date(data.temporaryAccessStartTime),
temporaryMode: TemporaryPermissionMode.Relative,
temporaryRange: data.temporaryRange
});
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } };
};
const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => {
const { scopeData, data } = dto;
const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onUpdateAdditionalPrivilegesGuard(dto);
const existingPrivilege = await additionalPrivilegeDAL.findOne({
id: dto.selector.id,
membershipId
});
if (!existingPrivilege)
throw new NotFoundError({ message: `Additional privilege with name ${data.name} doesn't exist` });
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const updatedData = { ...existingPrivilege, ...data };
if (!updatedData.isTemporary) {
const additionalPrivilege = await additionalPrivilegeDAL.updateById(existingPrivilege.id, {
name: updatedData.name,
isTemporary: data.isTemporary,
permissions: data.permissions ? JSON.stringify(packRules(data.permissions as RawRule[])) : undefined
});
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
}
if (!updatedData.temporaryAccessStartTime || !updatedData.temporaryRange) {
throw new BadRequestError({ message: "Temporary mode expects start time and range" });
}
const relativeTempAllocatedTimeInMs = ms(updatedData.temporaryRange);
const additionalPrivilege = await additionalPrivilegeDAL.create({
membershipId,
name: updatedData.name,
isTemporary: updatedData.isTemporary,
permissions: updatedData.permissions,
temporaryAccessEndTime: new Date(
new Date(updatedData.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs
),
temporaryAccessStartTime: new Date(updatedData.temporaryAccessStartTime),
temporaryMode: TemporaryPermissionMode.Relative,
temporaryRange: updatedData.temporaryRange
});
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const deleteAdditionalPrivilege = async (dto: TDeleteAdditionalPrivilegesDTO) => {
const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onDeleteAdditionalPrivilegesGuard(dto);
const existingPrivilege = await additionalPrivilegeDAL.findOne({
id: selector.id,
membershipId
});
if (!existingPrivilege)
throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` });
const additionalPrivilege = await additionalPrivilegeDAL.deleteById(existingPrivilege.id);
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => {
const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto);
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
id: selector.id,
membershipId
});
if (!additionalPrivilege)
throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` });
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const listAdditionalPrivileges = async (dto: TListAdditionalPrivilegesDTO) => {
const { scopeData } = dto;
const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto);
const additionalPrivileges = await additionalPrivilegeDAL.find({
membershipId
});
return {
additionalPrivileges: additionalPrivileges.map((el) => ({
...el,
permissions: unpackPermissions(el.permissions)
}))
};
};
return {
createAdditionalPrivilege,
updateAdditionalPrivilege,
deleteAdditionalPrivilege,
getAdditionalPrivilegeById,
listAdditionalPrivileges
};
};

View File

@@ -0,0 +1,73 @@
import { AccessScopeData, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas";
import { OrgServiceActor } from "@app/lib/types";
export interface TAdditionalPrivilegesScopeFactory {
onCreateAdditionalPrivilegesGuard: (arg: TCreateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onGetAdditionalPrivilegesByIdGuard: (arg: TGetAdditionalPrivilegesByIdDTO) => Promise<{ membershipId: string }>;
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
}
export type TCreateAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
data: {
actorId: string;
actorType: MembershipActors;
name: string;
permissions: unknown;
isTemporary: boolean;
temporaryMode?: TemporaryPermissionMode.Relative;
temporaryRange?: string;
temporaryAccessStartTime?: string;
};
};
export type TUpdateAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
id: string;
actorId: string;
actorType: MembershipActors;
};
data: Partial<{
name: string;
permissions: unknown;
isTemporary: boolean;
temporaryMode?: TemporaryPermissionMode.Relative;
temporaryRange?: string;
temporaryAccessStartTime?: string;
}>;
};
export type TListAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
actorId: string;
actorType: MembershipActors;
};
};
export type TDeleteAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
id: string;
actorId: string;
actorType: MembershipActors;
};
};
export type TGetAdditionalPrivilegesByIdDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
id: string;
actorId: string;
actorType: MembershipActors;
};
};

View File

@@ -0,0 +1,52 @@
import { AccessScope } from "@app/db/schemas";
import { InternalServerError } from "@app/lib/errors";
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
type TNamespaceAdditionalPrivilegesScopeFactoryDep = Record<string, never>;
export const newNamespaceAdditionalPrivilegesFactory = (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
deps: TNamespaceAdditionalPrivilegesScopeFactoryDep
): TAdditionalPrivilegesScopeFactory => {
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Namespace) {
return { key: "namespaceId" as const, value: dto.namespaceId };
}
throw new InternalServerError({ message: "Invalid scope provided for the namespace factory" });
};
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges create not implemented" });
};
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges update not implemented" });
};
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges delete not implemented" });
};
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges list not implemented" });
};
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges get by id not implemented" });
};
return {
onCreateAdditionalPrivilegesGuard,
onUpdateAdditionalPrivilegesGuard,
onDeleteAdditionalPrivilegesGuard,
onListAdditionalPrivilegesGuard,
onGetAdditionalPrivilegesByIdGuard,
getScopeField
};
};

View File

@@ -0,0 +1,52 @@
import { AccessScope } from "@app/db/schemas";
import { InternalServerError } from "@app/lib/errors";
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
type TOrgAdditionalPrivilegesScopeFactoryDep = Record<string, never>;
export const newOrgAdditionalPrivilegesFactory = (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
deps: TOrgAdditionalPrivilegesScopeFactoryDep
): TAdditionalPrivilegesScopeFactory => {
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Organization) {
return { key: "orgId" as const, value: dto.orgId };
}
throw new InternalServerError({ message: "Invalid scope provided for the org factory" });
};
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges create not implemented" });
};
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges update not implemented" });
};
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges delete not implemented" });
};
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges list not implemented" });
};
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges get by id not implemented" });
};
return {
onCreateAdditionalPrivilegesGuard,
onUpdateAdditionalPrivilegesGuard,
onDeleteAdditionalPrivilegesGuard,
onListAdditionalPrivilegesGuard,
onGetAdditionalPrivilegesByIdGuard,
getScopeField
};
};

View File

@@ -0,0 +1,222 @@
import { ForbiddenError } from "@casl/ability";
import { AccessScope, ActionProjectType, MembershipActors } from "@app/db/schemas";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionIdentityActions,
ProjectPermissionMemberActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { TMembershipDALFactory } from "@app/services/membership/membership-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
type TProjectAdditionalPrivilegesScopeFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
};
export const newProjectAdditionalPrivilegesFactory = ({
permissionService,
orgDAL,
membershipDAL
}: TProjectAdditionalPrivilegesScopeFactoryDep): TAdditionalPrivilegesScopeFactory => {
const $getPermission = (permission: OrgServiceActor, projectId: string) => {
return permissionService.getProjectPermission({
actor: permission.type,
actorId: permission.id,
actionProjectType: ActionProjectType.Any,
actorAuthMethod: permission.authMethod,
projectId,
actorOrgId: permission.orgId
});
};
const $getActorType = (type: MembershipActors) => {
if (type === MembershipActors.Group)
throw new InternalServerError({ message: "Group additional privilege not implemented for projects" });
if (type === MembershipActors.Identity) return ActorType.IDENTITY;
if (type === MembershipActors.User) return ActorType.USER;
throw new InternalServerError({ message: `Group additional privilege not implemented for actor: ${String(type)}` });
};
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Project) {
return { key: "projectId" as const, value: dto.projectId };
}
throw new BadRequestError({ message: "Invalid scope provided for the factory" });
};
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const actorType = $getActorType(dto.data.actorType);
const { permission } = await $getPermission(dto.permission, scope.value);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
const { permission: targetUserPermission, memberships } = await $getPermission(
{ ...dto.permission, type: actorType, id: dto.data.actorId },
scope.value
);
const permissionAction =
actorType === ActorType.USER
? ProjectPermissionMemberActions.GrantPrivileges
: ProjectPermissionIdentityActions.GrantPrivileges;
const permissionSubject =
actorType === ActorType.USER ? ProjectPermissionSub.Member : ProjectPermissionSub.Identity;
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject,
permission,
targetUserPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged actor",
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const membership = memberships.find(
(el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.data.actorId
);
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
return { membershipId: membership.id };
};
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const actorType = $getActorType(dto.selector.actorType);
const { permission } = await $getPermission(dto.permission, scope.value);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
const { permission: targetUserPermission, memberships } = await $getPermission(
{ ...dto.permission, type: actorType, id: dto.selector.actorId },
scope.value
);
const permissionAction =
actorType === ActorType.USER
? ProjectPermissionMemberActions.GrantPrivileges
: ProjectPermissionIdentityActions.GrantPrivileges;
const permissionSubject =
actorType === ActorType.USER ? ProjectPermissionSub.Member : ProjectPermissionSub.Identity;
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject,
permission,
targetUserPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged actor",
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const membership = memberships.find(
(el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.selector.actorId
);
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
return { membershipId: membership.id };
};
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const actorType = $getActorType(dto.selector.actorType);
const { permission } = await $getPermission(dto.permission, scope.value);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const membership = await membershipDAL.findOne({
scopeOrgId: dto.permission.orgId,
scopeProjectId: scope.value,
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
});
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
return { membershipId: membership.id };
};
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] = async (
dto
) => {
const scope = getScopeField(dto.scopeData);
const actorType = $getActorType(dto.selector.actorType);
const permissionSet =
actorType === ActorType.USER
? ([ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member] as const)
: ([ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity] as const);
const { permission } = await $getPermission(dto.permission, scope.value);
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
const membership = await membershipDAL.findOne({
scopeOrgId: dto.permission.orgId,
scopeProjectId: scope.value,
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
});
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
return { membershipId: membership.id };
};
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const actorType = $getActorType(dto.selector.actorType);
const permissionSet =
actorType === ActorType.USER
? ([ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member] as const)
: ([ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity] as const);
const { permission } = await $getPermission(dto.permission, scope.value);
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
const membership = await membershipDAL.findOne({
scopeOrgId: dto.permission.orgId,
scopeProjectId: scope.value,
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
});
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
return { membershipId: membership.id };
};
return {
onCreateAdditionalPrivilegesGuard,
onUpdateAdditionalPrivilegesGuard,
onDeleteAdditionalPrivilegesGuard,
onListAdditionalPrivilegesGuard,
onGetAdditionalPrivilegesByIdGuard,
getScopeField
};
};

View File

@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TMembershipDALFactory = ReturnType<typeof membershipDALFactory>;
export const membershipDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.Membership);
return orm;
};

View File

@@ -0,0 +1,11 @@
import { TMembershipDALFactory } from "./membership-dal";
type TMembershipServiceFactoryDep = {
membershipDAL: TMembershipDALFactory;
};
export type TMembershipServiceFactory = ReturnType<typeof membershipServiceFactory>;
export const membershipServiceFactory = ({ membershipDAL }: TMembershipServiceFactoryDep) => {
return {};
};

View File

@@ -44,11 +44,17 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
const factory = scopeFactory[scopeData.scope];
await factory.onCreateRoleGuard(dto);
const scope = factory.getScopeField(scopeData);
const existingRole = await roleDAL.findOne({
slug: data.slug,
[scope.key]: scope.value
});
if (existingRole) throw new NotFoundError({ message: `Role with ${data.slug} exist` });
validateHandlebarTemplate("Role Creation", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const scope = factory.getScopeField(scopeData);
const role = await roleDAL.create({
name: data.name,
description: data.description,