Bỏ qua, đến nội dung

Auth & Permission ​

Request pipeline ​

Mỗi HTTP request đi qua chuỗi middleware theo thứ tự:

setCurrentRole - xác định role ​

Middleware này chạy sau auth và trước acl:

  1. Đọc header X-Role từ request.
  2. Load danh sách role của user (từ cache roles:{userId} hoặc DB).
  3. Áp dụng system role mode:
    • default → dùng role từ X-Role hoặc role default của user.
    • allow-use-union → nếu X-Role = __union__, merge tất cả role.
    • only-use-union → luôn merge tất cả role.
  4. Set ctx.state.currentRole và ctx.state.currentRoles.

checkQueryPermission - lọc truy vấn ​

Trước khi query data, middleware merge ACL filter vào query:

Ví dụ: role member có strategy view:own → filter tự động thêm { createdById: '{{ ctx.state.currentUser.id }}' } vào mọi query.

checkChangesWithAssociation - kiểm tra mutation ​

Trước create/update, middleware kiểm tra:

  • Field whitelist - chỉ cho phép sửa field mà role được grant.
  • Association sanitization - lọc bỏ association data ngoài quyền.

checkAssociationOperate - kiểm tra thao tác association ​

Trước add, set, remove, toggle trên association, kiểm tra quyền trên resource đích.

withACLMeta - bổ sung meta vào response ​

Khi client gửi header X-With-ACL-Meta, response list và get được bổ sung:

json
{
  "data": [...],
  "meta": {
    "allowedActions": {
      "view": [1, 2, 3],
      "update": [1, 2],
      "destroy": [1]
    }
  }
}

Client dùng meta này để ẩn/hiện nút Edit, Delete cho từng row.

Role hệ thống ​

root - superuser ​

  • Ẩn khỏi danh sách role trên UI (hidden: true).
  • Bypass tất cả kiểm tra quyền: acl.allow('*', '*', (ctx) => currentRoles.includes('root')).
  • Snippet: ui.*, pm, pm.* - truy cập toàn bộ UI và plugin settings.
  • Không thể gán role root cho user qua UI (rolesUsers.beforeSave chặn).

admin - quản trị viên ​

  • allowConfigure: true - truy cập trang cấu hình.
  • allowNewMenu: true - tạo menu page mới.
  • Strategy: create, view, update, destroy - toàn quyền CRUD trên mọi collection (theo strategy).
  • Snippet: ui.*, pm, pm.*.

member - thành viên ​

  • default: true - user mới tự động nhận role này.
  • allowNewMenu: true.
  • Strategy: view:own - chỉ xem bản ghi do mình tạo.
  • Snippet: !ui.*, !pm, !pm.* - phủ định → không truy cập UI config và plugin settings.

anonymous - khách (ẩn) ​

  • Middleware roles:list mặc định lọc bỏ anonymous trừ khi truyền showAnonymous.
  • Dùng cho request không xác thực (public pages).

Scope - phân quyền theo dữ liệu ​

Scope định nghĩa filter tự động áp dụng khi user truy cập data.

Scope mặc định ​

KeyTênFilterÝ nghĩa
allTất cả(trống)Truy cập mọi bản ghi
ownCủa mình{ createdById: '{{ ctx.state.currentUser.id }}' }Chỉ bản ghi do user tạo

Custom scope ​

Admin có thể tạo scope tùy chỉnh cho từng collection:

json
{
  "name": "Cùng phòng ban",
  "resourceName": "orders",
  "scope": {
    "departmentId": "{{ ctx.state.currentUser.departmentId }}"
  }
}

Template {{ ctx.state.currentUser.* }} được thay thế bằng giá trị thực tại runtime.

Cách scope áp dụng ​

Ví dụ thực tế ​

403 khi không có quyền ​

typescript
// Role member chỉ có strategy 'view' → cố create sẽ bị từ chối
await db.getRepository('roles').update({
  filterByTargetKey: 'member',
  values: { strategy: { actions: ['view'] } },
});

const memberAgent = await app.agent().login(memberUser);
const response = await memberAgent.resource('posts').create({
  values: { title: 'test' },
});
// response.statusCode === 403

Field-level filtering - 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: 'create', fields: ['title', 'description'] },
      { name: 'view', fields: ['title'] },
    ],
  },
});

await agent.resource('posts').create({
  values: { title: 'Hello', description: 'World' },
});

const listResp = await editorAgent.resource('posts').list();
const post = listResp.body.data[0];
// post.id          → có (PK luôn trả về)
// post.title       → 'Hello'
// post.description → undefined (bị lọc bởi ACL)

Scope template - chỉ xem bản ghi của mình ​

typescript
// Tạo scope "own" dùng template currentUser
const { body: { data: scope } } = await agent
  .resource('dataSourcesRolesResourcesScopes')
  .create({
    values: {
      name: 'own',
      scope: { createdById: '{{ ctx.state.currentUser.id }}' },
    },
  });

// 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 },
    ],
  },
});

// User chỉ thấy bản ghi do mình tạo
const listResp = await editorAgent.resource('posts').list();
// listResp.body.data chỉ chứa posts có createdById = editor.id

ACL meta - phân quyền UI từng row ​

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

const { meta } = response.body;
// meta.allowedActions.view = ['t1', 't2', 't3']
// meta.allowedActions.update = ['t1', 't2']
// meta.allowedActions.destroy = ['t3']

// Client dùng meta này ẩn/hiện nút Edit, Delete cho từng row

Cache ​

Plugin cache thông tin role để tránh query DB mỗi request:

Cache keyNội dungInvalidated khi
roles:{userId}Danh sách role name của userrolesUsers thay đổi
roles:{userId}:defaultRoleRole mặc địnhrolesUsers thay đổi

Events cache:del:roles cũng xóa cache role.

Bảo mật - lưu ý ​

  • root có quyền tuyệt đối - hạn chế số user, audit đăng nhập.
  • rolesUsers.beforeSave chặn gán role root qua API quan hệ (chống leo thang quyền).
  • Middleware destroy so sánh count giữa filter user gửi và filter sau merge ACL - ngăn xóa vượt phạm vi.
  • Fixed params bảo vệ role/scope/collection hệ thống khỏi bị xóa.
  • Snippet pm.acl.roles chỉ nên gán cho admin tin cậy.