Docs
/

@leumas/licensing

Leumas licensing — Ed25519-signed OFFLINE entitlement tokens (LEV1.<payload>.<sig>), license-key mint/hash, the platform issuer service + routers, and the appliance client. The 'buy on leumas.tech →...

@leumas/licensing

Ed25519-signed offline entitlement tokens — the licensing seam between the Leumas-hosted platform and self-hosted Studio appliances (the EXE). Lands the unit deferred in ops/migration/imperium-hosting.json ("offline signed entitlement tokens").

Built (M2 groundwork, verified)

  • src/token.jssignEntitlementToken / verifyEntitlementToken / needsRefresh.

Format LEV1.<b64url payload>.<b64url sig>; node:crypto only. Payload carries entitlement_policies-shaped docs + a tenant features map + iat/nbf/exp/refreshAfter.

  • src/keygen.jsLEUMAS-XXXX-XXXX-XXXX-XXXX mint (Crockford base32), peppered sha256 storage

hash (apikeys discipline), keyLast4.

  • scripts/generate-keypair.mjs <kid> — public key → keys/public-keys.json (committed, ships in

the EXE); private key → LEUMAS_LICENSE_SIGNING_KEY on the platform ONLY.

To build (M2 remainder — see ops/migration/studio-desktop-licensing.json)

  • src/service.js — platform issuer: issue-on-membership (subscribe to auth's membership events),

activate (key → activation row + signed token), refresh (heartbeat + revocation check), deactivate, seat/activation limits. Tables: licenses + license_activations in @leumas/schemas (real Drizzle tables — money-adjacent).

  • src/client.js — appliance: persists license.json under @leumas/runtime-paths dataRoot,

daily refresh with offline grace (token stays valid until exp), degrades to a banner — NEVER bricks local data; lastSeenWallClock clock-tamper basic; onChangeentitlements.refresh().

  • routers/platform.router.js (/licensing/*, platform role only) +

routers/appliance.router.js (replaces the /api/license stub in products/leumas-api/src/routes/activation.js behind the SAME route shapes).

Desktop sign-in — the key-free door

POST /licensing/desktop/signin (platform) is what lets the Studio EXE's first run ask for a leumas.tech email and password instead of a licence key.

EXE first run ──-> POST /api/license/signin (appliance router)
 └-> client.signIn()
 └-> POST /licensing/desktop/signin (platform router)
                                ├ verifyCredentials()          ← injected usersService.login
                                └ service.signInDesktop()       → {token, license, licenseKey}
 <-─ appliance: provisionFederatedUser() + startSession() → session cookie

Three rules hold it together:

  • The password is checked by the platform, and never travels further. @leumas/licensing has

no @leumas/auth; the host injects verifyCredentials. Every failure — wrong password, unknown address, deactivated account — comes back as one indistinguishable 401, so the endpoint is not an account-enumeration oracle. It sits behind the same 10-per-15-min limiter as key activation.

  • One licence per DEVICE, key rotated on every sign-in. The appliance needs a plaintext key to

run its heartbeat and the platform stores only a hash, so a device signing in again is handed a NEW key rather than one nobody can recover. Rotation touches only that device's row; a shared row with a rotating key would cut off every other install the customer owns.

  • After the save, the state on disk is identical to a key activation. Same licenseKey, same

instanceId, same token — so refresh, deactivate, revocation, offline grace and the relay have exactly one code path, not two.

Sign-in is offered only when the appliance has a central server (LEUMAS_LICENSE_SERVER). Without one there is no account to check, so /api/license/signin answers 501 and GET /api/license/status reports signIn: false — which is what the first-run wizard reads to choose its flow. It never guesses.

The trust split (do not change casually)

The policy ceiling is signed by Leumas (the appliance's entitlements engine loads policies from the token, never from its own DB); membership assignment stays local (the customer's admin grants plans to their team via the existing offline grant path in @leumas/auth).

Desktop sign-in does not bend that split. It proves WHO centrally and mirrors that identity into a local row (provisionFederatedUser, admin only if it is the first account); what that person may do on the box is still local RBAC under a Leumas-signed ceiling.

What it exports

26 exports: 2 components, 0 hooks, 24 helpers. Import from @leumas/licensing.

RELAY_COLLECTIONRELEASE_CHANNELScreateInstanceAuthcreateLicenseClientcreateLicenseStatusRoutercreateLicensingRoutercreateLicensingServicecreateRelaycreateStudioLinkcreateUpdatesRouterhashLicenseKeyhashLicenseKeykeyLast4keyLast4loadPublicKeysmintLicenseKeymintLicenseKeyneedsRefreshneedsRefreshnormalizeLicenseKeynormalizeLicenseKeyresolveInstancePublicKeyssignEntitlementTokensignEntitlementTokenverifyEntitlementTokenverifyEntitlementToken
Source shared/packages/licensing/README.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt