Multi-repo license keys — single DLID covers multiple repos per product tier #615

Open
opened 2026-06-12 07:46:55 +00:00 by jmiller · 2 comments
Owner

Summary

License keys (DLIDs) need to support multi-repo entitlement — a single license key authorizes access to multiple repos simultaneously, scoped by product tier and domain.

Problem

Currently a DLID is tied to a single repo's update server. MokoSuite products are packaged as layered repos with git submodules:

MokoSuitePOS license = access to:
  - MokoSuite (base)
  - MokoSuiteCRM
  - MokoSuiteERP
  - MokoSuitePOS

A customer purchasing "MokoSuite POS" needs ONE license key that grants update access to all 4 repos, but NOT to MokoSuiteShop, MokoSuiteHRM, etc.

Proposed Data Model

License Key → Repo Entitlements

-- Existing: license key tied to domain
CREATE TABLE licenses (
  id INT PRIMARY KEY,
  dlid VARCHAR(64) UNIQUE,
  domain VARCHAR(255),
  contact_id INT,
  status ENUM('active','expired','revoked'),
  product_tier VARCHAR(50),  -- NEW: 'pos', 'shop', 'erp', etc.
  expires_at DATETIME
);

-- NEW: which repos a license key can access
CREATE TABLE license_repo_entitlements (
  id INT PRIMARY KEY,
  license_id INT NOT NULL,
  repo_owner VARCHAR(100) NOT NULL,
  repo_name VARCHAR(100) NOT NULL,
  UNIQUE KEY (license_id, repo_owner, repo_name)
);

-- NEW: product tier definitions (which repos are in each tier)
CREATE TABLE product_tiers (
  id INT PRIMARY KEY,
  tier_key VARCHAR(50) UNIQUE,  -- 'pos', 'shop', 'hrm', etc.
  tier_name VARCHAR(100),
  repos JSON  -- ["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS"]
);

Product Tier Definitions

Tier Repos Included
base MokoSuite
crm MokoSuite, MokoSuiteCRM
erp MokoSuite, MokoSuiteCRM, MokoSuiteERP
child MokoSuite, MokoSuiteCRM, MokoSuiteChild
create MokoSuite, MokoSuiteCRM, MokoSuiteCreate
npo MokoSuite, MokoSuiteCRM, MokoSuiteNPO
pos MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuitePOS
shop MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuiteShop
hrm MokoSuite, MokoSuiteCRM, MokoSuiteHRM
mrp MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuiteMRP
restaurant MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuitePOS, MokoSuiteRestaurant
enterprise ALL repos

API Changes

DLID Validation Endpoint

Current: GET /api/v1/repos/{owner}/{repo}/releases/check-dlid?dlid=xxx

  • Returns 200 if DLID is valid for that specific repo

Proposed: Same endpoint, but validation checks:

  1. DLID exists and is active
  2. DLID's domain matches request origin (or domain is *)
  3. DLID's product tier includes the requested repo
  4. DLID is not expired

New Endpoints

GET  /api/v1/licenses/{dlid}/repos          — list repos this DLID can access
POST /api/v1/licenses                        — create license with tier
PATCH /api/v1/licenses/{dlid}                — update tier/domain/status
GET  /api/v1/product-tiers                   — list available tiers
POST /api/v1/product-tiers                   — create/update tier definition

Update Server Integration

Each repo's updates.xml includes the DLID in the download URL:

<downloadurl>https://git.example.com/MokoConsulting/MokoSuitePOS/releases/download/v1.0.0/pkg_mokosuitepos.zip?dlid={KEY}</downloadurl>

The server validates the DLID against the repo being requested. A POS license key works for MokoSuitePOS, MokoSuiteCRM, MokoSuiteERP, and MokoSuite downloads — but returns 403 for MokoSuiteShop.

Upgrade Path

When a customer upgrades from CRM to POS:

  1. Their existing DLID's product_tier changes from crm to pos
  2. license_repo_entitlements is recalculated from the tier definition
  3. Their Joomla update servers for CRM + Base continue working
  4. New update servers for ERP + POS become accessible
  5. No new DLID needed — same key, expanded access

Integration with MokoSuite Shop

When a license-type product is purchased via MokoSuiteShop:

  1. LicenseHelper::generateLicense() creates the DLID
  2. The product's product_tier field determines which repos to entitle
  3. Entitlements are auto-created in license_repo_entitlements
  4. Customer receives one DLID that works across all repos in their tier
## Summary License keys (DLIDs) need to support **multi-repo entitlement** — a single license key authorizes access to multiple repos simultaneously, scoped by product tier and domain. ## Problem Currently a DLID is tied to a single repo's update server. MokoSuite products are packaged as layered repos with git submodules: ``` MokoSuitePOS license = access to: - MokoSuite (base) - MokoSuiteCRM - MokoSuiteERP - MokoSuitePOS ``` A customer purchasing "MokoSuite POS" needs ONE license key that grants update access to all 4 repos, but NOT to MokoSuiteShop, MokoSuiteHRM, etc. ## Proposed Data Model ### License Key → Repo Entitlements ```sql -- Existing: license key tied to domain CREATE TABLE licenses ( id INT PRIMARY KEY, dlid VARCHAR(64) UNIQUE, domain VARCHAR(255), contact_id INT, status ENUM('active','expired','revoked'), product_tier VARCHAR(50), -- NEW: 'pos', 'shop', 'erp', etc. expires_at DATETIME ); -- NEW: which repos a license key can access CREATE TABLE license_repo_entitlements ( id INT PRIMARY KEY, license_id INT NOT NULL, repo_owner VARCHAR(100) NOT NULL, repo_name VARCHAR(100) NOT NULL, UNIQUE KEY (license_id, repo_owner, repo_name) ); -- NEW: product tier definitions (which repos are in each tier) CREATE TABLE product_tiers ( id INT PRIMARY KEY, tier_key VARCHAR(50) UNIQUE, -- 'pos', 'shop', 'hrm', etc. tier_name VARCHAR(100), repos JSON -- ["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS"] ); ``` ### Product Tier Definitions | Tier | Repos Included | |---|---| | `base` | MokoSuite | | `crm` | MokoSuite, MokoSuiteCRM | | `erp` | MokoSuite, MokoSuiteCRM, MokoSuiteERP | | `child` | MokoSuite, MokoSuiteCRM, MokoSuiteChild | | `create` | MokoSuite, MokoSuiteCRM, MokoSuiteCreate | | `npo` | MokoSuite, MokoSuiteCRM, MokoSuiteNPO | | `pos` | MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuitePOS | | `shop` | MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuiteShop | | `hrm` | MokoSuite, MokoSuiteCRM, MokoSuiteHRM | | `mrp` | MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuiteMRP | | `restaurant` | MokoSuite, MokoSuiteCRM, MokoSuiteERP, MokoSuitePOS, MokoSuiteRestaurant | | `enterprise` | ALL repos | ### API Changes #### DLID Validation Endpoint Current: `GET /api/v1/repos/{owner}/{repo}/releases/check-dlid?dlid=xxx` - Returns 200 if DLID is valid for that specific repo Proposed: Same endpoint, but validation checks: 1. DLID exists and is active 2. DLID's domain matches request origin (or domain is `*`) 3. DLID's product tier includes the requested repo 4. DLID is not expired #### New Endpoints ``` GET /api/v1/licenses/{dlid}/repos — list repos this DLID can access POST /api/v1/licenses — create license with tier PATCH /api/v1/licenses/{dlid} — update tier/domain/status GET /api/v1/product-tiers — list available tiers POST /api/v1/product-tiers — create/update tier definition ``` ## Update Server Integration Each repo's `updates.xml` includes the DLID in the download URL: ```xml <downloadurl>https://git.example.com/MokoConsulting/MokoSuitePOS/releases/download/v1.0.0/pkg_mokosuitepos.zip?dlid={KEY}</downloadurl> ``` The server validates the DLID against the repo being requested. A POS license key works for `MokoSuitePOS`, `MokoSuiteCRM`, `MokoSuiteERP`, and `MokoSuite` downloads — but returns 403 for `MokoSuiteShop`. ## Upgrade Path When a customer upgrades from CRM to POS: 1. Their existing DLID's `product_tier` changes from `crm` to `pos` 2. `license_repo_entitlements` is recalculated from the tier definition 3. Their Joomla update servers for CRM + Base continue working 4. New update servers for ERP + POS become accessible 5. No new DLID needed — same key, expanded access ## Integration with MokoSuite Shop When a license-type product is purchased via MokoSuiteShop: 1. `LicenseHelper::generateLicense()` creates the DLID 2. The product's `product_tier` field determines which repos to entitle 3. Entitlements are auto-created in `license_repo_entitlements` 4. Customer receives one DLID that works across all repos in their tier
Author
Owner

Branch created: feature/615-multi-repo-license-keys-single-dlid-cove

git fetch origin
git checkout feature/615-multi-repo-license-keys-single-dlid-cove
Branch created: [`feature/615-multi-repo-license-keys-single-dlid-cove`](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea-APP/src/branch/feature/615-multi-repo-license-keys-single-dlid-cove) ```bash git fetch origin git checkout feature/615-multi-repo-license-keys-single-dlid-cove ```
Author
Owner

Competitive Research Results

Joomla Extension Licensing (Industry Standard)

Vendor DLID Model Multi-Extension Domain Binding
Akeeba Single DLID for ALL products Yes - one key for everything No (user-bound only)
RegularLabs Single DLID Yes - all-or-nothing No
JoomShaper API key per subscription Club membership for all Yes - domain limited
WordPress/EDD License key + activation Per-product with bundles Yes - activate/deactivate

Industry consensus: DLID in extra_query field of #__update_sites table. Single shared system plugin writes DLID to all MokoSuite update sites at once.

Recommended Architecture (Keygen + Akeeba Hybrid)

Data model (inside Gitea Go codebase):

  • License: UserID, DLID (32-char hex), Tier, MaxDomains, ExpiresAt, Status
  • Entitlement: LicenseID, ProductCode (e.g. "restaurant", "crm")
  • Activation: LicenseID, Domain, ActivatedAt, LastSeenAt

API endpoints (new Gitea routes):

  • GET /api/v1/licensing/updates/{product}.xml?dlid=XXX - Joomla update XML
  • GET /api/v1/licensing/download/{product}/{version}.zip - signed package download
  • POST /api/v1/licensing/activate - domain activation
  • GET /api/v1/licensing/validate?dlid=XXX&product=YYY - license check

What NOT to Build

  • No machine fingerprinting (Joomla sites lack stable hardware IDs)
  • No runtime feature gating (GPL requires code to be usable - gate updates, not features)
  • No separate licensing portal (lives inside Gitea dashboard)
  • No short-lived JWT DLIDs (Joomla caches update URLs - DLIDs must be long-lived)

Implementation Priority

  1. Database schema (Gitea migration) - license, entitlement, activation tables
  2. DLID generation tied to Gitea user accounts
  3. Update XML endpoint with DLID validation
  4. Signed download endpoint serving Gitea release assets (ed25519, Go stdlib)
  5. Joomla system plugin for client-side DLID config
  6. Gitea dashboard UI for license management
  7. Domain activation tracking
  8. Tier management admin UI

Joomla Client-Side Flow

  1. User enters DLID once in MokoSuite shared system plugin
  2. Plugin writes extra_query = "dlid=XXXXX" to ALL MokoSuite update_sites entries
  3. Joomla updater calls update XML with dlid parameter
  4. Gitea validates DLID, checks entitlement for requested product, returns XML or 403
  5. Download URL contains time-limited ed25519-signed token
  6. Gitea serves release asset from existing repo releases
## Competitive Research Results ### Joomla Extension Licensing (Industry Standard) | Vendor | DLID Model | Multi-Extension | Domain Binding | |--------|-----------|-----------------|----------------| | **Akeeba** | Single DLID for ALL products | Yes - one key for everything | No (user-bound only) | | **RegularLabs** | Single DLID | Yes - all-or-nothing | No | | **JoomShaper** | API key per subscription | Club membership for all | Yes - domain limited | | **WordPress/EDD** | License key + activation | Per-product with bundles | Yes - activate/deactivate | **Industry consensus:** DLID in `extra_query` field of `#__update_sites` table. Single shared system plugin writes DLID to all MokoSuite update sites at once. ### Recommended Architecture (Keygen + Akeeba Hybrid) **Data model** (inside Gitea Go codebase): - License: UserID, DLID (32-char hex), Tier, MaxDomains, ExpiresAt, Status - Entitlement: LicenseID, ProductCode (e.g. "restaurant", "crm") - Activation: LicenseID, Domain, ActivatedAt, LastSeenAt **API endpoints** (new Gitea routes): - `GET /api/v1/licensing/updates/{product}.xml?dlid=XXX` - Joomla update XML - `GET /api/v1/licensing/download/{product}/{version}.zip` - signed package download - `POST /api/v1/licensing/activate` - domain activation - `GET /api/v1/licensing/validate?dlid=XXX&product=YYY` - license check ### What NOT to Build - No machine fingerprinting (Joomla sites lack stable hardware IDs) - No runtime feature gating (GPL requires code to be usable - gate updates, not features) - No separate licensing portal (lives inside Gitea dashboard) - No short-lived JWT DLIDs (Joomla caches update URLs - DLIDs must be long-lived) ### Implementation Priority 1. Database schema (Gitea migration) - license, entitlement, activation tables 2. DLID generation tied to Gitea user accounts 3. Update XML endpoint with DLID validation 4. Signed download endpoint serving Gitea release assets (ed25519, Go stdlib) 5. Joomla system plugin for client-side DLID config 6. Gitea dashboard UI for license management 7. Domain activation tracking 8. Tier management admin UI ### Joomla Client-Side Flow 1. User enters DLID once in MokoSuite shared system plugin 2. Plugin writes `extra_query = "dlid=XXXXX"` to ALL MokoSuite update_sites entries 3. Joomla updater calls update XML with dlid parameter 4. Gitea validates DLID, checks entitlement for requested product, returns XML or 403 5. Download URL contains time-limited ed25519-signed token 6. Gitea serves release asset from existing repo releases
Sign in to join this conversation.