Bỏ qua, đến nội dung

Kiến trúc plugin-users

Tổng quan

plugin-users chịu trách nhiệm quản lý toàn bộ vòng đời người dùng trong hệ thống: định nghĩa collection users, cung cấp model xử lý dữ liệu nhạy cảm, tự động gắn thông tin audit (createdBy/updatedBy), đăng ký filter operator đặc biệt, và hỗ trợ đồng bộ user từ nguồn bên ngoài.

Sơ đồ kiến trúc

UserModel — desensitize

UserModel kế thừa Model từ @digiforce-nc/database và bổ sung method desensitize(). Method này duyệt tất cả field của collection, chỉ giữ lại những field không có hidden: true, loại bỏ dữ liệu nhạy cảm (như password, resetToken) trước khi trả về client.

typescript
// packages/plugins/@digiforce/plugin-users/src/server/models/UserModel.ts
export class UserModel extends Model {
  desensitize() {
    const { fields } = (this.constructor as typeof UserModel).collection;
    const result = (this.constructor as typeof UserModel).build({}, { isNewRecord: this.isNewRecord });
    for (const [name, value] of Object.entries(this.get())) {
      const field = fields.get(name);
      if (field && !field.options.hidden) {
        result.set(name, value);
      }
    }
    return result;
  }
}

Các field bị ẩn trong collection users: password (hidden: true) và resetToken (hidden: true).

Tự động gắn createdBy / updatedBy

Plugin lắng nghe hai event của database:

EventMục đích
afterDefineCollectionKhi collection được định nghĩa với createdBy: true hoặc updatedBy: true, tự động thêm field createdById (type context, createOnly: true) và relation createdBy (belongsTo → users)
field.afterAddKhi một field mới có interface createdBy hoặc updatedBy được thêm vào collection bất kỳ, inject field context tương ứng

Field context lấy giá trị từ state.currentUser.id, kiểu bigInt, có index. Field createdById chỉ ghi khi tạo mới (createOnly: true), trong khi updatedById cập nhật mỗi lần sửa.

typescript
// Ví dụ: collection orders tự động có createdBy
const Orders = db.collection({
  name: 'orders',
  createdBy: true,
  updatedBy: true,
  fields: [{ name: 'title', type: 'string' }],
});
// → orders tự động có: createdById, createdBy, updatedById, updatedBy

DB operators

Plugin đăng ký 3 operator tùy chỉnh trong beforeLoad():

OperatorLogicSử dụng
$isCurrentUserChuyển thành Op.eq với ctx.state.currentUser.id (fallback -1 nếu chưa đăng nhập)Lọc record thuộc user hiện tại
$isNotCurrentUserChuyển thành Op.ne với ctx.state.currentUser.idLọc record không thuộc user hiện tại
$isVarParse giá trị dạng template từ ctx.state, chuyển thành Op.eqFilter động theo biến hệ thống

Ví dụ sử dụng trong filter:

json
{
  "createdById": { "$isCurrentUser": true }
}

ACL — bảo vệ hệ thống

Plugin đăng ký fixedParams với ACL để bảo vệ dữ liệu quan trọng:

  • Không xóa được user id=1 (root user): action users:destroy luôn thêm filter id.$ne: 1
  • Không xóa được collection users: action collections:destroy luôn thêm filter name.$ne: 'users'
  • Action updateProfileupdateLang được phép cho tất cả user đã đăng nhập (loggedIn)
  • Toàn bộ action users:* nằm trong ACL snippet pm.users

UserDataSyncResource — đồng bộ bên ngoài

Khi plugin user-data-sync được kích hoạt, UserDataSyncResource đăng ký như một resource đồng bộ:

MethodHành vi
create()Tìm user theo matchKey (phone/email/username). Nếu tìm thấy → update, nếu không → tạo mới. Tự động lọc bỏ các field hệ thống (id, password, createdAt...)
update()Cập nhật user hiện có. Nếu isDeleted: true → xóa user (trừ khi user có role root). Nếu user không tồn tại → tạo lại

Tạo root user khi cài đặt

Trong method install(), plugin đọc biến môi trường để tạo user đầu tiên:

Biến môi trườngGiá trị mặc định
INIT_ROOT_EMAILadmin@digiforce.vn
INIT_ROOT_PASSWORDDigiM@t3
INIT_ROOT_DISPLAYNAMESuper Admin
INIT_ROOT_USERNAMEdigiforce

Sau khi tạo root user, plugin insert 3 UI schema cho profile form: admin create, admin edit, và user profile edit (uid: digiforce-user-profile-edit-form).

Xử lý password change

Khi field password thay đổi (event users.beforeUpdate), plugin thực hiện:

  1. Ghi lại thời điểm thay đổi vào passwordChangeTz (timestamp)
  2. Xóa cache roles của user (cache:del:roles)
  3. Xóa cache auth của user (cache:del:auth)

Điều này buộc user phải đăng nhập lại sau khi đổi mật khẩu, đảm bảo session cũ không còn hiệu lực.