Bitwise Operations & Bitmask: Quản lý Permissions/RBAC gọn nhẹ và an toàn
Hướng dẫn dùng Bitwise Operations và Bitmask cho Permissions/RBAC: TypeScript Number/BigInt, database BIGINT, middleware, migration, indexing và giới hạn bảo mật.
ShanverseUpdated 12 min read— viewsLoading views
Data Structures
Authorization
Ý tưởng cốt lõi
Mỗi permission chiếm đúng một bit:
Một user có READ + UPDATE + PUBLISH:
Thay vì nhiều cột boolean hoặc nhiều row mapping, ta có thể lưu permissions = 21.
Các phép toán phải nhớ
Trong authorization, XOR hiếm khi phù hợp vì gọi lại cùng thao tác sẽ thu hồi quyền. API “grant” và “revoke” rõ ràng thường an toàn hơn “toggle”.
Permission registry bằng TypeScript Number
Các bit position là một phần của data contract. Sau khi đã lưu production:
Comments
Không đổi thứ tự.
Không tái sử dụng bit đã deprecated.
Chỉ thêm bit mới ở vị trí chưa dùng.
Version hóa registry và migration.
Nếu đổi DeleteArticle từ bit 3 sang bit 4, toàn bộ integer cũ đổi nghĩa và có thể cấp sai quyền.
Helper functions an toàn
hasAll và hasAny không giống nhau
Nhầm hai hàm có thể tạo privilege escalation hoặc chặn nhầm user hợp lệ.
Trường hợp required mask bằng 0
Đây là tính chất toán học hợp lý: không yêu cầu quyền nào. Nhưng middleware nên xác nhận route không vô tình truyền 0 do config thiếu.
JavaScript Bitwise có giới hạn 32-bit
Đây là bẫy quan trọng nhất.
Các bitwise operator trên number chuyển operand thành signed 32-bit integer:
Do đó:
Không dùng Number bitmask cho hàng chục/hàng trăm permission mà không thiết kế rõ.
Nếu muốn mask không âm và tránh sign bit, thực tế chỉ nên dùng bit 0..30.
>>> 0 chuyển kết quả sang unsigned 32-bit nhưng không tạo thêm bit.
JSON/DB vẫn phải thống nhất signed/unsigned semantics.
Nếu cần hơn khoảng 31 cờ, dùng bigint, nhiều word/chunk, BitSet, hoặc mô hình quan hệ.
BigInt Bitmask
bigint hỗ trợ bitwise với độ rộng tùy ý theo nhu cầu bộ nhớ:
Không được trộn number và bigint:
BigInt cũng không có unsigned right shift >>>.
BigInt và JSON
JSON.stringify() không serialize BigInt mặc định:
Serialize thành decimal hoặc hex string:
Với API/JWT, document schema rõ:
Client không được tự khai báo mask để server tin tưởng. Server phải lấy quyền từ nguồn tin cậy hoặc xác thực token đã ký và kiểm tra freshness/revocation policy.
Xây Role từ Permission Mask
RBAC gồm:
User được gán Role.
Role chứa Permissions.
Authorization kiểm tra permission cần thiết.
User có nhiều role có thể union:
Role ID khác Permission Mask
Không nên dùng bitmask role để thay cho permission mask nếu role thay đổi thường xuyên. Permission mask nên biểu diễn capability ổn định; role là bundle có tên và metadata.
Nếu thay quyền của Editor, user gán role qua quan hệ sẽ nhận policy mới ngay. Nếu copy materialized mask vào từng user, cần recompute/invalidate mọi user liên quan.
Middleware kiểm tra quyền
401: chưa xác thực.
403: đã xác thực nhưng thiếu quyền.
Authorization phải chạy ở server/API; ẩn button ở frontend chỉ là UX.
Permission không chỉ là một bit global
Bitmask DeleteArticle không trả lời:
Xóa article nào?
Có thuộc tenant hiện tại không?
User có phải owner không?
Resource đang ở trạng thái nào?
Có được phép trong giờ làm việc hoặc region này không?
Cần kết hợp RBAC với scope/ABAC:
Đừng để global bit vô tình vượt qua tenant/resource boundary.
Allow và Deny Masks
Một số hệ thống cần explicit deny:
Nhưng “deny wins” phải là policy được định nghĩa rõ cho:
Nhiều role.
Group inheritance.
Resource override.
Tenant hierarchy.
Permission không biết trong version cũ.
Hai integer không tự giải quyết policy conflict. Nếu semantics phức tạp, policy engine hoặc relational rule model dễ audit hơn.
Lưu trong Database
Ví dụ PostgreSQL với BIGINT signed 64-bit:
Với signed BIGINT không âm, dùng tối đa 63 bit (0..62). Bit sign không nên dùng nếu muốn query/serialize đơn giản.
Cập nhật atomic
Grant:
Revoke:
Đây là read-modify-write atomic trong một statement và tránh lost update tốt hơn việc đọc mask về app, sửa rồi ghi đè. Vẫn cần transaction/locking phù hợp nếu update liên quan nhiều bảng hoặc audit log.
Cú pháp, kiểu bitwise và signedness khác nhau giữa database. Kiểm tra engine/driver và bind parameter type thực tế.
Query user có tất cả quyền
Có ít nhất một quyền:
“Tiết kiệm DB” có thật không?
Bitmask có thể giảm:
Số cột boolean.
Số row trong join table.
Payload/cache size.
Chi phí check permission đã materialize.
Nhưng không phải luôn giảm tổng chi phí hệ thống:
Predicate bitwise thường không dùng B-tree index thông thường hiệu quả.
Query “ai có permission X?” có thể cần full scan, expression index hoặc generated column.
Audit và migration khó hơn.
Permission metadata vẫn cần registry.
Role changes có thể buộc recompute mask.
Join table thường compress/index tốt và rõ nghĩa.
Nếu workload chủ yếu là “load một membership rồi check nhiều quyền”, bitmask rất phù hợp. Nếu workload thường xuyên query/report theo permission hoặc permission thay đổi động, bảng quan hệ có thể tốt hơn.
Hoặc duy trì bảng quan hệ chuẩn làm source of truth và mask chỉ là cache/materialized projection.
Đừng thêm index cho mọi bit một cách mù quáng; đo query plan và workload.
Kiến trúc Hybrid khuyến nghị
Một mô hình thực tế:
Quan hệ role/permission là source of truth và audit được.
Mask được tính để check nhanh trong request/cache/token.
Có permissionsVersion hoặc policy revision.
Role thay đổi sẽ invalidate/recompute mask.
Cách này giữ khả năng quản trị nhưng vẫn có hot-path nhanh.
Migration và Versioning
Quy tắc registry
Persist bit number, không chỉ giá trị decimal.
Không tái sử dụng bit cũ vì token/cache/backup có thể còn dữ liệu.
Deploy code hiểu bit mới trước khi cấp bit mới.
Version token và record.
Reject hoặc mask unknown bits theo trust boundary.
Có backfill và rollback plan.
Unknown bits
Input từ client có unknown bits phải bị từ chối, không chỉ im lặng giữ lại:
Trong migration nội bộ, có thể cần forward compatibility khác, nhưng phải có policy rõ.
JWT và Cache Staleness
Đưa permission mask vào JWT giúp giảm DB lookup nhưng tạo stale authorization:
Admin thu hồi quyền nhưng token cũ vẫn còn hiệu lực.
Role thay đổi nhưng cache chưa invalidate.
Service cũ không hiểu bit mới.
Giải pháp:
Access token ngắn hạn.
permissionsVersion/policyRevision và server-side comparison.
Revocation list cho sự kiện nghiêm trọng.
Re-check DB cho action nhạy cảm.
Event-driven cache invalidation.
Không dùng refresh token như authorization proof cho resource action.
Audit log
Không chỉ log mask decimal:
Tên permission, bit position, before/after và registry version giúp điều tra sau này khi schema đã thay đổi.
Security checklist
Server là authority; không tin mask từ UI.
Kiểm tra tenant/resource scope ngoài bit global.
Phân biệt hasAll và hasAny.
Không dùng XOR cho grant/revoke API.
Reject unknown bits tại trust boundary.
Không tái sử dụng bit deprecated.
Number mask không vượt giới hạn signed 32-bit của JavaScript.
BigInt serialize thành string và validate chặt.
DB type/signedness thống nhất với app.
Atomic update và audit nằm trong transaction phù hợp.
Token/cache có version và revoke strategy.
Deny/inheritance semantics được document.
Action nhạy cảm có audit và step-up controls nếu cần.
Property-based tests hữu ích
Các tính chất nên luôn đúng:
Ngoài ra test:
Mask 0.
Từng permission riêng lẻ.
Tất cả permissions.
Duplicated role grants.
Unknown bit.
Highest supported bit.
Number/BigInt serialization round trip.
Concurrent grant/revoke.
Role update và cache invalidation.
Cross-tenant access denial.
Khi nào không nên dùng Bitmask?
Không nên dùng làm source of truth duy nhất khi:
Permission được tạo/xóa động bởi khách hàng.
Có hàng trăm/hàng nghìn quyền.
Cần query/report theo permission thường xuyên.
Có resource-level grant phức tạp.
Policy phụ thuộc context, thời gian, ownership hoặc attribute.
Cần giải thích “vì sao được phép” chi tiết.
Nhiều service/ngôn ngữ/database khó thống nhất integer semantics.
Khi đó, dùng role/permission tables, policy engine, ACL, ABAC hoặc capability model; có thể thêm bitmask như cache tối ưu.
Kết luận
Bitmask biến tập permission thành một integer:
Đây là cách check quyền nhanh và compact khi permission catalog nhỏ, ổn định. Nhưng “tiết kiệm DB” chỉ đúng với workload phù hợp. JavaScript Number bị giới hạn signed 32-bit; BigInt cần serialization riêng; database có giới hạn/signedness và indexing khác nhau.
Thiết kế production tốt thường dùng relational RBAC làm source of truth, bitmask làm materialized hot-path, registry bất biến có version, authorization ở server, scope theo tenant/resource và audit đầy đủ.
1constcanEdit= Permission.ReadArticle | Permission.UpdateArticle;23hasAll(userMask, canEdit); // cần cả READ và UPDATE4hasAny(userMask, canEdit); // chỉ cần một trong hai
Codetypescript
1hasAll(mask, 0); // luôn true
Codetypescript
11<<30; // 107374182421<<31; // -214748364831<<32; // 1, vì shift count được xử lý modulo 32
Hướng dẫn Fast & Slow Pointers và Floyd’s Cycle Detection bằng TypeScript: phát hiện cycle, tìm entry, cycle length, functional graph và các giới hạn thực tế.