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 scoped-RBAC CASBIN_RBAC_DOMAIN_SCOPED_MODEL, do @venizia/ignis core cung cấp tại .../enforcers/models/rbac-domain.model.ts:

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

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

[role_definition]
g = _, _, _
g2 = _, _
g3 = _, _
g4 = _, _
g5 = _, _

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

[matchers]
m = g(r.sub, p.sub, r.dom) && (p.dom == "SYSTEM_WIDE" || (p.dom == "ANY_MEMBER" && g2(r.sub, r.dom)) || g3(r.dom, p.dom)) && (objectMatch(r.obj, p.obj) || g4(r.obj, p.obj)) && g5(r.act, p.act)
Quan hệCasbinMang
ggrouping roleassign_role (User→Role) + role_inherits (Role→Role), có domain
g2membershipjoin_domain (User→Merchant/Organizer) - trục dom
g3lồng domaindomain_inherits (Merchant ⊂ Organizer, Branch ⊂ Company)
g4lồng resourceresource_inherits (cây obj, vd OrderItem ⊂ Order)
g5lattice actionaction_inherits (manage ⊃ read/write/execute; write ⊃ create/update/delete)
TokenÝ nghĩa
subSubject - User_<id> hoặc Role_<id>
domDomain của request - Merchant_<id> từ x-merchant-id (hoặc không có)
objObject - permission/resource code, vd commerce.product.create
actAction - read / write / execute / manage / create / update / delete
eftEffect - allow (mặc định) / deny

Effect là default-DENY (allow-and-deny): request cần một allow khớp không deny nào khớp, nên một deny tường minh override mọi allow.

Domain scope của một grant (cột dom trên dòng p) là một trong:

ScopeKhớp
SYSTEM_WIDEmọi domain, bỏ qua membership
ANY_MEMBERmọi domain subject đã join qua g2
<Type>_<id>domain đó, cộng mọi child lồng qua g3

Chi tiết runtime. Cách ScopedCasbinAdapter nạp cạnh của một principal mỗi request, resolve domain từ x-merchant-id, role bypass và cache enforcer Redis được mô tả ở Casbin Authorization.

Các Variant PolicyDefinition

PolicyDefinition là bảng cạnh duy nhất - nó thay thế UserRole, PermissionMapping, và UserMapping. Mỗi row là một trong tám variant; ScopedCasbinAdapter lọc các row của một principal và emit Casbin line tương ứng.

VariantSubject → TargetCasbinLine emit ra
grantRole|User → Permissionpp, <Role|User>_<id>, <SYSTEM_WIDE|ANY_MEMBER|Type_id>, <objectCode>, <action>, <allow|deny>
assign_roleUser → Rolegg, User_<id>, Role_<id>, <domain|*> (domain null ⇒ *)
join_domainUser → Merchant|Organizerg2g2, User_<id>, <Type>_<domainId>
role_inheritsRole → Role (DAG)gg, Role_<child>, Role_<parent>, *
domain_inheritslồng domaing3g3, <Type>_<childId>, <Type>_<parentId>
resource_inheritslồng resource(obj)g4g4, <childCode>, <parentCode>
action_inheritslattice actiong5g5, <childAction>, <parentAction>
merchant_roleRole khả dụng trong một Merchant-không - metadata UI của @nx/core; adapter không bao giờ đọc

Tên app-facing nằm ở @nx/core common/policy-variant.ts (PolicyVariants), bọc quanh AuthorizationPolicyVariants của framework.

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 (từ IGNIS AuthorizationRoles)
900_adminAdminQuản Trị Viên900System (từ IGNIS)
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.

GUEST (001_guest, priority 1) là tầng chưa đăng nhập. Grant của nó dùng domain scope SYSTEM_WIDE (khớp mọi domain), nên route onboarding/tra-cứu pre-merchant chạy được trước khi chọn merchant.

Phân cấp Role

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

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 priority101 - 499 (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 (có một row assign_role tham chiếu)409
PermissionKhông thể xoá nếu đã cấp cho role/user (có một row grant tham chiếu)409

Cascade khi Xoá Role

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

  1. Xoá tất cả row grant: Role → Permission
  2. Xoá tất cả row merchant_role: metadata Role ↔ Merchant khả dụng
  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ỉ row grant trực tiếp (User → Permission)
  • GET /policy-definitions/users/{id}/permissions?mode=inherit → chuỗi assign_rolegrant (User → Role → Permission)
  • Mặc định: inherit

PolicyDefinition Services

ServiceQuản lý
RolePolicyDefinitionServiceRole↔Permission (grant), User↔Role (assign_role)
UserPolicyDefinitionServiceRole, permission, organizer, merchant của user (đọc)
OrganizerPolicyDefinitionServiceUser↔Organizer / User↔Merchant (join_domain)
PermissionPolicyDefinitionServiceUser↔Permission (grant, 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
ownerOwner123500_organizer-owner
employeeEmployee123100_employee

Seed Permission Cross-Package

Mỗi service package tự seed permission của mình qua migration process (*-seed-permissions, alwaysRun: true) để catalog luôn đồng bộ với code.

Grant theo role là coarse: seed-role-grants.ts (COARSE_MODULE_GRANTS) cấp cho mỗi fixed role một grant cho mỗi module nó được chạm tới, nhắm tới node resource của module. Lattice action (g5 action_inherits) và lồng resource (g4 resource_inherits) sau đó phủ mọi subject/operation bên dưới - vd OWNER nhận manage, còn CASHIER/EMPLOYEE nhận action hẹp hơn theo module. Grant owner/cashier/employee dùng domain scope ANY_MEMBER; guest dùng SYSTEM_WIDE. Super Admin / Admin / Operator được bypass enforcement nên không cần grant.

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 identity.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.