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 authtrướ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.currentRolectx.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 listget đượ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.