Bỏ qua, đến nội dung

@digiforce-nc/plugin-acl

Plugin quản lý vai trò (roles)phân quyền (permissions) - cầu nối giữa database và ACL engine, cung cấp UI quản trị và middleware kiểm soát truy cập.

Plugin này làm gì?

Hãy hình dung: @digiforce-nc/acl (core) là bộ máy Phân quyền - nó biết cách kiểm tra "user X có quyền Y không?". Nhưng bộ máy đó cần dữ liệu (role nào có quyền gì) và kết nối vào request pipeline. plugin-acl đảm nhận cả hai:

Ba nhiệm vụ chính

#Nhiệm vụChi tiết
1Lưu trữ & đồng bộLưu cấu hình role/permission trong DB → đồng bộ vào ACL engine (in-memory) qua hooks
2Middleware pipelineGắn role vào request, kiểm tra quyền trước khi xử lý, bổ sung ACL meta vào response
3UI quản trịTrang "Roles & Permissions" cho admin cấu hình role, gán quyền collection/action/scope

Kiến trúc

Tổng quan 4 tầng

Plugin hoạt động qua 4 tầng chính - từ giao diện người dùng xuống engine Phân quyền:

TầngVai tròThành phần
Client UIAdmin cấu hình role, user đổi roleRolesManagement, SwitchRole
Server APIXử lý request CRUD role/permission7 nhóm endpoint (xem bên dưới)
DatabaseLưu trữ bền vững5 bảng: roles, rolesUsers, resources, actions, scopes
ACL EnginePhân quyền in-memory, nhanhapp.acl - từ @digiforce-nc/acl (core)

API endpoints

Đây là các endpoint mà Server cung cấp - chia thành quản trị (admin dùng) và runtime (mọi user dùng):

EndpointAi dùngMô tả
roles CRUDAdminTạo, sửa, xóa, liệt kê role
roles.resources CRUDAdminGán permission cho collection trên từng role
availableActions:listAdminDanh sách action hệ thống hỗ trợ (view, create, ...)
roles.collections:listAdminDanh sách collection kèm trạng thái permission cho role
roles:checkMọi userClient gọi để biết mình có quyền gì → ẩn/hiện UI
roles:setSystemRoleModeAdminChuyển chế độ role (default / union)
users:setDefaultRoleMọi userUser tự đổi role mặc định

Middleware pipeline

Khi HTTP request đến, nó đi qua chuỗi middleware theo thứ tự:

MiddlewareThời điểmLàm gì
setCurrentRoleSau auth, trước aclĐọc X-Role header → xác định role hiện tại của user
checkQueryPermissionTrước handlerMerge ACL filter vào query (ví dụ: chỉ xem bản ghi của mình)
checkChangesWithAssociationTrước handlerKiểm tra field whitelist khi create/update, lọc association ngoài quyền
withACLMetaSau handlerBổ sung meta.allowedActions vào response (client biết row nào được edit/delete)

DB hooks - đồng bộ tự động

Khi admin sửa permission qua UI, dữ liệu lưu vào DB rồi tự động đồng bộ sang ACL engine qua hooks:

Sự kiện DBHook gọiKết quả ACL
Lưu rolewriteRoleToACL()acl.define() + cập nhật strategy
Thêm/sửa resourcewriteResourceToACL()Tạo ACLResource mới
Thêm/sửa actionwriteActionToACL()role.grantAction()
Thêm field mớiappend fieldTự thêm field vào tất cả action đã cấu hình
Xóa resource/action/fieldcleanupXóa grant tương ứng

Database - 5 bảng chính

BảngChứa gìVí dụ
rolesĐịnh nghĩa role + strategy mặc định{ name: 'editor', strategy: { actions: ['view'] } }
rolesUsersLiên kết user ↔ role (M2M){ userId: 1, roleName: 'editor' }
dataSourcesRolesResourcesPermission của role trên từng collection{ roleName: 'editor', name: 'posts' }
dataSourcesRolesResourcesActionsAction cụ thể + fields whitelist{ name: 'view', fields: ['title'] }
dataSourcesRolesResourcesScopesBộ lọc dữ liệu (scope){ name: 'own', scope: { createdById: '...' } }

Vòng đời plugin

Đồng bộ DB → ACL

Sự kiện DBHookHành động ACL
Lưu roleroles.afterSaveWithAssociationswriteRoleToACL()acl.define() + setStrategy()
Thêm/sửa resourcerolesResources.afterSavewriteResourceToACL() → tạo ACLResource
Thêm/sửa actionrolesResourcesActions.afterSavewriteActionToACL()role.grantAction()
Xóa resource/action*.afterDestroyXóa grant tương ứng
Thêm field mớifields.afterCreateAppend field vào tất cả action đã cấu hình
Xóa fieldfields.afterDestroyRemove field khỏi actions
App khởi độngafterStartwriteRolesToACL() - nạp toàn bộ

Ví dụ sử dụng API

Tạo role với strategy

typescript
// Tạo role mới với strategy cho phép view tất cả, create tất cả
await agent.resource('roles').create({
  values: {
    name: 'editor',
    title: 'Editor',
    strategy: {
      actions: ['view', 'create'],
    },
  },
});

Cập nhật strategy trên role

typescript
// Đổi strategy: view chỉ bản ghi của mình, create tất cả
await agent.resource('roles').update({
  filterByTargetKey: 'editor',
  values: {
    strategy: {
      actions: ['create:all', 'view:own'],
    },
  },
});

Cấu hình permission cho collection cụ thể

typescript
// Gán permission riêng cho collection "posts" trên role "editor"
await agent.resource('roles.resources', 'editor').create({
  values: {
    name: 'posts',
    usingActionsConfig: true,
    actions: [
      { name: 'create' },
      { name: 'view', fields: ['title', 'content'] },
      { name: 'update', fields: ['title', 'content'] },
    ],
  },
});

Tạo scope và gán vào action

typescript
// Tạo scope "published posts" - chỉ bản ghi published = true
const { body: { data: scope } } = await agent
  .resource('dataSourcesRolesResourcesScopes')
  .create({
    values: {
      resourceName: 'posts',
      name: 'published posts',
      scope: { published: true },
    },
  });

// Gán scope vào action view
await agent.resource('roles.resources', 'editor').create({
  values: {
    name: 'posts',
    usingActionsConfig: true,
    actions: [
      { name: 'view', fields: ['title'], scope: scope.id },
      { name: 'create' },
    ],
  },
});

Kiểm tra quyền hiệu lực (roles:check)

typescript
const response = await agent.resource('roles').check();
const { actions, role, allowAll } = response.body.data;

// actions chứa map dạng { 'posts:create': {...}, 'posts:view': {...} }
console.log(actions['posts:create']); // defined → có quyền
console.log(actions['posts:destroy']); // undefined → không có quyền

Lấy ACL meta cho từng row (client dùng để ẩn/hiện nút)

typescript
// Gửi header X-With-ACL-Meta
const response = await agent
  .set('X-With-ACL-Meta', true)
  .resource('posts')
  .list({});

const { data, meta } = response.body;
// meta.allowedActions.view = [1, 2, 3]    - row IDs được xem
// meta.allowedActions.update = [1, 2]      - row IDs được sửa
// meta.allowedActions.destroy = [1]        - row IDs được xóa

Kiểm tra 403 khi không có quyền

typescript
// User với role member (strategy: view:own) cố create → 403
const memberAgent = await app.agent().login(memberUser);
const response = await memberAgent.resource('posts').create({
  values: { title: 'test' },
});
// response.statusCode === 403

Field-level permission - response tự lọc field

typescript
// Role chỉ được view field "title" (không "description")
await agent.resource('roles.resources', 'editor').create({
  values: {
    name: 'posts',
    usingActionsConfig: true,
    actions: [
      { name: 'view', fields: ['title'] },
    ],
  },
});

const response = await editorAgent.resource('posts').list();
const post = response.body.data[0];
// post.id      → có (luôn bổ sung PK)
// post.title   → "Hello"
// post.description → undefined (bị lọc)

Thành phần client

Thành phầnMô tả
RolesManagementTrang quản lý roles (Settings → Roles & Permissions)
SwitchRoleComponent đổi role hiện tại
RoleModeSelectChọn chế độ role system (default / union)
ACLSettingsUITabs cấu hình quyền (System, Desktop Menu, ...)
RolesManagerRegistry cho plugin khác thêm tab vào roles screen

Dependencies

PackageVai trò
@digiforce-nc/aclACL engine (peer)
@digiforce-nc/serverServer framework (peer)
@digiforce-nc/databaseDatabase ORM (peer)
@digiforce-nc/actionsAction context types (peer)
@digiforce-nc/cacheCache role data (peer)
@digiforce-nc/clientClient framework (peer)

Mục lục chi tiết