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:
[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ần | Mô tả |
|---|---|
| sub | Subject — User_<id> hoặc Role_<id> |
| dom | Domain — Merchant_<id> trên membership; * (global) trên role-permission policy |
| obj | Object (resource/permission code đang truy cập) |
| act | Action (create, read, update, delete, execute) |
| eft | Effect (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 và 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.
| Variant | Subject → Target | Ý nghĩa | Ví dụ |
|---|---|---|---|
group | User → Role | Gán role cho user | User X có role "Operator" |
group | User → Organizer | Map user với org | User X thuộc Org A |
group | User → Merchant | Map user với merchant | User X làm việc tại Merchant B |
group | Role → Organizer | Scope role theo org | Role custom thuộc Org A |
group | Role → Merchant | Scope role theo merchant | Role custom cho Merchant B |
policy | Role → Permission | Cấp permission cho role | "Operator" có thể product.create |
policy | User → Permission | Cấp trực tiếp cho user | User 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á.
| Identifier | Name EN | Name VI | Priority | Scope |
|---|---|---|---|---|
999_super-admin | Super Admin | Siêu Quản Trị Viên | 999 | System |
900_admin | Admin | Quản Trị Viên | 900 | System |
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. 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, trongGLOBAL_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
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 (tồn tại PolicyDef USER→ROLE) | 409 |
| Permission | Khô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á:
- Xoá tất cả bản ghi
policy: ROLE→PERMISSION - Xoá tất cả bản ghi
group: ROLE→ORGANIZER/MERCHANT (map scope) - 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ỉ policy USER→PERMISSIONGET /policy-definitions/users/{id}/permissions?mode=inherit→ chuỗi USER→ROLE→PERMISSION- Mặc định:
inherit
PolicyDefinition Services
| Service | Quản lý |
|---|---|
RolePolicyDefinitionService | Role↔Permission (POLICY), Role↔User (GROUP), User↔Role |
UserPolicyDefinitionService | Role, permission, organizer, merchant của user (đọc) |
OrganizerPolicyDefinitionService | User↔Organizer (GROUP), User↔Merchant (GROUP) |
PermissionPolicyDefinitionService | User↔Permission (POLICY, cấp 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 (priority 500) | 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). 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, EMPLOYEE và CASHIER (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.
| 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 public.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