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:
[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ệ | Casbin | Mang |
|---|---|---|
g | grouping role | assign_role (User→Role) + role_inherits (Role→Role), có domain |
g2 | membership | join_domain (User→Merchant/Organizer) - trục dom |
g3 | lồng domain | domain_inherits (Merchant ⊂ Organizer, Branch ⊂ Company) |
g4 | lồng resource | resource_inherits (cây obj, vd OrderItem ⊂ Order) |
g5 | lattice action | action_inherits (manage ⊃ read/write/execute; write ⊃ create/update/delete) |
| Token | Ý nghĩa |
|---|---|
sub | Subject - User_<id> hoặc Role_<id> |
dom | Domain của request - Merchant_<id> từ x-merchant-id (hoặc không có) |
obj | Object - permission/resource code, vd commerce.product.create |
act | Action - read / write / execute / manage / create / update / delete |
eft | Effect - allow (mặc định) / deny |
Effect là default-DENY (allow-and-deny): request cần một allow khớp và 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:
| Scope | Khớp |
|---|---|
SYSTEM_WIDE | mọi domain, bỏ qua membership |
ANY_MEMBER | mọi domain subject đã join qua g2 |
<Type>_<id> | domain đó, cộng mọi child lồng qua g3 |
Chi tiết runtime. Cách
ScopedCasbinAdapternạ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.
| Variant | Subject → Target | Casbin | Line emit ra |
|---|---|---|---|
grant | Role|User → Permission | p | p, <Role|User>_<id>, <SYSTEM_WIDE|ANY_MEMBER|Type_id>, <objectCode>, <action>, <allow|deny> |
assign_role | User → Role | g | g, User_<id>, Role_<id>, <domain|*> (domain null ⇒ *) |
join_domain | User → Merchant|Organizer | g2 | g2, User_<id>, <Type>_<domainId> |
role_inherits | Role → Role (DAG) | g | g, Role_<child>, Role_<parent>, * |
domain_inherits | lồng domain | g3 | g3, <Type>_<childId>, <Type>_<parentId> |
resource_inherits | lồng resource(obj) | g4 | g4, <childCode>, <parentCode> |
action_inherits | lattice action | g5 | g5, <childAction>, <parentAction> |
merchant_role | Role 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á.
| Identifier | Name EN | Name VI | Priority | Scope |
|---|---|---|---|---|
999_super-admin | Super Admin | Siêu Quản Trị Viên | 999 | System (từ IGNIS AuthorizationRoles) |
900_admin | Admin | Quản Trị Viên | 900 | System (từ IGNIS) |
600_operator | Operator | Vận Hành Viên | 600 | System |
500_organizer-owner | Organizer Owner | Chủ Doanh Nghiệp | 500 | Organization |
110_cashier | Cashier | Thu Ngân | 110 | Merchant |
100_employee | Employee | Nhân Viên | 100 | Merchant |
010_customer | Customer | Khách Hàng | 10 | Customer |
001_guest | Guest | Khách | 1 | Global |
CASHIER là role nhân sự cấp merchant (cùng tier EMPLOYEE). Priority
110nằ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 scopeSYSTEM_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
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ắc | Giá trị |
|---|---|
| Khoảng priority | 101 - 499 (RolePriorities.MIN / MAX) |
| Type | CUSTOM |
| Identifier | Tự sinh: {paddedPriority}_{kebabCase(name.en)} |
| Tính duy nhất | Theo 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ạo | Scope được phép |
|---|---|
| User hệ thống (SUPER_ADMIN, ADMIN, OPERATOR) | Bất kỳ: system / organizer / merchant |
| Organizer Owner | Organizer 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[] và 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) →403khi update hoặc delete
Ràng buộc Xoá
| Entity | Ràng buộc | Lỗi |
|---|---|---|
| Role | Không thể xoá nếu đã gán user (có một row assign_role tham chiếu) | 409 |
| Permission | Khô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á:
- Xoá tất cả row
grant: Role → Permission - Xoá tất cả row
merchant_role: metadata Role ↔ Merchant khả dụng - Soft-delete entity Role
Validation Permission
| Field | Ràng buộc |
|---|---|
action | Phải là AuthorizationActions hợp lệ: create, read, update, delete, execute |
scope | Phải là PolicyDomains hợp lệ: SYSTEM, ORGANIZER, MERCHANT |
subject | Phải là một authorize model principal đã đăng ký (từ MetadataRegistry của IGNIS) |
code | Duy nhất toàn cục |
Tham chiếu API
Roles - /roles
| Method | Path | Mô tả |
|---|---|---|
GET | /roles | List (phân trang) |
GET | /roles/count | Count |
GET | /roles/:id | Lấy theo ID |
POST | /roles | Tạo role custom |
PATCH | /roles/:id | Cập nhật role |
DELETE | /roles/:id | Soft-delete (kèm guard) |
CreateRoleRequest:
| Field | Type | Bắt buộc |
|---|---|---|
name | { en, vi } | Yes |
description | { en, vi } | No |
priority | number (100-500) | Yes |
status | enum | No |
organizer | { id } | No (scope) |
merchant | { id } | No (scope) |
Permissions - /permissions
| Method | Path | Mô tả |
|---|---|---|
GET | /permissions | List |
GET | /permissions/count | Count |
GET | /permissions/:id | Lấy theo ID |
POST | /permissions | Tạo |
PATCH | /permissions/:id | Cập nhật (code bất biến) |
DELETE | /permissions/:id | Soft-delete (kèm check cấp phép) |
CreatePermissionRequest:
| Field | Type | Bắt buộc |
|---|---|---|
code | string | Yes (duy nhất toàn cục) |
name | { en, vi } | Yes |
description | { en, vi } | No |
subject | string | Yes |
action | string | Yes |
scope | string | Yes |
parentId | string | No |
Policy Definitions - /policy-definitions
CRUD cơ bản (chỉ đọc):
| Method | Path | Mô tả |
|---|---|---|
GET | /policy-definitions | List (phân trang, có filter) |
GET | /policy-definitions/:id | Lấy theo ID |
Sub-endpoint của Role - /policy-definitions/roles/{roleId}/{type}:
| Method | Path | type | Mô tả |
|---|---|---|---|
GET | .../roles/{id}/permissions | permissions | List permission của role |
POST | .../roles/{id}/permissions | permissions | Cấp/thu hồi permission |
GET | .../roles/{id}/users | users | List user của role |
POST | .../roles/{id}/users | users | Gán/gỡ user |
Sub-endpoint của User - /policy-definitions/users/{userId}/{type}:
| Method | Path | type | Mô tả |
|---|---|---|---|
GET | .../users/{id}/roles | roles | List role của user |
GET | .../users/{id}/permissions | permissions | List permission của user (query: mode=direct|inherit) |
GET | .../users/{id}/organizers | organizers | List organizer của user |
GET | .../users/{id}/merchants | merchants | List merchant của user |
POST | .../users/{id}/{type} | any | Cấp/thu hồi target |
Sub-endpoint của Organizer/Merchant:
| Method | Path | Mô 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
// 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
{ granted?: number, revoked?: number, skipped?: number }Phân giải Permission
GET /policy-definitions/users/{id}/permissions?mode=direct→ chỉ rowgranttrực tiếp (User → Permission)GET /policy-definitions/users/{id}/permissions?mode=inherit→ chuỗiassign_role→grant(User → Role → Permission)- Mặc định:
inherit
PolicyDefinition Services
| Service | Quản lý |
|---|---|
RolePolicyDefinitionService | Role↔Permission (grant), User↔Role (assign_role) |
UserPolicyDefinitionService | Role, permission, organizer, merchant của user (đọc) |
OrganizerPolicyDefinitionService | User↔Organizer / User↔Merchant (join_domain) |
PermissionPolicyDefinitionService | User↔Permission (grant, trực tiếp) |
BasePolicyDefinitionService | Validation chung chống privilege escalation |
Luồng Authorization JWT
Output của useRequestContext()
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 ADMINTích hợp Frontend
Decode JWT
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
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
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)
| Identifier | Priority | Type |
|---|---|---|
999_super-admin | 999 | SYSTEM |
900_admin | 900 | SYSTEM |
600_operator | 600 | SYSTEM |
500_organizer-owner | 500 | SYSTEM |
110_cashier | 110 | SYSTEM |
100_employee | 100 | SYSTEM |
010_customer | 10 | SYSTEM |
001_guest | 1 | SYSTEM |
User Mặc định (0002)
| Username | Password | Role Identifier |
|---|---|---|
superadmin | Superadmin123 | 999_super-admin |
admin | Admin123 | 900_admin |
owner | Owner123 | 500_organizer-owner |
employee | Employee123 | 100_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.
| Package | Tiền tố Permission | Ví dụ Code |
|---|---|---|
@nx/identity | identity.* | identity.user.create, identity.role.update |
@nx/commerce | commerce.* | commerce.product.create, commerce.merchant.read |
@nx/sale | sale.* | sale.order.create, sale.check.read |
@nx/finance | finance.* | finance.wallet.create, finance.transaction.read |
@nx/inventory | inventory.* | inventory.stock.update, inventory.purchase-order.create |
@nx/payment | payment.* | payment.webhook-config.create |
@nx/pricing | pricing.* | pricing.fare.create, pricing.tax.read |
@nx/ledger | ledger.* | ledger.generate, ledger.download |
@nx/signal | signal.* | 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
- Authentication - Sinh JWT khi sign-in
- User Management - Tạo user kèm gán role
- Customer Management - Role Customer
- MFA & OTP - Xác thực dựa trên OTP, rate limit
- Identity Overview - Kiến trúc