@huloglobal/vendure-licence-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@huloglobal/vendure-licence-sdk` are documented here. The
4
+ format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and
5
+ this project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] — Unreleased
8
+
9
+ ### Added
10
+ - Offline RSA-SHA256 JWT licence verification (`verifyLicence`).
11
+ - `RevocationChecker` with a 7-day default poll interval and soft-failure
12
+ caching of the previous revocation list.
13
+ - `LicenceStatus` and `LicencePayload` shared types.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ HULO Global Limited — Commercial Plugin Licence
2
+
3
+ Copyright (c) 2026 HULO Global Limited (Companies House registration in
4
+ England and Wales). All rights reserved.
5
+
6
+ This package contains licence-verification code shared by the
7
+ @hulo/vendure-plugin-* family of Vendure plugins. The package may be
8
+ installed and used only in conjunction with a paid HULO Vendure plugin
9
+ that depends on it. Redistribution of the package outside of that
10
+ context is not permitted.
11
+
12
+ The source code is published only to allow customers to audit the
13
+ verification logic for the plugins they have purchased. Modification,
14
+ reverse engineering of the licence-signing keys, or use of this package
15
+ to circumvent licence verification in any HULO product is expressly
16
+ prohibited.
17
+
18
+ No warranty is given. To the maximum extent permitted by law, HULO
19
+ Global Limited disclaims all liability arising from use of this
20
+ software.
21
+
22
+ For commercial licensing enquiries, contact: sales@eliteenterprisesoftware.com
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # @huloglobal/vendure-licence-sdk
2
+
3
+ Licence-key verification helpers shared by every commercial HULO Vendure plugin.
4
+
5
+ The SDK is consumed by plugins, not by Vendure storefronts directly. Install it
6
+ as a runtime dependency of your plugin package, embed your `publicKey`
7
+ constant in the plugin source, and call `verifyLicence(...)` once at boot.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ yarn add @huloglobal/vendure-licence-sdk
13
+ ```
14
+
15
+ ## Usage in a plugin
16
+
17
+ ```ts
18
+ import { verifyLicence, RevocationChecker } from '@huloglobal/vendure-licence-sdk';
19
+ import { VendurePlugin } from '@vendure/core';
20
+
21
+ const HULO_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
22
+ ... your plugin's embedded RSA public key ...
23
+ -----END PUBLIC KEY-----`;
24
+
25
+ const revocation = new RevocationChecker('https://licence.hulo-global.com/revoked.json');
26
+ revocation.start();
27
+
28
+ @VendurePlugin({ /* ... */ })
29
+ export class MyPlugin {
30
+ static init(options: { licenceKey?: string }) {
31
+ const status = verifyLicence({
32
+ licenceKey: options.licenceKey,
33
+ pluginId: 'vendure-plugin-my-thing',
34
+ host: process.env.VENDURE_HOST || 'localhost',
35
+ publicKey: HULO_PUBLIC_KEY,
36
+ revokedIds: revocation.getRevokedIds(),
37
+ });
38
+ // status.valid → enable all features
39
+ // !status.valid → log status.message, run in unlicensed mode
40
+ MyPlugin.licenceStatus = status;
41
+ return MyPlugin;
42
+ }
43
+ static licenceStatus: LicenceStatus;
44
+ }
45
+ ```
46
+
47
+ ## Why offline JWT + periodic revocation?
48
+
49
+ - **Boot-time offline check.** Vendure stays bootable even when our licence
50
+ server is unreachable. The customer's storefront never hangs on a network call.
51
+ - **Soft revocation.** Refunded / leaked keys are revoked by adding the JWT
52
+ `jti` to a flat list at `https://licence.hulo-global.com/revoked.json`. The
53
+ plugin re-fetches the list every 7 days.
54
+ - **Soft failure.** Network outages keep the previous cached revocation list in
55
+ memory — your customer is never accidentally locked out of features they paid
56
+ for because our server hiccuped.
57
+
58
+ ## Licence status reasons
59
+
60
+ `LicenceStatus.reason` is one of:
61
+
62
+ | reason | meaning |
63
+ | --- | --- |
64
+ | `ok` | Licence verified and active. |
65
+ | `missing` | No key supplied. Run in unlicensed mode. |
66
+ | `malformed` | Key isn't a valid JWT. |
67
+ | `bad-signature` | Signature didn't verify — possible tampering. |
68
+ | `plugin-mismatch` | Licence is for a different HULO plugin. |
69
+ | `domain-mismatch` | Host domain not in `allowedDomains`. |
70
+ | `expired` | `exp` is in the past. |
71
+ | `revoked` | Key appears in the revocation list. |
72
+
73
+ ## Licence
74
+
75
+ Commercial — see LICENSE.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @huloglobal/vendure-licence-sdk
3
+ *
4
+ * Licence-key validation helpers shared by every commercial HULO
5
+ * Vendure plugin. Each plugin embeds the SDK and calls
6
+ * `verifyLicence(...)` at boot. The SDK:
7
+ *
8
+ * - Verifies an offline RSA-signed JWT payload (no boot-time network
9
+ * call required — Vendure stays bootable even when our licence
10
+ * server is unreachable).
11
+ * - Optionally polls a revocation list every 7 days so leaked or
12
+ * refunded keys can be cancelled.
13
+ * - Exposes a single `LicenceStatus` object plugins can read to
14
+ * decide whether to enable write paths / paid features.
15
+ *
16
+ * Plugins are expected to fail-soft: if no key is supplied or the key
17
+ * is invalid, the plugin should still register without crashing, log a
18
+ * clear warning, and disable mutation paths or restrict to a free
19
+ * tier. The end-user can always upgrade by supplying a valid key.
20
+ */
21
+ export { verifyLicence } from './verify';
22
+ export { LicenceStatus, LicencePayload, VerifyLicenceOptions } from './types';
23
+ export { RevocationChecker } from './revocation';
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * @huloglobal/vendure-licence-sdk
4
+ *
5
+ * Licence-key validation helpers shared by every commercial HULO
6
+ * Vendure plugin. Each plugin embeds the SDK and calls
7
+ * `verifyLicence(...)` at boot. The SDK:
8
+ *
9
+ * - Verifies an offline RSA-signed JWT payload (no boot-time network
10
+ * call required — Vendure stays bootable even when our licence
11
+ * server is unreachable).
12
+ * - Optionally polls a revocation list every 7 days so leaked or
13
+ * refunded keys can be cancelled.
14
+ * - Exposes a single `LicenceStatus` object plugins can read to
15
+ * decide whether to enable write paths / paid features.
16
+ *
17
+ * Plugins are expected to fail-soft: if no key is supplied or the key
18
+ * is invalid, the plugin should still register without crashing, log a
19
+ * clear warning, and disable mutation paths or restrict to a free
20
+ * tier. The end-user can always upgrade by supplying a valid key.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.RevocationChecker = exports.verifyLicence = void 0;
24
+ var verify_1 = require("./verify");
25
+ Object.defineProperty(exports, "verifyLicence", { enumerable: true, get: function () { return verify_1.verifyLicence; } });
26
+ var revocation_1 = require("./revocation");
27
+ Object.defineProperty(exports, "RevocationChecker", { enumerable: true, get: function () { return revocation_1.RevocationChecker; } });
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAEH,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AAEtB,2CAAiD;AAAxC,+GAAA,iBAAiB,OAAA"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Periodically polls the HULO revocation endpoint and exposes the
3
+ * latest set of revoked licence ids. Each plugin instantiates one
4
+ * `RevocationChecker` at boot and reads `getRevokedIds()` whenever it
5
+ * re-verifies (which is cheap because the JWT check is already
6
+ * cached).
7
+ *
8
+ * Failures (network, 500s) keep the previous cached set in memory so a
9
+ * brief outage at HULO never disables paid features at the customer's
10
+ * end. The cache is wiped only when a fresh list arrives.
11
+ */
12
+ export declare class RevocationChecker {
13
+ private readonly url;
14
+ private readonly pollMs;
15
+ private revokedIds;
16
+ private timer;
17
+ private lastFetchedAt;
18
+ private lastError;
19
+ constructor(url: string, pollMs?: number);
20
+ /** Start the background poll. Idempotent. */
21
+ start(): void;
22
+ /** Stop the background poll. Useful for tests and graceful shutdown. */
23
+ stop(): void;
24
+ getRevokedIds(): Set<string>;
25
+ getStatus(): {
26
+ lastFetchedAt: Date | null;
27
+ count: number;
28
+ lastError: string | null;
29
+ };
30
+ /** Force an immediate refresh — useful from a CLI / admin button. */
31
+ refresh(): Promise<void>;
32
+ }
33
+ //# sourceMappingURL=revocation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revocation.d.ts","sourceRoot":"","sources":["../src/revocation.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB;IAOtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAP3B,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAAuB;gBAGnB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,MAAgC;IAG7D,6CAA6C;IAC7C,KAAK,IAAI,IAAI;IAUb,wEAAwE;IACxE,IAAI,IAAI,IAAI;IAOZ,aAAa,IAAI,GAAG,CAAC,MAAM,CAAC;IAI5B,SAAS,IAAI;QAAE,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAQpF,qEAAqE;IAC/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA0BjC"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RevocationChecker = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ const loggerCtx = 'LicenceSdk:Revocation';
6
+ /**
7
+ * Periodically polls the HULO revocation endpoint and exposes the
8
+ * latest set of revoked licence ids. Each plugin instantiates one
9
+ * `RevocationChecker` at boot and reads `getRevokedIds()` whenever it
10
+ * re-verifies (which is cheap because the JWT check is already
11
+ * cached).
12
+ *
13
+ * Failures (network, 500s) keep the previous cached set in memory so a
14
+ * brief outage at HULO never disables paid features at the customer's
15
+ * end. The cache is wiped only when a fresh list arrives.
16
+ */
17
+ class RevocationChecker {
18
+ constructor(url, pollMs = 7 * 24 * 60 * 60 * 1000) {
19
+ this.url = url;
20
+ this.pollMs = pollMs;
21
+ this.revokedIds = new Set();
22
+ this.timer = null;
23
+ this.lastFetchedAt = null;
24
+ this.lastError = null;
25
+ }
26
+ /** Start the background poll. Idempotent. */
27
+ start() {
28
+ if (this.timer)
29
+ return;
30
+ // Run the first refresh on the next tick so the host can finish
31
+ // bootstrapping; subsequent refreshes follow the poll interval.
32
+ setTimeout(() => this.refresh().catch(() => undefined), 5000);
33
+ this.timer = setInterval(() => this.refresh().catch(() => undefined), this.pollMs);
34
+ // Don't keep the Node event loop alive purely for licence polling.
35
+ if (typeof this.timer.unref === 'function')
36
+ this.timer.unref();
37
+ }
38
+ /** Stop the background poll. Useful for tests and graceful shutdown. */
39
+ stop() {
40
+ if (this.timer) {
41
+ clearInterval(this.timer);
42
+ this.timer = null;
43
+ }
44
+ }
45
+ getRevokedIds() {
46
+ return this.revokedIds;
47
+ }
48
+ getStatus() {
49
+ return {
50
+ lastFetchedAt: this.lastFetchedAt,
51
+ count: this.revokedIds.size,
52
+ lastError: this.lastError,
53
+ };
54
+ }
55
+ /** Force an immediate refresh — useful from a CLI / admin button. */
56
+ async refresh() {
57
+ try {
58
+ const controller = new AbortController();
59
+ const t = setTimeout(() => controller.abort(), 10000);
60
+ const res = await fetch(this.url, { signal: controller.signal });
61
+ clearTimeout(t);
62
+ if (!res.ok) {
63
+ this.lastError = `HTTP ${res.status}`;
64
+ core_1.Logger.warn(`Revocation fetch failed: ${this.lastError}`, loggerCtx);
65
+ return;
66
+ }
67
+ const body = await res.json();
68
+ if (!body || !Array.isArray(body.revoked)) {
69
+ this.lastError = 'malformed response';
70
+ core_1.Logger.warn(`Revocation list malformed`, loggerCtx);
71
+ return;
72
+ }
73
+ this.revokedIds = new Set(body.revoked.map(String));
74
+ this.lastFetchedAt = new Date();
75
+ this.lastError = null;
76
+ }
77
+ catch (e) {
78
+ this.lastError = (e === null || e === void 0 ? void 0 : e.message) || 'fetch failed';
79
+ // Soft failure — keep the previously cached set in place.
80
+ core_1.Logger.warn(`Revocation fetch error: ${this.lastError}`, loggerCtx);
81
+ }
82
+ }
83
+ }
84
+ exports.RevocationChecker = RevocationChecker;
85
+ //# sourceMappingURL=revocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revocation.js","sourceRoot":"","sources":["../src/revocation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Decoded payload from a HULO licence JWT.
3
+ *
4
+ * Plugins must check `pluginId` matches their own identifier and that
5
+ * the host domain matches one of `allowedDomains` (or the wildcard
6
+ * `*`). The SDK does both checks for you.
7
+ */
8
+ export interface LicencePayload {
9
+ /** Stable plugin identifier — e.g. "vendure-plugin-visitor-analytics". */
10
+ pluginId: string;
11
+ /** Customer-facing identifier, usually the buyer's email. */
12
+ customer: string;
13
+ /** Domains the host install is permitted to run on. Use `["*"]` to
14
+ * allow any. Normally one or two domains. */
15
+ allowedDomains: string[];
16
+ /** Plan tier, e.g. "starter" / "pro" / "enterprise". Plugins can
17
+ * read this to gate per-tier features. */
18
+ plan: string;
19
+ /** Issued-at (seconds since epoch). */
20
+ iat: number;
21
+ /** Expiry (seconds since epoch). After this the SDK reports
22
+ * `valid: false, reason: "expired"`. */
23
+ exp: number;
24
+ /** Unique licence id used for revocation lookup. */
25
+ jti: string;
26
+ }
27
+ /** Result of a `verifyLicence(...)` call. Plugins should branch on
28
+ * `valid` and surface `reason` in their admin UI. */
29
+ export interface LicenceStatus {
30
+ /** `true` only when the JWT signature is valid, the plugin id and
31
+ * host domain match, the licence has not expired and it does not
32
+ * appear in the revocation list. */
33
+ valid: boolean;
34
+ /** Short machine-readable reason — one of `ok`, `missing`,
35
+ * `malformed`, `bad-signature`, `plugin-mismatch`,
36
+ * `domain-mismatch`, `expired`, `revoked`, `unknown`. */
37
+ reason: string;
38
+ /** Decoded payload if the JWT structure was readable, otherwise
39
+ * `null`. Available even on some failure paths (`expired`,
40
+ * `revoked`) so plugins can show "expired on 2026-12-31". */
41
+ payload: LicencePayload | null;
42
+ /** Human-readable message intended for the admin UI. */
43
+ message: string;
44
+ }
45
+ /** Options the plugin passes to `verifyLicence`. */
46
+ export interface VerifyLicenceOptions {
47
+ /** The licence JWT supplied by the customer in their plugin
48
+ * `init()` config. Pass `undefined` or empty string for unlicensed
49
+ * installs — the SDK reports `valid: false, reason: "missing"`. */
50
+ licenceKey?: string | null;
51
+ /** Stable identifier of the plugin checking. The SDK rejects the
52
+ * licence if the JWT's `pluginId` doesn't match. */
53
+ pluginId: string;
54
+ /** Hostname of the install. Compared against `allowedDomains`.
55
+ * Read from `process.env.VENDURE_HOST` or the Vendure config when
56
+ * available. */
57
+ host: string;
58
+ /** RSA public key the SDK uses to verify the JWT signature.
59
+ * Embedded in the plugin source code at build time. */
60
+ publicKey: string;
61
+ /** Optional set of revoked `jti` values fetched from the
62
+ * revocation server. Pass an empty Set for an offline-only check. */
63
+ revokedIds?: Set<string>;
64
+ }
65
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB;kDAC8C;IAC9C,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;+CAC2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ;6CACyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;CACf;AAED;sDACsD;AACtD,MAAM,WAAW,aAAa;IAC1B;;yCAEqC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf;;8DAE0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf;;kEAE8D;IAC9D,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACjC;;wEAEoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;yDACqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB;;qBAEiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb;4DACwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB;0EACsE;IACtE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import { LicenceStatus, VerifyLicenceOptions } from './types';
2
+ /**
3
+ * Verify a HULO licence JWT. The JWT uses the `RS256` algorithm:
4
+ * `base64url(header).base64url(payload).base64url(signature)` where the
5
+ * signature is RSA-SHA256 over the dot-joined header+payload.
6
+ *
7
+ * We implement the verification inline (rather than pulling in
8
+ * `jsonwebtoken`) because it removes a runtime dependency and keeps
9
+ * the plugin install lean. The JWT format is small and stable.
10
+ */
11
+ export declare function verifyLicence(opts: VerifyLicenceOptions): LicenceStatus;
12
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE9E;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CA6DvE"}
package/dist/verify.js ADDED
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyLicence = verifyLicence;
4
+ const crypto_1 = require("crypto");
5
+ /**
6
+ * Verify a HULO licence JWT. The JWT uses the `RS256` algorithm:
7
+ * `base64url(header).base64url(payload).base64url(signature)` where the
8
+ * signature is RSA-SHA256 over the dot-joined header+payload.
9
+ *
10
+ * We implement the verification inline (rather than pulling in
11
+ * `jsonwebtoken`) because it removes a runtime dependency and keeps
12
+ * the plugin install lean. The JWT format is small and stable.
13
+ */
14
+ function verifyLicence(opts) {
15
+ const { licenceKey, pluginId, host, publicKey, revokedIds } = opts;
16
+ if (!licenceKey || !licenceKey.trim()) {
17
+ return missing();
18
+ }
19
+ const parts = licenceKey.trim().split('.');
20
+ if (parts.length !== 3)
21
+ return malformed();
22
+ const [headerB64, payloadB64, sigB64] = parts;
23
+ let header;
24
+ let payload;
25
+ try {
26
+ header = JSON.parse(base64UrlDecode(headerB64).toString('utf8'));
27
+ payload = JSON.parse(base64UrlDecode(payloadB64).toString('utf8'));
28
+ }
29
+ catch {
30
+ return malformed();
31
+ }
32
+ if (header.alg !== 'RS256' || header.typ !== 'JWT')
33
+ return malformed();
34
+ // Verify the RSA signature.
35
+ let signatureValid = false;
36
+ try {
37
+ const verifier = (0, crypto_1.createVerify)('RSA-SHA256');
38
+ verifier.update(`${headerB64}.${payloadB64}`);
39
+ verifier.end();
40
+ signatureValid = verifier.verify(publicKey, base64UrlDecode(sigB64));
41
+ }
42
+ catch {
43
+ signatureValid = false;
44
+ }
45
+ if (!signatureValid)
46
+ return badSignature(payload);
47
+ // Structural checks.
48
+ if (!payload.pluginId || payload.pluginId !== pluginId) {
49
+ return pluginMismatch(payload);
50
+ }
51
+ if (!Array.isArray(payload.allowedDomains))
52
+ return malformed();
53
+ const hostNorm = (host || '').toLowerCase().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
54
+ const matchesDomain = payload.allowedDomains.some(d => {
55
+ const dn = (d || '').toLowerCase().trim();
56
+ return dn === '*' || dn === hostNorm
57
+ || (dn.startsWith('*.') && hostNorm.endsWith(dn.slice(1)));
58
+ });
59
+ if (!matchesDomain)
60
+ return domainMismatch(payload, hostNorm);
61
+ const nowSec = Math.floor(Date.now() / 1000);
62
+ if (payload.exp && payload.exp < nowSec)
63
+ return expired(payload);
64
+ if (revokedIds && payload.jti && revokedIds.has(payload.jti)) {
65
+ return revoked(payload);
66
+ }
67
+ return {
68
+ valid: true,
69
+ reason: 'ok',
70
+ payload,
71
+ message: `Licensed to ${payload.customer} (${payload.plan}); expires ${formatExpiry(payload.exp)}.`,
72
+ };
73
+ }
74
+ // --- Status factories ------------------------------------------------------
75
+ function missing() {
76
+ return {
77
+ valid: false,
78
+ reason: 'missing',
79
+ payload: null,
80
+ message: 'No licence key configured. The plugin will run in unlicensed (degraded) mode.',
81
+ };
82
+ }
83
+ function malformed() {
84
+ return {
85
+ valid: false,
86
+ reason: 'malformed',
87
+ payload: null,
88
+ message: 'Licence key is malformed. Re-copy the key from your purchase confirmation.',
89
+ };
90
+ }
91
+ function badSignature(payload) {
92
+ return {
93
+ valid: false,
94
+ reason: 'bad-signature',
95
+ payload,
96
+ message: 'Licence signature failed verification. The key may be tampered with — contact support.',
97
+ };
98
+ }
99
+ function pluginMismatch(payload) {
100
+ return {
101
+ valid: false,
102
+ reason: 'plugin-mismatch',
103
+ payload,
104
+ message: `Licence is for "${payload.pluginId}" but the plugin checking is different — wrong key?`,
105
+ };
106
+ }
107
+ function domainMismatch(payload, host) {
108
+ return {
109
+ valid: false,
110
+ reason: 'domain-mismatch',
111
+ payload,
112
+ message: `Licence does not cover host "${host}". Allowed: ${payload.allowedDomains.join(', ')}.`,
113
+ };
114
+ }
115
+ function expired(payload) {
116
+ return {
117
+ valid: false,
118
+ reason: 'expired',
119
+ payload,
120
+ message: `Licence expired on ${formatExpiry(payload.exp)}. Renew to re-enable paid features.`,
121
+ };
122
+ }
123
+ function revoked(payload) {
124
+ return {
125
+ valid: false,
126
+ reason: 'revoked',
127
+ payload,
128
+ message: 'Licence has been revoked. Contact support if this is unexpected.',
129
+ };
130
+ }
131
+ // --- Helpers ---------------------------------------------------------------
132
+ function base64UrlDecode(s) {
133
+ const norm = s.replace(/-/g, '+').replace(/_/g, '/');
134
+ const pad = norm.length % 4 ? norm + '='.repeat(4 - (norm.length % 4)) : norm;
135
+ return Buffer.from(pad, 'base64');
136
+ }
137
+ function formatExpiry(secSinceEpoch) {
138
+ if (!secSinceEpoch)
139
+ return 'never';
140
+ return new Date(secSinceEpoch * 1000).toISOString().slice(0, 10);
141
+ }
142
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":";;AAYA,sCA6DC;AAzED,mCAAsC;AAGtC;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAAC,IAA0B;IACpD,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAEnE,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;QACpC,OAAO,OAAO,EAAE,CAAC;IACrB,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,EAAE,CAAC;IAE3C,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAC9C,IAAI,MAAW,CAAC;IAChB,IAAI,OAAuB,CAAC;IAC5B,IAAI,CAAC;QACD,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACjE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,EAAE,CAAC;IACvB,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,IAAI,MAAM,CAAC,GAAG,KAAK,KAAK;QAAE,OAAO,SAAS,EAAE,CAAC;IAEvE,4BAA4B;IAC5B,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,IAAA,qBAAY,EAAC,YAAY,CAAC,CAAC;QAC5C,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC,CAAC;QAC9C,QAAQ,CAAC,GAAG,EAAE,CAAC;QACf,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACL,cAAc,GAAG,KAAK,CAAC;IAC3B,CAAC;IACD,IAAI,CAAC,cAAc;QAAE,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;IAElD,qBAAqB;IACrB,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACrD,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC;QAAE,OAAO,SAAS,EAAE,CAAC;IAE/D,MAAM,QAAQ,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC7F,MAAM,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAClD,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QAC1C,OAAO,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,QAAQ;eAC7B,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,aAAa;QAAE,OAAO,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAE7D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,GAAG,MAAM;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IAEjE,IAAI,UAAU,IAAI,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO;QACH,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,IAAI;QACZ,OAAO;QACP,OAAO,EAAE,eAAe,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,IAAI,cAAc,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG;KACtG,CAAC;AACN,CAAC;AAED,8EAA8E;AAE9E,SAAS,OAAO;IACZ,OAAO;QACH,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,+EAA+E;KAC3F,CAAC;AACN,CAAC;AACD,SAAS,SAAS;IACd,OAAO;QACH,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,4EAA4E;KACxF,CAAC;AACN,CAAC;AACD,SAAS,YAAY,CAAC,OAAuB;IACzC,OAAO;QACH,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,eAAe;QACvB,OAAO;QACP,OAAO,EAAE,wFAAwF;KACpG,CAAC;AACN,CAAC;AACD,SAAS,cAAc,CAAC,OAAuB;IAC3C,OAAO;QACH,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,iBAAiB;QACzB,OAAO;QACP,OAAO,EAAE,mBAAmB,OAAO,CAAC,QAAQ,qDAAqD;KACpG,CAAC;AACN,CAAC;AACD,SAAS,cAAc,CAAC,OAAuB,EAAE,IAAY;IACzD,OAAO;QACH,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,iBAAiB;QACzB,OAAO;QACP,OAAO,EAAE,gCAAgC,IAAI,eAAe,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KACnG,CAAC;AACN,CAAC;AACD,SAAS,OAAO,CAAC,OAAuB;IACpC,OAAO;QACH,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,SAAS;QACjB,OAAO;QACP,OAAO,EAAE,sBAAsB,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,qCAAqC;KAChG,CAAC;AACN,CAAC;AACD,SAAS,OAAO,CAAC,OAAuB;IACpC,OAAO;QACH,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,SAAS;QACjB,OAAO;QACP,OAAO,EAAE,kEAAkE;KAC9E,CAAC;AACN,CAAC;AAED,8EAA8E;AAE9E,SAAS,eAAe,CAAC,CAAS;IAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9E,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,YAAY,CAAC,aAAqB;IACvC,IAAI,CAAC,aAAa;QAAE,OAAO,OAAO,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@huloglobal/vendure-licence-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Licence validation helpers for HULO Vendure plugins. JWT verification, revocation polling and an admin status check used by paid HULO plugins to confirm the host installation is licensed.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "Wayne Garrison <wayne@garrison.me.uk>",
7
+ "homepage": "https://github.com/exceeded/vendure-licence-sdk",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/exceeded/vendure-licence-sdk.git"
11
+ },
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "CHANGELOG.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "prepublishOnly": "yarn build"
23
+ },
24
+ "peerDependencies": {
25
+ "@vendure/core": ">=3.0.0",
26
+ "@nestjs/common": ">=10.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@nestjs/common": "^10.0.0",
30
+ "@vendure/core": "^3.6.0",
31
+ "typescript": "^5.4.0"
32
+ },
33
+ "keywords": [
34
+ "vendure",
35
+ "vendure-plugin",
36
+ "licence",
37
+ "license"
38
+ ]
39
+ }