feat(acl): deep data access by section for roles and users

Role default + user override with OR rules; covers customers, leads, sales, activities, notes, routines, projects, and tickets.
This commit is contained in:
2026-10-22 11:53:26 +03:30
parent cb5944e767
commit 09a60b3b5a
25 changed files with 1997 additions and 122 deletions
+9
View File
@@ -3,6 +3,7 @@ import {
ArrayUnique,
IsArray,
IsIn,
IsObject,
IsOptional,
IsString,
MaxLength,
@@ -36,6 +37,10 @@ export class CreateOrgRoleDto {
@IsIn(PERMISSION_KEYS, { each: true })
permissions?: RolePermissionKey[];
@IsOptional()
@IsObject()
dataAccessPolicy?: Record<string, unknown>;
@IsOptional()
@ValidateIf((_, v) => v !== null)
@IsString()
@@ -64,6 +69,10 @@ export class UpdateOrgRoleDto {
@IsIn(PERMISSION_KEYS, { each: true })
permissions?: RolePermissionKey[];
@IsOptional()
@IsObject()
dataAccessPolicy?: Record<string, unknown>;
@IsOptional()
@ValidateIf((_, v) => v !== null)
@IsString()
+17 -1
View File
@@ -5,7 +5,13 @@ import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import type { OrgRoleDto, RolePermissionKey, UserRole } from '@mento/shared';
import type {
DataAccessPolicy,
OrgRoleDto,
RolePermissionKey,
UserRole,
} from '@mento/shared';
import { parseDataAccessPolicy } from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import {
@@ -25,6 +31,7 @@ type RoleRow = {
description: string;
color: string;
permissions: string[];
dataAccessPolicy?: unknown;
systemKey: string | null;
parentId: string | null;
createdAt: Date;
@@ -70,6 +77,7 @@ export class OrgRolesService {
description?: string;
color?: string;
permissions?: RolePermissionKey[];
dataAccessPolicy?: DataAccessPolicy;
parentId?: string | null;
},
): Promise<OrgRoleDto> {
@@ -101,6 +109,9 @@ export class OrgRolesService {
color: dto.color ?? parent?.color ?? 'teal',
permissions,
parentId,
...(dto.dataAccessPolicy
? { dataAccessPolicy: parseDataAccessPolicy(dto.dataAccessPolicy) }
: {}),
},
include: ROLE_MEMBERS_INCLUDE,
});
@@ -122,6 +133,7 @@ export class OrgRolesService {
description?: string;
color?: string;
permissions?: RolePermissionKey[];
dataAccessPolicy?: DataAccessPolicy;
parentId?: string | null;
},
): Promise<OrgRoleDto> {
@@ -144,6 +156,9 @@ export class OrgRolesService {
if (dto.permissions !== undefined) {
data.permissions = sanitizePermissions(dto.permissions);
}
if (dto.dataAccessPolicy !== undefined) {
data.dataAccessPolicy = parseDataAccessPolicy(dto.dataAccessPolicy);
}
if (dto.parentId !== undefined) {
const nextParentId =
@@ -410,6 +425,7 @@ export class OrgRolesService {
description: row.description,
color: row.color,
permissions: sanitizePermissions(row.permissions),
dataAccessPolicy: parseDataAccessPolicy(row.dataAccessPolicy),
systemKey,
parentId: row.parentId,
parentName: row.parentId ? (nameById.get(row.parentId) ?? null) : null,