Skip to content

RBAC & Policy Definitions v1.0.0

Entity Cốt lõi

Mô hình Authorization Casbin

Hệ RBAC dùng mô hình domain-aware của Casbin, định nghĩa trong src/security/casbin-model.ts:

ini
[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act, eft

[role_definition]
g = _, _, _

[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

[matchers]
m = g(r.sub, p.sub, r.dom) && keyMatch(r.dom, p.dom) && r.obj == p.obj && r.act == p.act
Thành phầnMô tả
subSubject — User_<id> hoặc Role_<id>
domDomain — Merchant_<id> trên membership; * (global) trên role-permission policy
objObject (resource/permission code đang truy cập)
actAction (create, read, update, delete, execute)
eftEffect (allow hoặc deny)
g = _, _, _Grouping role có domain: user → role TRONG merchant (scope per-merchant nằm ở đây)

Policy effect kết hợp allow/deny: request được phép nếu có ít nhất một rule allow match không deny nào match. Role permission emit domain-agnostic (p, Role, *, …); keyMatch nhận mọi domain, còn g-line ràng buộc merchant nào role áp dụng. Một domain matching function (keyMatch) được đăng ký trên g để membership global g, user, role, * khớp mọi domain.

Chi tiết runtime. Cách adapter nạp policy theo user, resolve domain theo merchant, HQ-owner expansion, role bypass và cache enforcer được mô tả ở Casbin Authorization.

Các Pattern PolicyDefinition

PolicyDefinition là bảng liên kết trung tâm — nó thay thế UserRole, PermissionMapping, và UserMapping bằng một mô hình linh hoạt duy nhất hỗ trợ cả gán group và cấp phép policy.

VariantSubject → TargetÝ nghĩaVí dụ
groupUser → RoleGán role cho userUser X có role "Operator"
groupUser → OrganizerMap user với orgUser X thuộc Org A
groupUser → MerchantMap user với merchantUser X làm việc tại Merchant B
groupRole → OrganizerScope role theo orgRole custom thuộc Org A
groupRole → MerchantScope role theo merchantRole custom cho Merchant B
policyRole → PermissionCấp permission cho role"Operator" có thể product.create
policyUser → PermissionCấp trực tiếp cho userUser X có thể order.delete

Role Hệ thống Cố định

Tám role được seed khi migration (alwaysRun: true). Không thể sửa hoặc xoá.

IdentifierName ENName VIPriorityScope
999_super-adminSuper AdminSiêu Quản Trị Viên999System
900_adminAdminQuản Trị Viên900System
600_operatorOperatorVận Hành Viên600System
500_organizer-ownerOrganizer OwnerChủ Doanh Nghiệp500Organization
110_cashierCashierThu Ngân110Merchant
100_employeeEmployeeNhân Viên100Merchant
010_customerCustomerKhách Hàng10Customer
001_guestGuestKhách1Global

CASHIER là role nhân sự cấp merchant (cùng tier EMPLOYEE). Priority 110 nằm trong dải custom-role (101–499) — điều này được phép với role cố định; dải đó chỉ ràng buộc role CUSTOM do người dùng tạo. CASHIER hiện có cùng tập permission như EMPLOYEE ở mọi package (xem Seed Permission Cross-Package).

GUEST (001_guest, priority 1) là role tầng global (chưa đăng nhập, trong GLOBAL_ROLE_IDENTIFIERS), không có permission backend — giống Customer nhưng phạm vi global thay vì theo merchant.

Phân cấp Role

Priority 999 ┌─────────────────┐
             │  SUPER_ADMIN    │ ← full system access
             └─────────────────┘
Priority 600 ┌─────────────────┐
             │  OPERATOR       │ ← system operations
             └─────────────────┘
Priority 900 ┌─────────────────┐
             │  ADMIN          │ ← administration
             └─────────────────┘
Priority 500 ┌─────────────────┐
             │  OWNER          │ ← organizer scope
             └─────────────────┘
Priority 110 ┌─────────────────┐
             │  CASHIER        │ ← merchant scope (cố định, nằm trong dải custom)
             └─────────────────┘
Priority 100 ┌─────────────────┐
             │  EMPLOYEE       │ ← merchant scope
             └─────────────────┘
  100–500    ┌─────────────────┐
             │  CUSTOM ROLES   │ ← user-created
             └─────────────────┘
Priority 10  ┌─────────────────┐
             │  CUSTOMER       │ ← end user
             └─────────────────┘
Priority 1   ┌─────────────────┐
             │  GUEST          │ ← global, chưa đăng nhập (no perms)
             └─────────────────┘

Helper AppFixedRoles

typescript
AppFixedRoles.isDefaultRole(identifier)     // true if any of the 8 fixed roles
AppFixedRoles.isSystemUser(roles)           // true if SUPER_ADMIN, ADMIN, or OPERATOR
AppFixedRoles.isOrganizerOwner(roles)       // true if OWNER

Định dạng identifier: {priority:3 zero-padded}_{kebab-case-name} — sinh bởi AuthorizationRole.build() với dấu _ làm delimiter.

Role Tuỳ chỉnh

Quy tắcGiá trị
Khoảng priority101499 (RolePriorities.MIN / MAX)
TypeCUSTOM
IdentifierTự sinh: {paddedPriority}_{kebabCase(name.en)}
Tính duy nhấtTheo scope (toàn cục cho system, theo org/merchant cho scope)

Quy tắc Scope theo Người tạo

Role của Người tạoScope được phép
User hệ thống (SUPER_ADMIN, ADMIN, OPERATOR)Bất kỳ: system / organizer / merchant
Organizer OwnerOrganizer của mình hoặc các merchant của mình
User khác (Employee, v.v.)Chỉ merchant của mình

Quyền sở hữu được validate theo organizerIds[]merchantIds[] của người tạo từ JWT token.

Quy tắc Nghiệp vụ

Guard Priority

  • Không thể tạo/cập nhật/xoá role có priority bằng hoặc cao hơn
  • Không thể cấp/thu hồi role có priority bằng hoặc cao hơn qua PolicyDefinition
  • Ngăn privilege escalation

Bảo vệ Role Cố định

  • Role hệ thống (AppFixedRoles.DEFAULT_ROLE_IDENTIFIERS) → 403 khi update hoặc delete

Ràng buộc Xoá

EntityRàng buộcLỗi
RoleKhông thể xoá nếu đã gán user (tồn tại PolicyDef USER→ROLE)409
PermissionKhông thể xoá nếu đã cấp cho role/user (tồn tại PolicyDef với targetType=Permission)409

Cascade khi Xoá Role

Khi một role được phép xoá bị xoá:

  1. Xoá tất cả bản ghi policy: ROLE→PERMISSION
  2. Xoá tất cả bản ghi group: ROLE→ORGANIZER/MERCHANT (map scope)
  3. Soft-delete entity Role

Validation Permission

FieldRàng buộc
actionPhải là AuthorizationActions hợp lệ: create, read, update, delete, execute
scopePhải là PolicyDomains hợp lệ: SYSTEM, ORGANIZER, MERCHANT
subjectPhải là một authorize model principal đã đăng ký (từ MetadataRegistry của IGNIS)
codeDuy nhất toàn cục

Tham chiếu API

Roles — /roles

MethodPathMô tả
GET/rolesList (phân trang)
GET/roles/countCount
GET/roles/:idLấy theo ID
POST/rolesTạo role custom
PATCH/roles/:idCập nhật role
DELETE/roles/:idSoft-delete (kèm guard)

CreateRoleRequest:

FieldTypeBắt buộc
name{ en, vi }Yes
description{ en, vi }No
prioritynumber (100–500)Yes
statusenumNo
organizer{ id }No (scope)
merchant{ id }No (scope)

Permissions — /permissions

MethodPathMô tả
GET/permissionsList
GET/permissions/countCount
GET/permissions/:idLấy theo ID
POST/permissionsTạo
PATCH/permissions/:idCập nhật (code bất biến)
DELETE/permissions/:idSoft-delete (kèm check cấp phép)

CreatePermissionRequest:

FieldTypeBắt buộc
codestringYes (duy nhất toàn cục)
name{ en, vi }Yes
description{ en, vi }No
subjectstringYes
actionstringYes
scopestringYes
parentIdstringNo

Policy Definitions — /policy-definitions

CRUD cơ bản (chỉ đọc):

MethodPathMô tả
GET/policy-definitionsList (phân trang, có filter)
GET/policy-definitions/:idLấy theo ID

Sub-endpoint của Role/policy-definitions/roles/{roleId}/{type}:

MethodPathtypeMô tả
GET.../roles/{id}/permissionspermissionsList permission của role
POST.../roles/{id}/permissionspermissionsCấp/thu hồi permission
GET.../roles/{id}/usersusersList user của role
POST.../roles/{id}/usersusersGán/gỡ user

Sub-endpoint của User/policy-definitions/users/{userId}/{type}:

MethodPathtypeMô tả
GET.../users/{id}/rolesrolesList role của user
GET.../users/{id}/permissionspermissionsList permission của user (query: mode=direct|inherit)
GET.../users/{id}/organizersorganizersList organizer của user
GET.../users/{id}/merchantsmerchantsList merchant của user
POST.../users/{id}/{type}anyCấp/thu hồi target

Sub-endpoint của Organizer/Merchant:

MethodPathMô tả
GET.../organizers/{id}List user của organizer
POST.../organizers/{id}Gán/gỡ user
GET.../commerces/{id}List user của merchant
POST.../commerces/{id}Gán/gỡ user

Request Body cho Grant/Revoke

typescript
// For role and user targets
ManageRoleTargetsRequest / ManageUserTargetsRequest {
  action: 'grant' | 'revoke',
  ids: string[],       // min 1
  domain?: string       // optional scope label
}

// For organizer/merchant targets
ManageGroupTargetsRequest {
  action: 'grant' | 'revoke',
  ids: string[]         // min 1
}

Response

typescript
{ granted?: number, revoked?: number, skipped?: number }

Phân giải Permission

  • GET /policy-definitions/users/{id}/permissions?mode=direct → chỉ policy USER→PERMISSION
  • GET /policy-definitions/users/{id}/permissions?mode=inherit → chuỗi USER→ROLE→PERMISSION
  • Mặc định: inherit

PolicyDefinition Services

ServiceQuản lý
RolePolicyDefinitionServiceRole↔Permission (POLICY), Role↔User (GROUP), User↔Role
UserPolicyDefinitionServiceRole, permission, organizer, merchant của user (đọc)
OrganizerPolicyDefinitionServiceUser↔Organizer (GROUP), User↔Merchant (GROUP)
PermissionPolicyDefinitionServiceUser↔Permission (POLICY, cấp trực tiếp)
BasePolicyDefinitionServiceValidation chung chống privilege escalation

Luồng Authorization JWT

Output của useRequestContext()

typescript
const { currentUser, userId, roles, isAlwaysAllowed } = useRequestContext();

currentUser.priority.highest    // max priority across all roles
currentUser.priority.lowest     // min priority
currentUser.organizers          // [{ id: "org1" }, ...]
currentUser.merchants           // [{ id: "mer1" }, ...]

roles           // string[] of identifiers, e.g. ["999_super-admin"]
isAlwaysAllowed // true if SUPER_ADMIN or ADMIN

Tích hợp Frontend

Decode JWT

typescript
const payload = decodeJwt(token);
const roles = payload.roles;  // Array<{ id, identifier, priority }>
const organizerIds = payload.organizerIds?.split(',').filter(Boolean) ?? [];
const merchantIds = payload.merchantIds?.split(',').filter(Boolean) ?? [];

Kiểm tra Quyền

typescript
const isAdmin = roles.some(r => ['999_super-admin', '900_admin'].includes(r.identifier));
const isSystem = roles.some(r => ['999_super-admin', '900_admin', '600_operator'].includes(r.identifier));
const hasOrg = (orgId: string) => organizerIds.includes(orgId);

UI Nhận biết Permission

typescript
const perms = await fetch(`/policy-definitions/users/${userId}/permissions`);
const codes = new Set(perms.map(p => p.code));

const canCreateProduct = codes.has('commerce.product.create');

Dữ liệu Seed

Role Cố định (0001)

IdentifierPriorityType
999_super-admin999SYSTEM
900_admin900SYSTEM
600_operator600SYSTEM
500_organizer-owner500SYSTEM
110_cashier110SYSTEM
100_employee100SYSTEM
010_customer10SYSTEM
001_guest1SYSTEM

User Mặc định (0002)

UsernamePasswordRole Identifier
superadminSuperadmin123999_super-admin
adminAdmin123900_admin
ownerOwner123 (priority 500)500_organizer-owner
employeeEmployee123100_employee

Seed Permission Cross-Package

Mỗi service package tự seed permission của mình qua migration process (*-seed-permissions). Các migration này chạy với alwaysRun: true để đảm bảo permission luôn đồng bộ với thay đổi code.

Theo baseline "lenient", mỗi package cấp toàn bộ tập permission của nó cho OWNER, EMPLOYEECASHIER (CASHIER mirror EMPLOYEE) để app chạy không vướng 403; việc siết quyền theo từng role làm sau qua policy-definition API. Muốn cấp quyền cho một fixed role mới, thêm identifier của nó vào map grantsByRoleIdentifier trong các process *-seed-role-permissions liên quan.

PackageTiền tố PermissionVí dụ Code
@nx/identityidentity.*identity.user.create, identity.role.update
@nx/commercecommerce.*commerce.product.create, commerce.merchant.read
@nx/salesale.*sale.order.create, sale.check.read
@nx/financefinance.*finance.wallet.create, finance.transaction.read
@nx/inventoryinventory.*inventory.stock.update, inventory.purchase-order.create
@nx/paymentpayment.*payment.webhook-config.create
@nx/pricingpricing.*pricing.fare.create, pricing.tax.read
@nx/ledgerledger.*ledger.generate, ledger.download
@nx/signalsignal.*signal.client.read

Tất cả permission được lưu trong bảng public.Permission và có thể cấp cho role hoặc user qua PolicyDefinition.

Trang liên quan

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.