CallMeTechie
DE Login
Home Products Blog About Contact

Setting up your license key

🚀 Installation & Setup · Updated 1 month ago

Applying feature checks

The license service is queried in three places:

1. Middleware — server-side API guard

// src/routes/api/rdp.js
router.use(requireFeature('remote_desktop'));

requireFeature responds with 403 {ok:false, feature, upgrade_url} on missing feature. For quantitative limits there is requireLimit(key, countFn):

// src/routes/api/peers.js (schematisch)
router.post('/', requireLimit('vpn_peers', () => peers.count()), ...);

requireLimit distinguishes three states: -1 → unlimited, 0 → feature not available (403), otherwise comparison count >= limit.

2. Templates — UI gate

injectLicense makes license.features available in every view:

{% if license.features.remote_desktop %}
  <a href="/rdp">RDP</a>
{% else %}
  <span class="lock-icon" title="Pro feature">…</span>
{% endif %}

The UI typically shows locked features anyway (with a lock icon) to make upgrade paths visible.

3. Services — opportunistic

At the service layer, features are queried to skip side paths:

// src/routes/api/gateway.js (Heartbeat-Handler)
if (body.hostname && hasFeature('internal_dns')) {
  peers.setHostname(peerId, body.hostname, 'agent');
}

The heartbeat itself does not fail when internal_dns is off — the field is simply ignored.

Enforcement on downgrade

When a license refresh detects a plan change (previousPlan !== cachedPlan), enforceLimitsInternal() runs:

  1. For every limit feature (vpn_peers, http_routes, l4_routes), the actual count is computed.
  2. If count > limit, the count - limit oldest entries (ORDER BY created_at ASC) are set to enabled=0.
  3. WireGuard config and Caddy config are synchronized — deactivated peers/routes disappear from the wire.
  4. Activity log entry peer_license_disabled / route_license_disabled.
  5. Optional email alert if email_alerts_enabled=true.

Records are never deleted. A later upgrade does not automatically reactivate them, but the admin sees them in the UI as deactivated and can manually enable them.

Pattern for new features

For every new paid feature, four things must happen (see feedback_new_feature_license.md):

  1. Flag in COMMUNITY_FALLBACK — usually false, as a limit usually 0.
  2. API guardrouter.use(requireFeature('X')) or per-route.
  3. UI guard — template wrap with {% if license.features.X %} and lock fallback.
  4. Service fallback — on downgrade, no service must crash, only the feature code path is skipped.

Key rotation and removal

removeLicense() deletes the JWT, stops the refresh timer, sets community mode, removes the key from the DB, and starts enforcement. _overrideForTest(features) patches cachedFeatures directly — only for test setup, not for production.

Testing the licensing system

The service exports _overrideForTest and _getHardwareFingerprint for test harnesses. In test setup, the real refresh timer is not started (no startLicenseRefresh() call), so tests stay deterministic.

See also

Source files

  • src/services/license.js — Core service, COMMUNITY_FALLBACK, validateLicense, enforceLimitsInternal.
  • src/middleware/license.jsinjectLicense, requireFeature, requireLimit, requireFeatureField.
  • src/services/settings.js — DB-backed key storage for UI activation.
  • config/default.jslicense.server, license.tokenPath, license.key, license.signingKey.

Cookie Settings

We use cookies to improve your experience. Essential cookies are always active.

Privacy Policy
ESC
↑↓ navigate open esc close