Giao diện
@digiforce-nc/plugin-acl
Plugin quản lý vai trò (roles) và 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 |
|---|---|---|
| 1 | Lưu trữ & đồng bộ | Lưu cấu hình role/permission trong DB → đồng bộ vào ACL engine (in-memory) qua hooks |
| 2 | Middleware pipeline | Gắn role vào request, kiểm tra quyền trước khi xử lý, bổ sung ACL meta vào response |
| 3 | UI 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ầng | Vai trò | Thành phần |
|---|---|---|
| Client UI | Admin cấu hình role, user đổi role | RolesManagement, SwitchRole |
| Server API | Xử lý request CRUD role/permission | 7 nhóm endpoint (xem bên dưới) |
| Database | Lưu trữ bền vững | 5 bảng: roles, rolesUsers, resources, actions, scopes |
| ACL Engine | Phân quyền in-memory, nhanh | app.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):
| Endpoint | Ai dùng | Mô tả |
|---|---|---|
roles CRUD | Admin | Tạo, sửa, xóa, liệt kê role |
roles.resources CRUD | Admin | Gán permission cho collection trên từng role |
availableActions:list | Admin | Danh sách action hệ thống hỗ trợ (view, create, ...) |
roles.collections:list | Admin | Danh sách collection kèm trạng thái permission cho role |
roles:check | Mọi user | Client gọi để biết mình có quyền gì → ẩn/hiện UI |
roles:setSystemRoleMode | Admin | Chuyển chế độ role (default / union) |
users:setDefaultRole | Mọi user | User tự đổi role mặc định |
Middleware pipeline
Khi HTTP request đến, nó đi qua chuỗi middleware theo thứ tự:
| Middleware | Thời điểm | Làm gì |
|---|---|---|
| setCurrentRole | Sau auth, trước acl | Đọc X-Role header → xác định role hiện tại của user |
| checkQueryPermission | Trước handler | Merge ACL filter vào query (ví dụ: chỉ xem bản ghi của mình) |
| checkChangesWithAssociation | Trước handler | Kiểm tra field whitelist khi create/update, lọc association ngoài quyền |
| withACLMeta | Sau handler | Bổ 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 DB | Hook gọi | Kết quả ACL |
|---|---|---|
| Lưu role | writeRoleToACL() | acl.define() + cập nhật strategy |
| Thêm/sửa resource | writeResourceToACL() | Tạo ACLResource mới |
| Thêm/sửa action | writeActionToACL() | role.grantAction() |
| Thêm field mới | append field | Tự thêm field vào tất cả action đã cấu hình |
| Xóa resource/action/field | cleanup | Xóa grant tương ứng |
Database - 5 bảng chính
| Bảng | Chứa gì | Ví dụ |
|---|---|---|
roles | Định nghĩa role + strategy mặc định | { name: 'editor', strategy: { actions: ['view'] } } |
rolesUsers | Liên kết user ↔ role (M2M) | { userId: 1, roleName: 'editor' } |
dataSourcesRolesResources | Permission của role trên từng collection | { roleName: 'editor', name: 'posts' } |
dataSourcesRolesResourcesActions | Action cụ thể + fields whitelist | { name: 'view', fields: ['title'] } |
dataSourcesRolesResourcesScopes | Bộ lọc dữ liệu (scope) | { name: 'own', scope: { createdById: '...' } } |
Vòng đời plugin
Đồng bộ DB → ACL
| Sự kiện DB | Hook | Hành động ACL |
|---|---|---|
| Lưu role | roles.afterSaveWithAssociations | writeRoleToACL() → acl.define() + setStrategy() |
| Thêm/sửa resource | rolesResources.afterSave | writeResourceToACL() → tạo ACLResource |
| Thêm/sửa action | rolesResourcesActions.afterSave | writeActionToACL() → role.grantAction() |
| Xóa resource/action | *.afterDestroy | Xóa grant tương ứng |
| Thêm field mới | fields.afterCreate | Append field vào tất cả action đã cấu hình |
| Xóa field | fields.afterDestroy | Remove field khỏi actions |
| App khởi động | afterStart | writeRolesToACL() - 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ềnLấ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óaKiể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 === 403Field-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ần | Mô tả |
|---|---|
RolesManagement | Trang quản lý roles (Settings → Roles & Permissions) |
SwitchRole | Component đổi role hiện tại |
RoleModeSelect | Chọn chế độ role system (default / union) |
ACLSettingsUI | Tabs cấu hình quyền (System, Desktop Menu, ...) |
RolesManager | Registry cho plugin khác thêm tab vào roles screen |
Dependencies
| Package | Vai trò |
|---|---|
@digiforce-nc/acl | ACL engine (peer) |
@digiforce-nc/server | Server framework (peer) |
@digiforce-nc/database | Database ORM (peer) |
@digiforce-nc/actions | Action context types (peer) |
@digiforce-nc/cache | Cache role data (peer) |
@digiforce-nc/client | Client framework (peer) |