Lewati ke konten utama

Gitlab Issue Fr013 Licensing Framework

Problem Statement

Saat ini TessaERP belum memiliki mekanisme licensing yang terstandarisasi untuk membatasi penggunaan aplikasi berdasarkan entitlement pelanggan.

Kondisi saat ini menyebabkan:

  • Tidak ada mekanisme resmi untuk membedakan pelanggan Trial, Subscription, maupun Perpetual License
  • Tidak ada validasi lisensi yang dapat mencegah modifikasi file lisensi oleh pelanggan
  • ERP belum memiliki abstraction layer untuk membaca entitlement pelanggan
  • Belum tersedia fondasi untuk feature licensing, user limitation, subscription expiry, maupun module entitlement
  • Deployment On-Premise maupun SaaS belum memiliki proses validasi lisensi yang konsisten

Sebagai bagian dari transformasi ERP menjadi platform SaaS dan commercial product, diperlukan fondasi licensing yang sederhana namun extensible.


Goals

Mengimplementasikan fondasi Licensing Framework pada TessaERP dengan pendekatan:

  • License file berbasis JSON
  • License file ditandatangani menggunakan Private Key
  • License Service melakukan verifikasi menggunakan Public Key
  • ERP tidak membaca license file secara langsung
  • ERP mengakses entitlement melalui abstraction ILicenseProvider
  • License generation masih dilakukan secara manual melalui backend/admin tool
  • Menyediakan fondasi untuk feature licensing dan subscription validation pada phase berikutnya

Tujuan utama:

  • Menjamin integritas license file
  • Memisahkan licensing concern dari business module ERP
  • Menyediakan extensible license schema untuk kebutuhan masa depan
  • Menyiapkan pondasi untuk SaaS maupun On-Premise deployment

Scope of Works

Backend

1. License Domain Model

Implementasikan struktur license payload yang extensible.

{
"licenseId": "LIC-2026-0001",
"tenantId": "TENANT001",
"edition": "Professional",
"subscription": {
"type": "annual",
"expiresAt": "2027-12-31"
},
"features": {
"sales": true,
"purchase": true,
"inventory": true,
"finance": false
},
"limits": {
"users": 50
},
"constraints": {
"machineFingerprint": null
}
}
SectionPurpose
licenseIdUnique License Identifier
tenantIdTenant / Customer Identifier — diverifikasi terhadap env var TENANT_ID/ENTITY_ID (EID)
editionProduct Edition
subscriptionSubscription Information
featuresFeature Entitlement
limitsUser / Resource Limitation
constraintsFuture Restriction Support (machineFingerprint: Phase 2)

2. Cryptographic Signing Framework

Implementasi mekanisme signing menggunakan asymmetric cryptography.

Portal / License Generator memiliki: Private Key
License Service memiliki: Public Key

License Payload
↓ Sign using Private Key
↓ license.lic
↓ Verify using Public Key

Requirements:

  • Gunakan RSA minimal 2048-bit atau ekuivalen
  • Signature disimpan bersama payload
  • Verifikasi dilakukan sebelum license digunakan
  • Tidak boleh menggunakan shared secret / symmetric key

3. License File Format

{
"payload": { ... },
"signature": "BASE64_SIGNATURE"
}

Validation Rules:

License dianggap valid jika:

  • Payload dapat dibaca
  • Signature valid
  • License belum expired
  • tenantId cocok dengan env var TENANT_ID/ENTITY_ID (EID)

License dianggap invalid jika:

  • Payload dimodifikasi
  • Signature tidak valid
  • Signature tidak ditemukan
  • Format file tidak sesuai
  • tenantId tidak cocok dengan env var TENANT_ID/ENTITY_ID (EID)

4. License Verification Service

public interface ILicenseVerificationService
{
LicenseValidationResult Validate();
}

public class LicenseValidationResult
{
public bool IsValid { get; set; }
public string Reason { get; set; }
public DateTime? ExpirationDate { get; set; }
}

Verification Process pada startup:

Load license.lic
↓ Verify Signature
↓ Validate tenantId vs TENANT_ID/ENTITY_ID (EID) env var
↓ Validate Expiry
↓ Cache License Information

Error Reason Messages:

ConditionReason
File not found"License file not found. Please upload a valid license."
Signature invalid"License file is corrupted or has been tampered. Request a new license from your vendor."
Tenant mismatch"This license was issued for a different tenant. Contact your vendor."
Expired"Your license expired on {date}. Please renew your subscription."

5. License Provider Abstraction

ERP tidak boleh membaca license file secara langsung.

public interface ILicenseProvider
{
bool IsValid();
bool HasFeature(string featureCode);
int GetLimit(string limitCode);
LicenseInfo GetLicenseInfo();
}

Example Usage:

if (_licenseProvider.HasFeature("finance"))
{
ShowFinanceMenu();
}

ERP module hanya menggunakan interface tersebut. ERP tidak mengetahui cara license dibuat, ditandatangani, lokasi file, maupun mekanisme verifikasi.


6. License Bootstrap Validation

Pada startup:

Application Start
↓ Validate License
↓ License Valid?
YES → Continue Startup
NO → Show License Error with Go To Upload License View/Page

Jika lisensi tidak valid:

  • Redirect semua request ke /license (upload page)
  • Simpan log error
  • Blok akses ERP

Implementasi interceptor di middleware level (bukan per-page):

app.Use(async (context, next) =>
{
var license = context.RequestServices.GetRequiredService<ILicenseProvider>();
if (!license.IsValid() && !context.Request.Path.StartsWithSegments("/license"))
{
context.Response.Redirect("/license");
return;
}
await next();
});

7. License Upload Page (Invalid License UI)

Saat license invalid, tampilkan halaman upload.

Upload Flow:

User uploads license.lic
↓ Parse file
↓ Verify signature (public key)
↓ Validate tenantId vs TENANT_ID/ENTITY_ID (EID) env var
↓ Validate expiry
↓ All valid?
YES → Save license.lic → Show restart instruction
NO → Show specific error reason

Post-upload Success Message:

✅ License file uploaded successfully.

Please restart the application server to apply the new license

8. Manual License Generator

Implementasikan utility/backend tool untuk generate license secara manual.

Input:

FieldMandatory
Tenant IDYes
EditionYes
Subscription TypeYes
Expiry DateYes
FeaturesNo
LimitsNo

Output: license.lic

Generate Payload
↓ Sign using Private Key
↓ Export license.lic

File kemudian dikirim secara manual ke customer atau deployment environment.


9. Configuration

KeyDescription
Licensing:LicenseFilePathLokasi file license
Licensing:PublicKeyPathLokasi public key
Licensing:LicenseValidationEnabledEnable / Disable Validation
{
"Licensing": {
"LicenseFilePath": "./license/license.lic",
"PublicKeyPath": "./license/public.pem",
"LicenseValidationEnabled": true
}
}

Business Rules

  • Setiap ERP instance hanya menggunakan satu file license aktif
  • License wajib diverifikasi saat startup
  • tenantId pada license wajib cocok dengan env var TENANT_ID/ENTITY_ID (EID)
  • ERP tidak boleh membaca payload secara langsung tanpa melalui License Provider
  • Signature verification wajib berhasil sebelum entitlement digunakan
  • License yang expired dianggap invalid
  • Public Key hanya digunakan untuk verification
  • Private Key tidak boleh tersedia pada deployment ERP customer

Out of Scope (Phase 1)

  • Online Activation
  • Machine Fingerprint Enforcement (reserved in schema, enforced in Phase 2)
  • Hot Reload License (penggantian license memerlukan restart)
  • Grace Period for Expired License
  • Concurrent User Enforcement
  • Feature Menu Hiding based on entitlement
  • Subscription Auto Renewal
  • License Revocation Server
  • Cloud License Validation
  • License Management Portal UI
  • Automatic License Distribution
  • Per User Licensing
  • Per Branch Licensing
  • Usage Based Licensing
  • License Service as Separate Microservice

Acceptance Criteria

  • License payload dapat di-generate secara manual
  • License dapat ditandatangani menggunakan Private Key
  • License dapat diverifikasi menggunakan Public Key
  • Modifikasi payload menyebabkan verifikasi gagal
  • Pesan error menampilkan reason spesifik (expired / signature invalid / tenant mismatch / file missing)
  • License dengan tenantId berbeda ditolak
  • License expiry tervalidasi dengan benar
  • Halaman upload license ditampilkan ketika license invalid
  • Terdapat validasi license file pada View upload license (expired / signature invalid / tenant mismatch)
  • Setelah upload berhasil, instruksi restart server aplikasi ditampilkan
  • Private Key tidak tersedia pada environment customer
  • Public Key digunakan hanya untuk verification
  • Dapat memvalidasi feature licensing dan limit enforcement (hanya tampilkan feature/menu module yang eligible)
  • Dapat memvalidasi limit enforcement (tidak bisa membuat user lebih dari limit)

References