Skip to content

Validation & Activation

Pipeline Validation

POST /validation/validateValidationService.validate(opts). Yêu cầu authentication (JWT hoặc Basic); không có code permission riêng.

Request

ts
interface IValidateRequest {
  key: string;            // license key — bắt buộc
  fingerprint?: string;   // fingerprint thiết bị — kích hoạt xử lý slot khi có
  label?: string;         // tên hiển thị thiết bị
  platform?: string;      // định danh OS / platform
  ip?: string;            // route handler forward khi áp dụng
  userAgent?: string;     // route handler forward khi áp dụng
}

Schema body request (ValidateRequestSchema trong controllers/validation/definitions.ts) chỉ chấp nhận { key, fingerprint?, label?, platform? } từ client. ip / userAgent được điền bởi tầng controller, không phải client.

Pipeline

Bước 1 — Tìm license

licenseRepository.findOne({ filter: { where: { key } } }). Nếu null, trả LICENSE_NOT_FOUND ngay không làm gì thêm.

Bước 2 — Cổng status / ngày-bắt-đầu (checkLicenseStatus)

Điều kiệnResult code
LicenseStatuses.isInactive(license.status) (bất kỳ trong suspended, revoked, expired)LICENSE_${license.status.toUpperCase()}LICENSE_SUSPENDED, LICENSE_REVOKED, LICENSE_EXPIRED
new Date(license.startsAt) > new Date()LICENSE_NOT_STARTED

Bước 3 — Kiểm tra hết hạn (checkExpiration)

Ba nhánh, đánh giá với now:

Điều kiệnHiệu ứng
expiresAtnull (vĩnh viễn) HOẶC expiresAt >= nowTiếp tục, không grace
expiresAt < now graceExpiresAt > nowinGracePeriod = true, tiếp tục
Qua grace (hoặc không grace nào)Auto-expire

Nhánh auto-expire thực thi:

ts
const { count } = await licenseRepository.updateBy({
  data: { status: 'expired' },
  where: { id: license.id, status: 'activated' },
  options: { shouldReturn: false },
});

Mệnh đề where status = activated là guard race. Nếu hai request cùng phát hiện một license qua-grace, chỉ một trong chúng nhận count === 1. Cái thua:

  1. Đọc lại license với findLicense({ id })
  2. Trả LICENSE_${current.status} dựa trên bất kỳ cái nào cái thắng để lại

Cái thắng bổ sung:

  • Log một LicenseEvent expired
  • Gọi publishCertificate({ license: { ...license, status: 'expired' } }) để consumer thấy status mới

Bước 4 — Giải song song

Khi license đã qua status và hết hạn, hai query chạy qua Promise.all:

  1. resolveFeatures({ policyId, featuresOverride: license.override?.features ?? null })
  2. resolveActivationForValidation({ license, fingerprint, label, platform, ip, userAgent })

resolveActivationForValidation bản thân nạp policy và danh sách activation song song bên trong Promise.all riêng của nó.

Bước 5 — Slot activation (resolveActivationForValidation)

Logic slot bị bỏ qua hoàn toàn nếu thiếu fingerprint. Response khi đó mang activation: { id: null, used: <total>, limit: <policy hoặc override limit, hoặc null> } nhưng không tiêu một slot.

Nếu cung cấp fingerprint:

  1. Tìm một activation (licenseId, fingerprint) có sẵn trong danh sách đã nạp. Nếu thấy, tái dùng.
  2. Ngược lại gọi tryCreateActivation().

Tạo activation hai pha (tryCreateActivation)

PhaVì sao nó tồn tại
Fast path (không lock)Nếu giới hạn đã đạt ở count không-lock, ta có thể từ chối mà không trả phí cho một transaction. Nếu không có giới hạn nào, ta chỉ tạo hàng trực tiếp.
Slow path (có lock)Khi ta dưới giới hạn ở kiểm tra không-lock, ta lấy row lock LockStrengths.UPDATE trên license cha và re-count bên trong transaction. Cái này serialize các activation đồng thời với cùng license và ngăn bypass giới hạn.

Nếu re-count slow-path thấy license giờ ở hoặc vượt giới hạn, transaction rollback và caller nhận isLimitReached: true.

Khi isLimitReached, validate() short-circuit với:

json
{
  "valid": false,
  "code": "ACTIVATION_LIMIT_REACHED",
  "license": { ... },
  "features": {},
  "activation": { "id": null, "used": <currentCount>, "limit": <limit> }
}

Bước 6 — Bookkeeping fire-and-forget

Sau một validation thành công, validate() cập nhật last_validated_at mà không await promise:

ts
this.licenseRepository
  .updateById({ id: license.id, data: { lastValidatedAt: nowIso } })
  .catch(err => this.logger.for('validate').error('Failed to update lastValidatedAt | Error: %s', err));

Nếu update thất bại, lỗi được log nhưng response validation vẫn thành công. Không có LicenseEvent validated ghi bởi đường này — sự kiện duy nhất validate() phát là expired, và chỉ ở nhánh auto-expiry trong bước 3.

Response (IValidationResult)

ts
interface IValidationResult {
  valid: boolean;
  code: string;
  license: {
    id: string;
    key: string;
    status: string;
    expiresAt: string | null;
  };
  features: Record<string, unknown>;
  activation: {
    id: string | null;
    used: number;
    limit: number | null;
  };
  certificate?: string;
}

Response không mang một trường licenseKey riêng. license.key hiện diện khi tìm thấy một license; certificate cache (nếu có) là cái client forward tới các service khác. Không có secret giải mã AES trong response — xem Certificate System.

Mã kết quả validation

CodevalidĐặt bởiÝ nghĩa
VALIDfall-through bước 6License active và chưa hết hạn
GRACE_PERIODbước 3 (isInGracePeriod)Qua expiresAt nhưng trong graceExpiresAt
LICENSE_NOT_FOUNDbước 1Không hàng nào khớp key
LICENSE_SUSPENDEDbước 2status === 'suspended'
LICENSE_REVOKEDbước 2status === 'revoked'
LICENSE_EXPIREDbước 2 (đã expired) hoặc bước 3 (auto-expiry)Qua grace
LICENSE_NOT_STARTEDbước 2startsAt ở tương lai
ACTIVATION_LIMIT_REACHEDbước 5Đạt giới hạn slot, không slot nào tiêu

Nếu nhánh race-loser auto-expiry kích hoạt (bước 3), code trả về là LICENSE_${actual_status} — thường LICENSE_EXPIRED nhưng có thể LICENSE_SUSPENDED hoặc LICENSE_REVOKED nếu cái thắng tình cờ là một thao tác khác.

ActivationService

ActivationService là cái back controller CRUD chuẩn /activations và cũng được tái dùng bởi pipeline validation. Nó expose hai method:

activate({ licenseId, fingerprint, label?, platform?, hostname?, ip? })

Hai pha, tương tự tryCreateActivation của validate(), nhưng dùng khi một client muốn đăng ký một thiết bị rõ ràng thay vì lúc validation.

  1. Fast path (không lock): tìm một (licenseId, fingerprint) có sẵn. Nếu thấy, trả nó nguyên trạng.
  2. Slow path (transaction):
    • SELECT license FOR UPDATE
    • 404 nếu thiếu; 409 nếu !isActive(status)
    • Giải config activation (license.override.activation ?? policy.activation ?? null)
    • Nếu config non-null, count activation bên trong transaction; nếu đạt giới hạn → 409 Conflict (Activation limit reached (<limit>))
    • Insert hàng activation bên trong transaction
    • Log một LicenseEvent activated với { fingerprint, activationId }
    • Commit và trả { data: <activation> }

deactivate({ activationId, ip? })

  1. findById404 nếu thiếu
  2. deleteById
  3. Log một LicenseEvent deactivated với { fingerprint, activationId }

Không có endpoint REST có tên cho deactivate; client gọi DELETE /activations/{id} chuẩn và dựa vào wiring controller hoặc gọi service này trực tiếp. Cũng không có đường "deactivate by fingerprint" — client phải biết activation id.

Không heartbeat, không auto-reaping

Không có method heartbeat(), không method isAlive(), không cột lastHeartbeatAt trên Activation, không endpoint POST /validation/heartbeat, không TTL Redis cho activation, và không reaper background. Một slot activation được giữ tới khi ai đó gọi deactivate() (hoặc tới khi license cha bị xoá, cascade). Nếu một thiết bị mất, admin phải xoá hàng activation của nó thủ công.

Bảng Activation

Tham chiếu cột đầy đủ nằm trong Domain Model → Activation. Điểm chính cho trang này: fingerprint lưu nguyên văn; ràng buộc partial-unique UQ_Activation_licenseId_fingerprint cưỡng chế một hàng mỗi thiết bị mỗi license (và cấp năng lượng cho fast-path idempotent); ON DELETE CASCADE từ License giải phóng mọi seat khi một license bị xoá.

Trang liên quan

TrangMô tả
ArchitectureSequence diagram validate + activate
Domain ModelSchema đầy đủ Activation + bất biến
Policies & FeaturesCấu hình giới hạn activation, giải feature
License LifecycleChuyển trạng thái và log audit
Certificate SystemCách trường certificate trong response được tạo

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.