@ansh-ui/license 0.2.0 → 0.3.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/dist/index.cjs +115 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -42
- package/dist/index.d.ts +13 -42
- package/dist/index.js +115 -56
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2,8 +2,118 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@ansh-ui/core');
|
|
4
4
|
|
|
5
|
-
// src/
|
|
5
|
+
// src/base64url.ts
|
|
6
|
+
function base64UrlDecode(input) {
|
|
7
|
+
const normalised = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
8
|
+
const padded = normalised + "=".repeat((4 - normalised.length % 4) % 4);
|
|
9
|
+
if (typeof atob === "function") {
|
|
10
|
+
const binary = atob(padded);
|
|
11
|
+
const bytes = new Uint8Array(binary.length);
|
|
12
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
13
|
+
return bytes;
|
|
14
|
+
}
|
|
15
|
+
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
16
|
+
}
|
|
17
|
+
var PUBLIC_KEY_B64 = "ZnSy7P02L2gBSMrvugpEg0cuLMq1MIv2jMi6wnxgLZs" ;
|
|
6
18
|
var KEY_PREFIX = "aur_";
|
|
19
|
+
|
|
20
|
+
// src/verify.ts
|
|
21
|
+
var LICENSING_URL = "https://ansh.techiesofepi.com";
|
|
22
|
+
var EDITIONS = ["free", "pro", "enterprise"];
|
|
23
|
+
function verifyLicense(key) {
|
|
24
|
+
return verifyLicenseWithPublicKey(key, PUBLIC_KEY_B64);
|
|
25
|
+
}
|
|
26
|
+
async function verifyLicenseWithPublicKey(key, publicKeyB64) {
|
|
27
|
+
const trimmed = key.trim();
|
|
28
|
+
if (!trimmed.startsWith(KEY_PREFIX)) {
|
|
29
|
+
return fail(`Key does not start with "${KEY_PREFIX}". Check for a copy-paste error.`);
|
|
30
|
+
}
|
|
31
|
+
const body = trimmed.slice(KEY_PREFIX.length);
|
|
32
|
+
const separator = body.lastIndexOf(".");
|
|
33
|
+
if (separator <= 0) {
|
|
34
|
+
return fail("Key is missing its signature separator. It may be truncated.");
|
|
35
|
+
}
|
|
36
|
+
const payloadPart = body.slice(0, separator);
|
|
37
|
+
const signaturePart = body.slice(separator + 1);
|
|
38
|
+
let payload;
|
|
39
|
+
let payloadBytes;
|
|
40
|
+
try {
|
|
41
|
+
payloadBytes = base64UrlDecode(payloadPart);
|
|
42
|
+
payload = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
43
|
+
} catch {
|
|
44
|
+
return fail("Key payload is not valid base64url-encoded JSON.");
|
|
45
|
+
}
|
|
46
|
+
let signatureValid;
|
|
47
|
+
try {
|
|
48
|
+
signatureValid = await verifySignature(
|
|
49
|
+
publicKeyB64,
|
|
50
|
+
payloadBytes,
|
|
51
|
+
base64UrlDecode(signaturePart)
|
|
52
|
+
);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return fail(
|
|
55
|
+
`Signature verification is unavailable in this runtime (${error instanceof Error ? error.message : "unknown error"}). Ed25519 via Web Crypto requires Node 18.4+, Chrome 137+, Firefox 129+ or Safari 17+. Verify at build time instead if you must support older runtimes.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (!signatureValid) {
|
|
59
|
+
return fail("Signature does not match. The key has been modified or is not genuine.");
|
|
60
|
+
}
|
|
61
|
+
if (!payload || typeof payload !== "object" || !EDITIONS.includes(payload.edition)) {
|
|
62
|
+
return fail("Key payload names an unknown edition. It may be from a newer release.");
|
|
63
|
+
}
|
|
64
|
+
if (payload.expires !== null) {
|
|
65
|
+
const expiresAt = new Date(payload.expires).getTime();
|
|
66
|
+
if (Number.isNaN(expiresAt)) {
|
|
67
|
+
return fail(`Key payload has an unreadable expiry date ("${String(payload.expires)}").`);
|
|
68
|
+
}
|
|
69
|
+
if (expiresAt < Date.now()) {
|
|
70
|
+
return {
|
|
71
|
+
valid: false,
|
|
72
|
+
payload,
|
|
73
|
+
edition: "free",
|
|
74
|
+
expired: true,
|
|
75
|
+
reason: `Licence expired on ${payload.expires}. Previously-installed versions continue to work; renew to access newer releases.`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { valid: true, payload, edition: payload.edition };
|
|
80
|
+
}
|
|
81
|
+
function inspectLicense(key) {
|
|
82
|
+
try {
|
|
83
|
+
const body = key.trim().slice(KEY_PREFIX.length);
|
|
84
|
+
const separator = body.lastIndexOf(".");
|
|
85
|
+
if (separator <= 0) return null;
|
|
86
|
+
return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))));
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
var importedKeys = /* @__PURE__ */ new Map();
|
|
92
|
+
async function verifySignature(publicKeyB64, payload, signature) {
|
|
93
|
+
const subtle = globalThis.crypto?.subtle;
|
|
94
|
+
if (!subtle) throw new Error("Web Crypto is unavailable");
|
|
95
|
+
let key = importedKeys.get(publicKeyB64);
|
|
96
|
+
if (!key) {
|
|
97
|
+
key = subtle.importKey(
|
|
98
|
+
"raw",
|
|
99
|
+
base64UrlDecode(publicKeyB64),
|
|
100
|
+
{ name: "Ed25519" },
|
|
101
|
+
false,
|
|
102
|
+
["verify"]
|
|
103
|
+
);
|
|
104
|
+
importedKeys.set(publicKeyB64, key);
|
|
105
|
+
key.catch(() => importedKeys.delete(publicKeyB64));
|
|
106
|
+
}
|
|
107
|
+
return subtle.verify(
|
|
108
|
+
{ name: "Ed25519" },
|
|
109
|
+
await key,
|
|
110
|
+
signature,
|
|
111
|
+
payload
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
function fail(reason) {
|
|
115
|
+
return { valid: false, payload: null, edition: "free", reason };
|
|
116
|
+
}
|
|
7
117
|
var EDITION_CAPABILITIES = {
|
|
8
118
|
free: ["effect:core", "behaviour:core", "renderer:canvas2d"],
|
|
9
119
|
pro: [
|
|
@@ -28,37 +138,10 @@ var EDITION_CAPABILITIES = {
|
|
|
28
138
|
};
|
|
29
139
|
var activeEdition = "free";
|
|
30
140
|
var activePayload = null;
|
|
31
|
-
async function verifyLicense(key) {
|
|
32
|
-
const trimmed = key.trim();
|
|
33
|
-
if (!trimmed.startsWith(KEY_PREFIX)) {
|
|
34
|
-
return fail(`Key does not start with "${KEY_PREFIX}". Check for a copy-paste error.`);
|
|
35
|
-
}
|
|
36
|
-
const body = trimmed.slice(KEY_PREFIX.length);
|
|
37
|
-
const separator = body.lastIndexOf(".");
|
|
38
|
-
if (separator <= 0) {
|
|
39
|
-
return fail("Key is missing its signature separator. It may be truncated.");
|
|
40
|
-
}
|
|
41
|
-
const payloadPart = body.slice(0, separator);
|
|
42
|
-
body.slice(separator + 1);
|
|
43
|
-
let payload;
|
|
44
|
-
let payloadBytes;
|
|
45
|
-
try {
|
|
46
|
-
payloadBytes = base64UrlDecode(payloadPart);
|
|
47
|
-
payload = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
48
|
-
} catch {
|
|
49
|
-
return fail("Key payload is not valid base64url-encoded JSON.");
|
|
50
|
-
}
|
|
51
|
-
{
|
|
52
|
-
return {
|
|
53
|
-
valid: false,
|
|
54
|
-
payload,
|
|
55
|
-
edition: "free",
|
|
56
|
-
reason: "License verification is unconfigured in this build of @ansh-ui/license. This is not a problem with your key. See https://ansh.dev/docs/licensing"
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
141
|
async function activateLicense(key) {
|
|
61
|
-
|
|
142
|
+
return applyVerification(await verifyLicense(key));
|
|
143
|
+
}
|
|
144
|
+
function applyVerification(result) {
|
|
62
145
|
if (!result.valid) {
|
|
63
146
|
console.warn(`[ansh] Licence not activated: ${result.reason}`);
|
|
64
147
|
return result;
|
|
@@ -85,7 +168,7 @@ function useFreeEdition() {
|
|
|
85
168
|
for (const capability of paid) {
|
|
86
169
|
registry.revoke(
|
|
87
170
|
capability,
|
|
88
|
-
|
|
171
|
+
`This component requires Ansh Pro. See ${LICENSING_URL} to compare editions.`
|
|
89
172
|
);
|
|
90
173
|
}
|
|
91
174
|
}
|
|
@@ -98,30 +181,6 @@ function currentLicense() {
|
|
|
98
181
|
function hasCapability(capability) {
|
|
99
182
|
return core.getRegistry().supports(capability);
|
|
100
183
|
}
|
|
101
|
-
function base64UrlDecode(input) {
|
|
102
|
-
const normalised = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
103
|
-
const padded = normalised + "=".repeat((4 - normalised.length % 4) % 4);
|
|
104
|
-
if (typeof atob === "function") {
|
|
105
|
-
const binary = atob(padded);
|
|
106
|
-
const bytes = new Uint8Array(binary.length);
|
|
107
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
108
|
-
return bytes;
|
|
109
|
-
}
|
|
110
|
-
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
111
|
-
}
|
|
112
|
-
function fail(reason) {
|
|
113
|
-
return { valid: false, payload: null, edition: "free", reason };
|
|
114
|
-
}
|
|
115
|
-
function inspectLicense(key) {
|
|
116
|
-
try {
|
|
117
|
-
const body = key.trim().slice(KEY_PREFIX.length);
|
|
118
|
-
const separator = body.lastIndexOf(".");
|
|
119
|
-
if (separator <= 0) return null;
|
|
120
|
-
return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))));
|
|
121
|
-
} catch {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
184
|
|
|
126
185
|
exports.EDITION_CAPABILITIES = EDITION_CAPABILITIES;
|
|
127
186
|
exports.activateLicense = activateLicense;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["getRegistry"],"mappings":"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","file":"index.cjs","sourcesContent":["import { getRegistry } from '@ansh-ui/core'\n\n/**\n * Edition gating and license key verification.\n *\n * ## What this is, honestly\n *\n * This is **not DRM, and it cannot be.** Any check that runs in a browser can be removed by editing\n * the bundle, and any check that runs at build time can be removed by patching the package. Nothing\n * shipped as JavaScript to a client is enforceable. Pretending otherwise would mean building\n * obfuscation and phone-home telemetry that hurts paying customers — slower builds, offline CI\n * failures, privacy concerns — while stopping nobody who was determined.\n *\n * So the design goal is different: **make it easy and obvious to be correctly licensed.** Concretely:\n *\n * - A signed key proves an entitlement without any network call, so CI works offline and no usage\n * data leaves the customer's machine.\n * - Pro components live behind a capability check that produces a clear, linkable error instead of\n * a mysteriously blank canvas.\n * - In development, an unlicensed Pro component renders a visible watermark and logs once. In\n * production it throws at mount. Neither is a puzzle to diagnose.\n * - Pro code lives in a separate entry point, so a Free-edition bundle never contains it. That is\n * a genuine benefit for Free users — smaller bundles — rather than a restriction.\n *\n * Enforcement, where it matters, happens at the point of sale and in the license agreement. This\n * module's job is to keep honest customers out of trouble.\n *\n * ## Key format\n *\n * `aur_<base64url(payload)>.<base64url(signature)>`\n *\n * The payload is JSON, so a customer can inspect exactly what they were issued — expiry, seats,\n * edition — without a tool. The signature is Ed25519 over the payload bytes.\n */\n\nexport type Edition = 'free' | 'pro' | 'enterprise'\n\nexport interface LicensePayload {\n /** Who the licence was issued to. Shown in diagnostics. */\n licensee: string\n edition: Edition\n /** ISO 8601 issue date. */\n issued: string\n /**\n * ISO 8601 expiry, or `null` for a perpetual licence.\n *\n * A perpetual licence for a version range is the honest model for a component library: the code a\n * customer already installed keeps working forever, and the licence governs access to *new*\n * versions. A licence that switches off working software is hostile.\n */\n expires: string | null\n /** Number of developer seats. Informational — not enforced at runtime. */\n seats?: number\n /** Capabilities this key grants beyond the edition's defaults. */\n grants?: string[]\n /** Domains the licence covers. Informational. */\n domains?: string[]\n}\n\nexport interface VerificationResult {\n valid: boolean\n payload: LicensePayload | null\n edition: Edition\n /** Human-readable explanation when `valid` is false. */\n reason?: string\n /** `true` when the signature verified but the licence has expired. */\n expired?: boolean\n}\n\n/**\n * The Ansh public signing key, raw Ed25519 (32 bytes), base64url.\n *\n * Public by definition — it can only verify, never sign. Shipping it in the package is what makes\n * offline verification possible.\n *\n * This placeholder is the all-zero key, which verifies nothing. It is replaced at release time by\n * the real key; until then `verifyLicense` reports `unconfigured` rather than silently accepting or\n * rejecting everything, so an integration bug can't be mistaken for a licensing one.\n */\nconst PUBLIC_KEY_B64 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\n\nconst KEY_PREFIX = 'aur_'\n\n/** Capabilities each edition grants. */\nconst EDITION_CAPABILITIES: Record<Edition, readonly string[]> = {\n free: ['effect:core', 'behaviour:core', 'renderer:canvas2d'],\n pro: [\n 'effect:core',\n 'effect:pro',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n ],\n enterprise: [\n 'effect:core',\n 'effect:pro',\n 'effect:enterprise',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n 'renderer:webgpu',\n 'worker',\n ],\n}\n\nlet activeEdition: Edition = 'free'\nlet activePayload: LicensePayload | null = null\n\n/**\n * Verify a licence key.\n *\n * Async because Web Crypto is async. Call it once at application startup (or in a build plugin) and\n * `await` it — the result is cached in module state, so components check a boolean rather than\n * re-verifying.\n *\n * Never throws. A malformed key, an unsupported runtime and a genuinely invalid signature all\n * produce a `VerificationResult` with a `reason`, because a licence problem must not be able to\n * crash an application.\n */\nexport async function verifyLicense(key: string): Promise<VerificationResult> {\n const trimmed = key.trim()\n\n if (!trimmed.startsWith(KEY_PREFIX)) {\n return fail(`Key does not start with \"${KEY_PREFIX}\". Check for a copy-paste error.`)\n }\n\n const body = trimmed.slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) {\n return fail('Key is missing its signature separator. It may be truncated.')\n }\n\n const payloadPart = body.slice(0, separator)\n const signaturePart = body.slice(separator + 1)\n\n let payload: LicensePayload\n let payloadBytes: Uint8Array\n try {\n payloadBytes = base64UrlDecode(payloadPart)\n payload = JSON.parse(new TextDecoder().decode(payloadBytes)) as LicensePayload\n } catch {\n return fail('Key payload is not valid base64url-encoded JSON.')\n }\n\n if (PUBLIC_KEY_B64 === 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {\n // Distinguish \"we haven't shipped a signing key yet\" from \"your key is bad\". Conflating the two\n // would send customers to support for a problem that is entirely ours.\n return {\n valid: false,\n payload,\n edition: 'free',\n reason:\n 'License verification is unconfigured in this build of @ansh-ui/license. ' +\n 'This is not a problem with your key. See https://ansh.dev/docs/licensing',\n }\n }\n\n let signatureValid: boolean\n try {\n signatureValid = await verifySignature(payloadBytes, base64UrlDecode(signaturePart))\n } catch (error) {\n return fail(\n `Signature verification is unavailable in this runtime ` +\n `(${error instanceof Error ? error.message : 'unknown error'}). ` +\n `Ed25519 via Web Crypto requires Node 18.4+, Chrome 137+, Firefox 129+ or Safari 17+. ` +\n `Verify at build time instead if you must support older runtimes.`,\n )\n }\n\n if (!signatureValid) {\n return fail('Signature does not match. The key has been modified or is not genuine.')\n }\n\n const expired = payload.expires !== null && new Date(payload.expires).getTime() < Date.now()\n if (expired) {\n return {\n valid: false,\n payload,\n edition: 'free',\n expired: true,\n reason:\n `Licence expired on ${payload.expires}. ` +\n `Previously-installed versions continue to work; renew to access newer releases.`,\n }\n }\n\n return { valid: true, payload, edition: payload.edition }\n}\n\n/**\n * Activate a licence, granting its capabilities in the registry.\n *\n * This is the single call an application makes. After it resolves, Pro components mount normally\n * and `registry.requireCapability('effect:pro')` succeeds.\n *\n * ```ts\n * import { activateLicense } from '@ansh-ui/license'\n * await activateLicense(process.env.ANSH_LICENSE_KEY!)\n * ```\n */\nexport async function activateLicense(key: string): Promise<VerificationResult> {\n const result = await verifyLicense(key)\n\n if (!result.valid) {\n console.warn(`[ansh] Licence not activated: ${result.reason}`)\n return result\n }\n\n activeEdition = result.edition\n activePayload = result.payload\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES[result.edition]) {\n registry.provide(capability)\n }\n for (const grant of result.payload?.grants ?? []) {\n registry.provide(grant)\n }\n\n return result\n}\n\n/**\n * Apply the Free edition explicitly.\n *\n * Called automatically the first time a capability is checked without activation, so Free works with\n * no setup at all. Exposed for tests and for resetting between app instances.\n */\nexport function useFreeEdition(): void {\n activeEdition = 'free'\n activePayload = null\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES.free) registry.provide(capability)\n\n // Revoke paid capabilities with a message that says what to do, rather than leaving the generic\n // \"missing capability\" error to speak for itself.\n const paid = [\n ...new Set([...EDITION_CAPABILITIES.pro, ...EDITION_CAPABILITIES.enterprise]),\n ].filter((capability) => !EDITION_CAPABILITIES.free.includes(capability))\n\n for (const capability of paid) {\n registry.revoke(\n capability,\n 'This component requires Ansh Pro. See https://ansh.dev/pro to compare editions.',\n )\n }\n}\n\n/** The currently active edition. */\nexport function currentEdition(): Edition {\n return activeEdition\n}\n\n/** The active licence payload, or `null` on Free. */\nexport function currentLicense(): LicensePayload | null {\n return activePayload\n}\n\n/** Is a capability available under the active edition? */\nexport function hasCapability(capability: string): boolean {\n return getRegistry().supports(capability)\n}\n\n// ---------------------------------------------------------------------------\n// Crypto\n// ---------------------------------------------------------------------------\n\nlet cachedKey: CryptoKey | null = null\n\nasync function verifySignature(payload: Uint8Array, signature: Uint8Array): Promise<boolean> {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) throw new Error('Web Crypto is unavailable')\n\n if (!cachedKey) {\n cachedKey = await subtle.importKey(\n 'raw',\n base64UrlDecode(PUBLIC_KEY_B64) as unknown as ArrayBuffer,\n { name: 'Ed25519' },\n false,\n ['verify'],\n )\n }\n\n return subtle.verify(\n { name: 'Ed25519' },\n cachedKey,\n signature as unknown as ArrayBuffer,\n payload as unknown as ArrayBuffer,\n )\n}\n\n/**\n * base64url → bytes.\n *\n * Hand-rolled rather than using `atob` because base64url uses `-`/`_` instead of `+`/`/` and omits\n * padding, and because `atob` does not exist in every Node context we might run in.\n */\nfunction base64UrlDecode(input: string): Uint8Array {\n const normalised = input.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalised + '='.repeat((4 - (normalised.length % 4)) % 4)\n\n if (typeof atob === 'function') {\n const binary = atob(padded)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n }\n\n // Node without a global `atob` (very old versions, or a stripped runtime).\n return new Uint8Array(Buffer.from(padded, 'base64'))\n}\n\nfunction fail(reason: string): VerificationResult {\n return { valid: false, payload: null, edition: 'free', reason }\n}\n\n/**\n * Decode a key's payload **without verifying it**.\n *\n * For diagnostics only — printing \"your licence expires on X\" in a CLI, for instance. Never gate on\n * this: the payload is unauthenticated and trivially forged.\n */\nexport function inspectLicense(key: string): LicensePayload | null {\n try {\n const body = key.trim().slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) return null\n return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))))\n } catch {\n return null\n }\n}\n\nexport { EDITION_CAPABILITIES }\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/base64url.ts","../src/public-key.ts","../src/verify.ts","../src/activation.ts"],"names":["getRegistry"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * base64url → bytes.\n *\n * Hand-rolled rather than using `atob` because base64url uses `-`/`_` instead of `+`/`/` and omits\n * padding, and because `atob` does not exist in every Node context we might run in.\n */\nexport function base64UrlDecode(input: string): Uint8Array {\n const normalised = input.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalised + '='.repeat((4 - (normalised.length % 4)) % 4)\n\n if (typeof atob === 'function') {\n const binary = atob(padded)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n }\n\n // Node without a global `atob` (very old versions, or a stripped runtime).\n return new Uint8Array(Buffer.from(padded, 'base64'))\n}\n","/**\n * The Ansh public signing key, raw Ed25519 (32 bytes), base64url.\n *\n * Public by definition — it can only verify, never sign. Shipping it in the package is what makes\n * offline verification possible.\n *\n * The real key is injected at build time: `tsup.config.ts` replaces `__ANSH_LICENSE_PUBLIC_KEY__`\n * with the `ANSH_LICENSE_PUBLIC_KEY` environment variable, which `release.yml` sets from a repository\n * secret. A build without it falls back to the all-zero placeholder, which verifies nothing, and\n * `verifyLicense` then reports `unconfigured` rather than silently accepting or rejecting everything,\n * so an integration bug can't be mistaken for a licensing one. `scripts/check-license-key.mjs`\n * refuses to publish a placeholder build.\n */\n\ndeclare const __ANSH_LICENSE_PUBLIC_KEY__: string | undefined\n\nexport const PLACEHOLDER_PUBLIC_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\n\n/** `typeof` guard so running from source (tests, the docs site) works without the define. */\nexport const PUBLIC_KEY_B64: string =\n typeof __ANSH_LICENSE_PUBLIC_KEY__ === 'string' && __ANSH_LICENSE_PUBLIC_KEY__ !== ''\n ? __ANSH_LICENSE_PUBLIC_KEY__\n : PLACEHOLDER_PUBLIC_KEY\n\nexport const KEY_PREFIX = 'aur_'\n","import { base64UrlDecode } from './base64url'\nimport { KEY_PREFIX, PLACEHOLDER_PUBLIC_KEY, PUBLIC_KEY_B64 } from './public-key'\nimport type { Edition, LicensePayload, VerificationResult } from './types'\n\n/** Where licence problems send people. The pricing section lives on the landing page. */\nexport const LICENSING_URL = 'https://ansh.techiesofepi.com'\n\nconst EDITIONS: readonly Edition[] = ['free', 'pro', 'enterprise']\n\n/**\n * Verify a licence key.\n *\n * Async because Web Crypto is async. Call it once at application startup (or in a build plugin) and\n * `await` it — the result is cached in module state, so components check a boolean rather than\n * re-verifying.\n *\n * Never throws. A malformed key, an unsupported runtime and a genuinely invalid signature all\n * produce a `VerificationResult` with a `reason`, because a licence problem must not be able to\n * crash an application.\n */\nexport function verifyLicense(key: string): Promise<VerificationResult> {\n return verifyLicenseWithPublicKey(key, PUBLIC_KEY_B64)\n}\n\n/**\n * {@link verifyLicense} against an explicit public key.\n *\n * Not re-exported from the package root: the shipped key is the only one a consumer should trust.\n * It exists so the test suite can sign with a throwaway keypair.\n */\nexport async function verifyLicenseWithPublicKey(\n key: string,\n publicKeyB64: string,\n): Promise<VerificationResult> {\n const trimmed = key.trim()\n\n if (!trimmed.startsWith(KEY_PREFIX)) {\n return fail(`Key does not start with \"${KEY_PREFIX}\". Check for a copy-paste error.`)\n }\n\n const body = trimmed.slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) {\n return fail('Key is missing its signature separator. It may be truncated.')\n }\n\n const payloadPart = body.slice(0, separator)\n const signaturePart = body.slice(separator + 1)\n\n let payload: LicensePayload\n let payloadBytes: Uint8Array\n try {\n payloadBytes = base64UrlDecode(payloadPart)\n payload = JSON.parse(new TextDecoder().decode(payloadBytes)) as LicensePayload\n } catch {\n return fail('Key payload is not valid base64url-encoded JSON.')\n }\n\n if (publicKeyB64 === PLACEHOLDER_PUBLIC_KEY) {\n // Distinguish \"we haven't shipped a signing key yet\" from \"your key is bad\". Conflating the two\n // would send customers to support for a problem that is entirely ours.\n return {\n valid: false,\n payload,\n edition: 'free',\n reason:\n 'License verification is unconfigured in this build of @ansh-ui/license. ' +\n `This is not a problem with your key. See ${LICENSING_URL}`,\n }\n }\n\n let signatureValid: boolean\n try {\n signatureValid = await verifySignature(\n publicKeyB64,\n payloadBytes,\n base64UrlDecode(signaturePart),\n )\n } catch (error) {\n return fail(\n `Signature verification is unavailable in this runtime ` +\n `(${error instanceof Error ? error.message : 'unknown error'}). ` +\n `Ed25519 via Web Crypto requires Node 18.4+, Chrome 137+, Firefox 129+ or Safari 17+. ` +\n `Verify at build time instead if you must support older runtimes.`,\n )\n }\n\n if (!signatureValid) {\n return fail('Signature does not match. The key has been modified or is not genuine.')\n }\n\n // The signature proves we issued these bytes, not that they're well-formed. Checked after\n // verification so a forged payload can't probe which shapes we accept.\n if (!payload || typeof payload !== 'object' || !EDITIONS.includes(payload.edition)) {\n return fail('Key payload names an unknown edition. It may be from a newer release.')\n }\n\n if (payload.expires !== null) {\n const expiresAt = new Date(payload.expires).getTime()\n // An unparseable date would otherwise compare as NaN and silently never expire.\n if (Number.isNaN(expiresAt)) {\n return fail(`Key payload has an unreadable expiry date (\"${String(payload.expires)}\").`)\n }\n if (expiresAt < Date.now()) {\n return {\n valid: false,\n payload,\n edition: 'free',\n expired: true,\n reason:\n `Licence expired on ${payload.expires}. ` +\n `Previously-installed versions continue to work; renew to access newer releases.`,\n }\n }\n }\n\n return { valid: true, payload, edition: payload.edition }\n}\n\n/**\n * Decode a key's payload **without verifying it**.\n *\n * For diagnostics only — printing \"your licence expires on X\" in a CLI, for instance. Never gate on\n * this: the payload is unauthenticated and trivially forged.\n */\nexport function inspectLicense(key: string): LicensePayload | null {\n try {\n const body = key.trim().slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) return null\n return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))))\n } catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Crypto\n// ---------------------------------------------------------------------------\n\nconst importedKeys = new Map<string, Promise<CryptoKey>>()\n\nasync function verifySignature(\n publicKeyB64: string,\n payload: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) throw new Error('Web Crypto is unavailable')\n\n let key = importedKeys.get(publicKeyB64)\n if (!key) {\n key = subtle.importKey(\n 'raw',\n base64UrlDecode(publicKeyB64) as unknown as ArrayBuffer,\n { name: 'Ed25519' },\n false,\n ['verify'],\n )\n importedKeys.set(publicKeyB64, key)\n // A failed import must not poison the cache for the next attempt.\n key.catch(() => importedKeys.delete(publicKeyB64))\n }\n\n return subtle.verify(\n { name: 'Ed25519' },\n await key,\n signature as unknown as ArrayBuffer,\n payload as unknown as ArrayBuffer,\n )\n}\n\nfunction fail(reason: string): VerificationResult {\n return { valid: false, payload: null, edition: 'free', reason }\n}\n","import { getRegistry } from '@ansh-ui/core'\nimport type { Edition, LicensePayload, VerificationResult } from './types'\nimport { LICENSING_URL, verifyLicense } from './verify'\n\n/** Capabilities each edition grants. */\nexport const EDITION_CAPABILITIES: Record<Edition, readonly string[]> = {\n free: ['effect:core', 'behaviour:core', 'renderer:canvas2d'],\n pro: [\n 'effect:core',\n 'effect:pro',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n ],\n enterprise: [\n 'effect:core',\n 'effect:pro',\n 'effect:enterprise',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n 'renderer:webgpu',\n 'worker',\n ],\n}\n\nlet activeEdition: Edition = 'free'\nlet activePayload: LicensePayload | null = null\n\n/**\n * Activate a licence, granting its capabilities in the registry.\n *\n * This is the single call an application makes. After it resolves, Pro components mount normally\n * and `registry.requireCapability('effect:pro')` succeeds.\n *\n * ```ts\n * import { activateLicense } from '@ansh-ui/license'\n * await activateLicense(process.env.ANSH_LICENSE_KEY!)\n * ```\n */\nexport async function activateLicense(key: string): Promise<VerificationResult> {\n return applyVerification(await verifyLicense(key))\n}\n\n/** The registry half of {@link activateLicense}, split out so tests can feed it a result. */\nexport function applyVerification(result: VerificationResult): VerificationResult {\n if (!result.valid) {\n console.warn(`[ansh] Licence not activated: ${result.reason}`)\n return result\n }\n\n activeEdition = result.edition\n activePayload = result.payload\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES[result.edition]) {\n registry.provide(capability)\n }\n for (const grant of result.payload?.grants ?? []) {\n registry.provide(grant)\n }\n\n return result\n}\n\n/**\n * Apply the Free edition explicitly.\n *\n * Called automatically the first time a capability is checked without activation, so Free works with\n * no setup at all. Exposed for tests and for resetting between app instances.\n */\nexport function useFreeEdition(): void {\n activeEdition = 'free'\n activePayload = null\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES.free) registry.provide(capability)\n\n // Revoke paid capabilities with a message that says what to do, rather than leaving the generic\n // \"missing capability\" error to speak for itself.\n const paid = [\n ...new Set([...EDITION_CAPABILITIES.pro, ...EDITION_CAPABILITIES.enterprise]),\n ].filter((capability) => !EDITION_CAPABILITIES.free.includes(capability))\n\n for (const capability of paid) {\n registry.revoke(\n capability,\n `This component requires Ansh Pro. See ${LICENSING_URL} to compare editions.`,\n )\n }\n}\n\n/** The currently active edition. */\nexport function currentEdition(): Edition {\n return activeEdition\n}\n\n/** The active licence payload, or `null` on Free. */\nexport function currentLicense(): LicensePayload | null {\n return activePayload\n}\n\n/** Is a capability available under the active edition? */\nexport function hasCapability(capability: string): boolean {\n return getRegistry().supports(capability)\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edition gating and license key verification.
|
|
3
|
-
*
|
|
4
|
-
* ## What this is, honestly
|
|
5
|
-
*
|
|
6
|
-
* This is **not DRM, and it cannot be.** Any check that runs in a browser can be removed by editing
|
|
7
|
-
* the bundle, and any check that runs at build time can be removed by patching the package. Nothing
|
|
8
|
-
* shipped as JavaScript to a client is enforceable. Pretending otherwise would mean building
|
|
9
|
-
* obfuscation and phone-home telemetry that hurts paying customers — slower builds, offline CI
|
|
10
|
-
* failures, privacy concerns — while stopping nobody who was determined.
|
|
11
|
-
*
|
|
12
|
-
* So the design goal is different: **make it easy and obvious to be correctly licensed.** Concretely:
|
|
13
|
-
*
|
|
14
|
-
* - A signed key proves an entitlement without any network call, so CI works offline and no usage
|
|
15
|
-
* data leaves the customer's machine.
|
|
16
|
-
* - Pro components live behind a capability check that produces a clear, linkable error instead of
|
|
17
|
-
* a mysteriously blank canvas.
|
|
18
|
-
* - In development, an unlicensed Pro component renders a visible watermark and logs once. In
|
|
19
|
-
* production it throws at mount. Neither is a puzzle to diagnose.
|
|
20
|
-
* - Pro code lives in a separate entry point, so a Free-edition bundle never contains it. That is
|
|
21
|
-
* a genuine benefit for Free users — smaller bundles — rather than a restriction.
|
|
22
|
-
*
|
|
23
|
-
* Enforcement, where it matters, happens at the point of sale and in the license agreement. This
|
|
24
|
-
* module's job is to keep honest customers out of trouble.
|
|
25
|
-
*
|
|
26
|
-
* ## Key format
|
|
27
|
-
*
|
|
28
|
-
* `aur_<base64url(payload)>.<base64url(signature)>`
|
|
29
|
-
*
|
|
30
|
-
* The payload is JSON, so a customer can inspect exactly what they were issued — expiry, seats,
|
|
31
|
-
* edition — without a tool. The signature is Ed25519 over the payload bytes.
|
|
32
|
-
*/
|
|
33
1
|
type Edition = 'free' | 'pro' | 'enterprise';
|
|
34
2
|
interface LicensePayload {
|
|
35
3
|
/** Who the licence was issued to. Shown in diagnostics. */
|
|
@@ -61,8 +29,7 @@ interface VerificationResult {
|
|
|
61
29
|
/** `true` when the signature verified but the licence has expired. */
|
|
62
30
|
expired?: boolean;
|
|
63
31
|
}
|
|
64
|
-
|
|
65
|
-
declare const EDITION_CAPABILITIES: Record<Edition, readonly string[]>;
|
|
32
|
+
|
|
66
33
|
/**
|
|
67
34
|
* Verify a licence key.
|
|
68
35
|
*
|
|
@@ -75,6 +42,16 @@ declare const EDITION_CAPABILITIES: Record<Edition, readonly string[]>;
|
|
|
75
42
|
* crash an application.
|
|
76
43
|
*/
|
|
77
44
|
declare function verifyLicense(key: string): Promise<VerificationResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Decode a key's payload **without verifying it**.
|
|
47
|
+
*
|
|
48
|
+
* For diagnostics only — printing "your licence expires on X" in a CLI, for instance. Never gate on
|
|
49
|
+
* this: the payload is unauthenticated and trivially forged.
|
|
50
|
+
*/
|
|
51
|
+
declare function inspectLicense(key: string): LicensePayload | null;
|
|
52
|
+
|
|
53
|
+
/** Capabilities each edition grants. */
|
|
54
|
+
declare const EDITION_CAPABILITIES: Record<Edition, readonly string[]>;
|
|
78
55
|
/**
|
|
79
56
|
* Activate a licence, granting its capabilities in the registry.
|
|
80
57
|
*
|
|
@@ -100,12 +77,6 @@ declare function currentEdition(): Edition;
|
|
|
100
77
|
declare function currentLicense(): LicensePayload | null;
|
|
101
78
|
/** Is a capability available under the active edition? */
|
|
102
79
|
declare function hasCapability(capability: string): boolean;
|
|
103
|
-
/**
|
|
104
|
-
* Decode a key's payload **without verifying it**.
|
|
105
|
-
*
|
|
106
|
-
* For diagnostics only — printing "your licence expires on X" in a CLI, for instance. Never gate on
|
|
107
|
-
* this: the payload is unauthenticated and trivially forged.
|
|
108
|
-
*/
|
|
109
|
-
declare function inspectLicense(key: string): LicensePayload | null;
|
|
110
80
|
|
|
111
|
-
export { EDITION_CAPABILITIES,
|
|
81
|
+
export { EDITION_CAPABILITIES, activateLicense, currentEdition, currentLicense, hasCapability, inspectLicense, useFreeEdition, verifyLicense };
|
|
82
|
+
export type { Edition, LicensePayload, VerificationResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edition gating and license key verification.
|
|
3
|
-
*
|
|
4
|
-
* ## What this is, honestly
|
|
5
|
-
*
|
|
6
|
-
* This is **not DRM, and it cannot be.** Any check that runs in a browser can be removed by editing
|
|
7
|
-
* the bundle, and any check that runs at build time can be removed by patching the package. Nothing
|
|
8
|
-
* shipped as JavaScript to a client is enforceable. Pretending otherwise would mean building
|
|
9
|
-
* obfuscation and phone-home telemetry that hurts paying customers — slower builds, offline CI
|
|
10
|
-
* failures, privacy concerns — while stopping nobody who was determined.
|
|
11
|
-
*
|
|
12
|
-
* So the design goal is different: **make it easy and obvious to be correctly licensed.** Concretely:
|
|
13
|
-
*
|
|
14
|
-
* - A signed key proves an entitlement without any network call, so CI works offline and no usage
|
|
15
|
-
* data leaves the customer's machine.
|
|
16
|
-
* - Pro components live behind a capability check that produces a clear, linkable error instead of
|
|
17
|
-
* a mysteriously blank canvas.
|
|
18
|
-
* - In development, an unlicensed Pro component renders a visible watermark and logs once. In
|
|
19
|
-
* production it throws at mount. Neither is a puzzle to diagnose.
|
|
20
|
-
* - Pro code lives in a separate entry point, so a Free-edition bundle never contains it. That is
|
|
21
|
-
* a genuine benefit for Free users — smaller bundles — rather than a restriction.
|
|
22
|
-
*
|
|
23
|
-
* Enforcement, where it matters, happens at the point of sale and in the license agreement. This
|
|
24
|
-
* module's job is to keep honest customers out of trouble.
|
|
25
|
-
*
|
|
26
|
-
* ## Key format
|
|
27
|
-
*
|
|
28
|
-
* `aur_<base64url(payload)>.<base64url(signature)>`
|
|
29
|
-
*
|
|
30
|
-
* The payload is JSON, so a customer can inspect exactly what they were issued — expiry, seats,
|
|
31
|
-
* edition — without a tool. The signature is Ed25519 over the payload bytes.
|
|
32
|
-
*/
|
|
33
1
|
type Edition = 'free' | 'pro' | 'enterprise';
|
|
34
2
|
interface LicensePayload {
|
|
35
3
|
/** Who the licence was issued to. Shown in diagnostics. */
|
|
@@ -61,8 +29,7 @@ interface VerificationResult {
|
|
|
61
29
|
/** `true` when the signature verified but the licence has expired. */
|
|
62
30
|
expired?: boolean;
|
|
63
31
|
}
|
|
64
|
-
|
|
65
|
-
declare const EDITION_CAPABILITIES: Record<Edition, readonly string[]>;
|
|
32
|
+
|
|
66
33
|
/**
|
|
67
34
|
* Verify a licence key.
|
|
68
35
|
*
|
|
@@ -75,6 +42,16 @@ declare const EDITION_CAPABILITIES: Record<Edition, readonly string[]>;
|
|
|
75
42
|
* crash an application.
|
|
76
43
|
*/
|
|
77
44
|
declare function verifyLicense(key: string): Promise<VerificationResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Decode a key's payload **without verifying it**.
|
|
47
|
+
*
|
|
48
|
+
* For diagnostics only — printing "your licence expires on X" in a CLI, for instance. Never gate on
|
|
49
|
+
* this: the payload is unauthenticated and trivially forged.
|
|
50
|
+
*/
|
|
51
|
+
declare function inspectLicense(key: string): LicensePayload | null;
|
|
52
|
+
|
|
53
|
+
/** Capabilities each edition grants. */
|
|
54
|
+
declare const EDITION_CAPABILITIES: Record<Edition, readonly string[]>;
|
|
78
55
|
/**
|
|
79
56
|
* Activate a licence, granting its capabilities in the registry.
|
|
80
57
|
*
|
|
@@ -100,12 +77,6 @@ declare function currentEdition(): Edition;
|
|
|
100
77
|
declare function currentLicense(): LicensePayload | null;
|
|
101
78
|
/** Is a capability available under the active edition? */
|
|
102
79
|
declare function hasCapability(capability: string): boolean;
|
|
103
|
-
/**
|
|
104
|
-
* Decode a key's payload **without verifying it**.
|
|
105
|
-
*
|
|
106
|
-
* For diagnostics only — printing "your licence expires on X" in a CLI, for instance. Never gate on
|
|
107
|
-
* this: the payload is unauthenticated and trivially forged.
|
|
108
|
-
*/
|
|
109
|
-
declare function inspectLicense(key: string): LicensePayload | null;
|
|
110
80
|
|
|
111
|
-
export { EDITION_CAPABILITIES,
|
|
81
|
+
export { EDITION_CAPABILITIES, activateLicense, currentEdition, currentLicense, hasCapability, inspectLicense, useFreeEdition, verifyLicense };
|
|
82
|
+
export type { Edition, LicensePayload, VerificationResult };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,117 @@
|
|
|
1
1
|
import { getRegistry } from '@ansh-ui/core';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/base64url.ts
|
|
4
|
+
function base64UrlDecode(input) {
|
|
5
|
+
const normalised = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
6
|
+
const padded = normalised + "=".repeat((4 - normalised.length % 4) % 4);
|
|
7
|
+
if (typeof atob === "function") {
|
|
8
|
+
const binary = atob(padded);
|
|
9
|
+
const bytes = new Uint8Array(binary.length);
|
|
10
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
11
|
+
return bytes;
|
|
12
|
+
}
|
|
13
|
+
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
14
|
+
}
|
|
15
|
+
var PUBLIC_KEY_B64 = "ZnSy7P02L2gBSMrvugpEg0cuLMq1MIv2jMi6wnxgLZs" ;
|
|
4
16
|
var KEY_PREFIX = "aur_";
|
|
17
|
+
|
|
18
|
+
// src/verify.ts
|
|
19
|
+
var LICENSING_URL = "https://ansh.techiesofepi.com";
|
|
20
|
+
var EDITIONS = ["free", "pro", "enterprise"];
|
|
21
|
+
function verifyLicense(key) {
|
|
22
|
+
return verifyLicenseWithPublicKey(key, PUBLIC_KEY_B64);
|
|
23
|
+
}
|
|
24
|
+
async function verifyLicenseWithPublicKey(key, publicKeyB64) {
|
|
25
|
+
const trimmed = key.trim();
|
|
26
|
+
if (!trimmed.startsWith(KEY_PREFIX)) {
|
|
27
|
+
return fail(`Key does not start with "${KEY_PREFIX}". Check for a copy-paste error.`);
|
|
28
|
+
}
|
|
29
|
+
const body = trimmed.slice(KEY_PREFIX.length);
|
|
30
|
+
const separator = body.lastIndexOf(".");
|
|
31
|
+
if (separator <= 0) {
|
|
32
|
+
return fail("Key is missing its signature separator. It may be truncated.");
|
|
33
|
+
}
|
|
34
|
+
const payloadPart = body.slice(0, separator);
|
|
35
|
+
const signaturePart = body.slice(separator + 1);
|
|
36
|
+
let payload;
|
|
37
|
+
let payloadBytes;
|
|
38
|
+
try {
|
|
39
|
+
payloadBytes = base64UrlDecode(payloadPart);
|
|
40
|
+
payload = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
41
|
+
} catch {
|
|
42
|
+
return fail("Key payload is not valid base64url-encoded JSON.");
|
|
43
|
+
}
|
|
44
|
+
let signatureValid;
|
|
45
|
+
try {
|
|
46
|
+
signatureValid = await verifySignature(
|
|
47
|
+
publicKeyB64,
|
|
48
|
+
payloadBytes,
|
|
49
|
+
base64UrlDecode(signaturePart)
|
|
50
|
+
);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return fail(
|
|
53
|
+
`Signature verification is unavailable in this runtime (${error instanceof Error ? error.message : "unknown error"}). Ed25519 via Web Crypto requires Node 18.4+, Chrome 137+, Firefox 129+ or Safari 17+. Verify at build time instead if you must support older runtimes.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (!signatureValid) {
|
|
57
|
+
return fail("Signature does not match. The key has been modified or is not genuine.");
|
|
58
|
+
}
|
|
59
|
+
if (!payload || typeof payload !== "object" || !EDITIONS.includes(payload.edition)) {
|
|
60
|
+
return fail("Key payload names an unknown edition. It may be from a newer release.");
|
|
61
|
+
}
|
|
62
|
+
if (payload.expires !== null) {
|
|
63
|
+
const expiresAt = new Date(payload.expires).getTime();
|
|
64
|
+
if (Number.isNaN(expiresAt)) {
|
|
65
|
+
return fail(`Key payload has an unreadable expiry date ("${String(payload.expires)}").`);
|
|
66
|
+
}
|
|
67
|
+
if (expiresAt < Date.now()) {
|
|
68
|
+
return {
|
|
69
|
+
valid: false,
|
|
70
|
+
payload,
|
|
71
|
+
edition: "free",
|
|
72
|
+
expired: true,
|
|
73
|
+
reason: `Licence expired on ${payload.expires}. Previously-installed versions continue to work; renew to access newer releases.`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { valid: true, payload, edition: payload.edition };
|
|
78
|
+
}
|
|
79
|
+
function inspectLicense(key) {
|
|
80
|
+
try {
|
|
81
|
+
const body = key.trim().slice(KEY_PREFIX.length);
|
|
82
|
+
const separator = body.lastIndexOf(".");
|
|
83
|
+
if (separator <= 0) return null;
|
|
84
|
+
return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))));
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var importedKeys = /* @__PURE__ */ new Map();
|
|
90
|
+
async function verifySignature(publicKeyB64, payload, signature) {
|
|
91
|
+
const subtle = globalThis.crypto?.subtle;
|
|
92
|
+
if (!subtle) throw new Error("Web Crypto is unavailable");
|
|
93
|
+
let key = importedKeys.get(publicKeyB64);
|
|
94
|
+
if (!key) {
|
|
95
|
+
key = subtle.importKey(
|
|
96
|
+
"raw",
|
|
97
|
+
base64UrlDecode(publicKeyB64),
|
|
98
|
+
{ name: "Ed25519" },
|
|
99
|
+
false,
|
|
100
|
+
["verify"]
|
|
101
|
+
);
|
|
102
|
+
importedKeys.set(publicKeyB64, key);
|
|
103
|
+
key.catch(() => importedKeys.delete(publicKeyB64));
|
|
104
|
+
}
|
|
105
|
+
return subtle.verify(
|
|
106
|
+
{ name: "Ed25519" },
|
|
107
|
+
await key,
|
|
108
|
+
signature,
|
|
109
|
+
payload
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function fail(reason) {
|
|
113
|
+
return { valid: false, payload: null, edition: "free", reason };
|
|
114
|
+
}
|
|
5
115
|
var EDITION_CAPABILITIES = {
|
|
6
116
|
free: ["effect:core", "behaviour:core", "renderer:canvas2d"],
|
|
7
117
|
pro: [
|
|
@@ -26,37 +136,10 @@ var EDITION_CAPABILITIES = {
|
|
|
26
136
|
};
|
|
27
137
|
var activeEdition = "free";
|
|
28
138
|
var activePayload = null;
|
|
29
|
-
async function verifyLicense(key) {
|
|
30
|
-
const trimmed = key.trim();
|
|
31
|
-
if (!trimmed.startsWith(KEY_PREFIX)) {
|
|
32
|
-
return fail(`Key does not start with "${KEY_PREFIX}". Check for a copy-paste error.`);
|
|
33
|
-
}
|
|
34
|
-
const body = trimmed.slice(KEY_PREFIX.length);
|
|
35
|
-
const separator = body.lastIndexOf(".");
|
|
36
|
-
if (separator <= 0) {
|
|
37
|
-
return fail("Key is missing its signature separator. It may be truncated.");
|
|
38
|
-
}
|
|
39
|
-
const payloadPart = body.slice(0, separator);
|
|
40
|
-
body.slice(separator + 1);
|
|
41
|
-
let payload;
|
|
42
|
-
let payloadBytes;
|
|
43
|
-
try {
|
|
44
|
-
payloadBytes = base64UrlDecode(payloadPart);
|
|
45
|
-
payload = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
46
|
-
} catch {
|
|
47
|
-
return fail("Key payload is not valid base64url-encoded JSON.");
|
|
48
|
-
}
|
|
49
|
-
{
|
|
50
|
-
return {
|
|
51
|
-
valid: false,
|
|
52
|
-
payload,
|
|
53
|
-
edition: "free",
|
|
54
|
-
reason: "License verification is unconfigured in this build of @ansh-ui/license. This is not a problem with your key. See https://ansh.dev/docs/licensing"
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
139
|
async function activateLicense(key) {
|
|
59
|
-
|
|
140
|
+
return applyVerification(await verifyLicense(key));
|
|
141
|
+
}
|
|
142
|
+
function applyVerification(result) {
|
|
60
143
|
if (!result.valid) {
|
|
61
144
|
console.warn(`[ansh] Licence not activated: ${result.reason}`);
|
|
62
145
|
return result;
|
|
@@ -83,7 +166,7 @@ function useFreeEdition() {
|
|
|
83
166
|
for (const capability of paid) {
|
|
84
167
|
registry.revoke(
|
|
85
168
|
capability,
|
|
86
|
-
|
|
169
|
+
`This component requires Ansh Pro. See ${LICENSING_URL} to compare editions.`
|
|
87
170
|
);
|
|
88
171
|
}
|
|
89
172
|
}
|
|
@@ -96,30 +179,6 @@ function currentLicense() {
|
|
|
96
179
|
function hasCapability(capability) {
|
|
97
180
|
return getRegistry().supports(capability);
|
|
98
181
|
}
|
|
99
|
-
function base64UrlDecode(input) {
|
|
100
|
-
const normalised = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
101
|
-
const padded = normalised + "=".repeat((4 - normalised.length % 4) % 4);
|
|
102
|
-
if (typeof atob === "function") {
|
|
103
|
-
const binary = atob(padded);
|
|
104
|
-
const bytes = new Uint8Array(binary.length);
|
|
105
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
106
|
-
return bytes;
|
|
107
|
-
}
|
|
108
|
-
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
109
|
-
}
|
|
110
|
-
function fail(reason) {
|
|
111
|
-
return { valid: false, payload: null, edition: "free", reason };
|
|
112
|
-
}
|
|
113
|
-
function inspectLicense(key) {
|
|
114
|
-
try {
|
|
115
|
-
const body = key.trim().slice(KEY_PREFIX.length);
|
|
116
|
-
const separator = body.lastIndexOf(".");
|
|
117
|
-
if (separator <= 0) return null;
|
|
118
|
-
return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))));
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
182
|
|
|
124
183
|
export { EDITION_CAPABILITIES, activateLicense, currentEdition, currentLicense, hasCapability, inspectLicense, useFreeEdition, verifyLicense };
|
|
125
184
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { getRegistry } from '@ansh-ui/core'\n\n/**\n * Edition gating and license key verification.\n *\n * ## What this is, honestly\n *\n * This is **not DRM, and it cannot be.** Any check that runs in a browser can be removed by editing\n * the bundle, and any check that runs at build time can be removed by patching the package. Nothing\n * shipped as JavaScript to a client is enforceable. Pretending otherwise would mean building\n * obfuscation and phone-home telemetry that hurts paying customers — slower builds, offline CI\n * failures, privacy concerns — while stopping nobody who was determined.\n *\n * So the design goal is different: **make it easy and obvious to be correctly licensed.** Concretely:\n *\n * - A signed key proves an entitlement without any network call, so CI works offline and no usage\n * data leaves the customer's machine.\n * - Pro components live behind a capability check that produces a clear, linkable error instead of\n * a mysteriously blank canvas.\n * - In development, an unlicensed Pro component renders a visible watermark and logs once. In\n * production it throws at mount. Neither is a puzzle to diagnose.\n * - Pro code lives in a separate entry point, so a Free-edition bundle never contains it. That is\n * a genuine benefit for Free users — smaller bundles — rather than a restriction.\n *\n * Enforcement, where it matters, happens at the point of sale and in the license agreement. This\n * module's job is to keep honest customers out of trouble.\n *\n * ## Key format\n *\n * `aur_<base64url(payload)>.<base64url(signature)>`\n *\n * The payload is JSON, so a customer can inspect exactly what they were issued — expiry, seats,\n * edition — without a tool. The signature is Ed25519 over the payload bytes.\n */\n\nexport type Edition = 'free' | 'pro' | 'enterprise'\n\nexport interface LicensePayload {\n /** Who the licence was issued to. Shown in diagnostics. */\n licensee: string\n edition: Edition\n /** ISO 8601 issue date. */\n issued: string\n /**\n * ISO 8601 expiry, or `null` for a perpetual licence.\n *\n * A perpetual licence for a version range is the honest model for a component library: the code a\n * customer already installed keeps working forever, and the licence governs access to *new*\n * versions. A licence that switches off working software is hostile.\n */\n expires: string | null\n /** Number of developer seats. Informational — not enforced at runtime. */\n seats?: number\n /** Capabilities this key grants beyond the edition's defaults. */\n grants?: string[]\n /** Domains the licence covers. Informational. */\n domains?: string[]\n}\n\nexport interface VerificationResult {\n valid: boolean\n payload: LicensePayload | null\n edition: Edition\n /** Human-readable explanation when `valid` is false. */\n reason?: string\n /** `true` when the signature verified but the licence has expired. */\n expired?: boolean\n}\n\n/**\n * The Ansh public signing key, raw Ed25519 (32 bytes), base64url.\n *\n * Public by definition — it can only verify, never sign. Shipping it in the package is what makes\n * offline verification possible.\n *\n * This placeholder is the all-zero key, which verifies nothing. It is replaced at release time by\n * the real key; until then `verifyLicense` reports `unconfigured` rather than silently accepting or\n * rejecting everything, so an integration bug can't be mistaken for a licensing one.\n */\nconst PUBLIC_KEY_B64 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\n\nconst KEY_PREFIX = 'aur_'\n\n/** Capabilities each edition grants. */\nconst EDITION_CAPABILITIES: Record<Edition, readonly string[]> = {\n free: ['effect:core', 'behaviour:core', 'renderer:canvas2d'],\n pro: [\n 'effect:core',\n 'effect:pro',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n ],\n enterprise: [\n 'effect:core',\n 'effect:pro',\n 'effect:enterprise',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n 'renderer:webgpu',\n 'worker',\n ],\n}\n\nlet activeEdition: Edition = 'free'\nlet activePayload: LicensePayload | null = null\n\n/**\n * Verify a licence key.\n *\n * Async because Web Crypto is async. Call it once at application startup (or in a build plugin) and\n * `await` it — the result is cached in module state, so components check a boolean rather than\n * re-verifying.\n *\n * Never throws. A malformed key, an unsupported runtime and a genuinely invalid signature all\n * produce a `VerificationResult` with a `reason`, because a licence problem must not be able to\n * crash an application.\n */\nexport async function verifyLicense(key: string): Promise<VerificationResult> {\n const trimmed = key.trim()\n\n if (!trimmed.startsWith(KEY_PREFIX)) {\n return fail(`Key does not start with \"${KEY_PREFIX}\". Check for a copy-paste error.`)\n }\n\n const body = trimmed.slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) {\n return fail('Key is missing its signature separator. It may be truncated.')\n }\n\n const payloadPart = body.slice(0, separator)\n const signaturePart = body.slice(separator + 1)\n\n let payload: LicensePayload\n let payloadBytes: Uint8Array\n try {\n payloadBytes = base64UrlDecode(payloadPart)\n payload = JSON.parse(new TextDecoder().decode(payloadBytes)) as LicensePayload\n } catch {\n return fail('Key payload is not valid base64url-encoded JSON.')\n }\n\n if (PUBLIC_KEY_B64 === 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {\n // Distinguish \"we haven't shipped a signing key yet\" from \"your key is bad\". Conflating the two\n // would send customers to support for a problem that is entirely ours.\n return {\n valid: false,\n payload,\n edition: 'free',\n reason:\n 'License verification is unconfigured in this build of @ansh-ui/license. ' +\n 'This is not a problem with your key. See https://ansh.dev/docs/licensing',\n }\n }\n\n let signatureValid: boolean\n try {\n signatureValid = await verifySignature(payloadBytes, base64UrlDecode(signaturePart))\n } catch (error) {\n return fail(\n `Signature verification is unavailable in this runtime ` +\n `(${error instanceof Error ? error.message : 'unknown error'}). ` +\n `Ed25519 via Web Crypto requires Node 18.4+, Chrome 137+, Firefox 129+ or Safari 17+. ` +\n `Verify at build time instead if you must support older runtimes.`,\n )\n }\n\n if (!signatureValid) {\n return fail('Signature does not match. The key has been modified or is not genuine.')\n }\n\n const expired = payload.expires !== null && new Date(payload.expires).getTime() < Date.now()\n if (expired) {\n return {\n valid: false,\n payload,\n edition: 'free',\n expired: true,\n reason:\n `Licence expired on ${payload.expires}. ` +\n `Previously-installed versions continue to work; renew to access newer releases.`,\n }\n }\n\n return { valid: true, payload, edition: payload.edition }\n}\n\n/**\n * Activate a licence, granting its capabilities in the registry.\n *\n * This is the single call an application makes. After it resolves, Pro components mount normally\n * and `registry.requireCapability('effect:pro')` succeeds.\n *\n * ```ts\n * import { activateLicense } from '@ansh-ui/license'\n * await activateLicense(process.env.ANSH_LICENSE_KEY!)\n * ```\n */\nexport async function activateLicense(key: string): Promise<VerificationResult> {\n const result = await verifyLicense(key)\n\n if (!result.valid) {\n console.warn(`[ansh] Licence not activated: ${result.reason}`)\n return result\n }\n\n activeEdition = result.edition\n activePayload = result.payload\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES[result.edition]) {\n registry.provide(capability)\n }\n for (const grant of result.payload?.grants ?? []) {\n registry.provide(grant)\n }\n\n return result\n}\n\n/**\n * Apply the Free edition explicitly.\n *\n * Called automatically the first time a capability is checked without activation, so Free works with\n * no setup at all. Exposed for tests and for resetting between app instances.\n */\nexport function useFreeEdition(): void {\n activeEdition = 'free'\n activePayload = null\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES.free) registry.provide(capability)\n\n // Revoke paid capabilities with a message that says what to do, rather than leaving the generic\n // \"missing capability\" error to speak for itself.\n const paid = [\n ...new Set([...EDITION_CAPABILITIES.pro, ...EDITION_CAPABILITIES.enterprise]),\n ].filter((capability) => !EDITION_CAPABILITIES.free.includes(capability))\n\n for (const capability of paid) {\n registry.revoke(\n capability,\n 'This component requires Ansh Pro. See https://ansh.dev/pro to compare editions.',\n )\n }\n}\n\n/** The currently active edition. */\nexport function currentEdition(): Edition {\n return activeEdition\n}\n\n/** The active licence payload, or `null` on Free. */\nexport function currentLicense(): LicensePayload | null {\n return activePayload\n}\n\n/** Is a capability available under the active edition? */\nexport function hasCapability(capability: string): boolean {\n return getRegistry().supports(capability)\n}\n\n// ---------------------------------------------------------------------------\n// Crypto\n// ---------------------------------------------------------------------------\n\nlet cachedKey: CryptoKey | null = null\n\nasync function verifySignature(payload: Uint8Array, signature: Uint8Array): Promise<boolean> {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) throw new Error('Web Crypto is unavailable')\n\n if (!cachedKey) {\n cachedKey = await subtle.importKey(\n 'raw',\n base64UrlDecode(PUBLIC_KEY_B64) as unknown as ArrayBuffer,\n { name: 'Ed25519' },\n false,\n ['verify'],\n )\n }\n\n return subtle.verify(\n { name: 'Ed25519' },\n cachedKey,\n signature as unknown as ArrayBuffer,\n payload as unknown as ArrayBuffer,\n )\n}\n\n/**\n * base64url → bytes.\n *\n * Hand-rolled rather than using `atob` because base64url uses `-`/`_` instead of `+`/`/` and omits\n * padding, and because `atob` does not exist in every Node context we might run in.\n */\nfunction base64UrlDecode(input: string): Uint8Array {\n const normalised = input.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalised + '='.repeat((4 - (normalised.length % 4)) % 4)\n\n if (typeof atob === 'function') {\n const binary = atob(padded)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n }\n\n // Node without a global `atob` (very old versions, or a stripped runtime).\n return new Uint8Array(Buffer.from(padded, 'base64'))\n}\n\nfunction fail(reason: string): VerificationResult {\n return { valid: false, payload: null, edition: 'free', reason }\n}\n\n/**\n * Decode a key's payload **without verifying it**.\n *\n * For diagnostics only — printing \"your licence expires on X\" in a CLI, for instance. Never gate on\n * this: the payload is unauthenticated and trivially forged.\n */\nexport function inspectLicense(key: string): LicensePayload | null {\n try {\n const body = key.trim().slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) return null\n return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))))\n } catch {\n return null\n }\n}\n\nexport { EDITION_CAPABILITIES }\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/base64url.ts","../src/public-key.ts","../src/verify.ts","../src/activation.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * base64url → bytes.\n *\n * Hand-rolled rather than using `atob` because base64url uses `-`/`_` instead of `+`/`/` and omits\n * padding, and because `atob` does not exist in every Node context we might run in.\n */\nexport function base64UrlDecode(input: string): Uint8Array {\n const normalised = input.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalised + '='.repeat((4 - (normalised.length % 4)) % 4)\n\n if (typeof atob === 'function') {\n const binary = atob(padded)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n }\n\n // Node without a global `atob` (very old versions, or a stripped runtime).\n return new Uint8Array(Buffer.from(padded, 'base64'))\n}\n","/**\n * The Ansh public signing key, raw Ed25519 (32 bytes), base64url.\n *\n * Public by definition — it can only verify, never sign. Shipping it in the package is what makes\n * offline verification possible.\n *\n * The real key is injected at build time: `tsup.config.ts` replaces `__ANSH_LICENSE_PUBLIC_KEY__`\n * with the `ANSH_LICENSE_PUBLIC_KEY` environment variable, which `release.yml` sets from a repository\n * secret. A build without it falls back to the all-zero placeholder, which verifies nothing, and\n * `verifyLicense` then reports `unconfigured` rather than silently accepting or rejecting everything,\n * so an integration bug can't be mistaken for a licensing one. `scripts/check-license-key.mjs`\n * refuses to publish a placeholder build.\n */\n\ndeclare const __ANSH_LICENSE_PUBLIC_KEY__: string | undefined\n\nexport const PLACEHOLDER_PUBLIC_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\n\n/** `typeof` guard so running from source (tests, the docs site) works without the define. */\nexport const PUBLIC_KEY_B64: string =\n typeof __ANSH_LICENSE_PUBLIC_KEY__ === 'string' && __ANSH_LICENSE_PUBLIC_KEY__ !== ''\n ? __ANSH_LICENSE_PUBLIC_KEY__\n : PLACEHOLDER_PUBLIC_KEY\n\nexport const KEY_PREFIX = 'aur_'\n","import { base64UrlDecode } from './base64url'\nimport { KEY_PREFIX, PLACEHOLDER_PUBLIC_KEY, PUBLIC_KEY_B64 } from './public-key'\nimport type { Edition, LicensePayload, VerificationResult } from './types'\n\n/** Where licence problems send people. The pricing section lives on the landing page. */\nexport const LICENSING_URL = 'https://ansh.techiesofepi.com'\n\nconst EDITIONS: readonly Edition[] = ['free', 'pro', 'enterprise']\n\n/**\n * Verify a licence key.\n *\n * Async because Web Crypto is async. Call it once at application startup (or in a build plugin) and\n * `await` it — the result is cached in module state, so components check a boolean rather than\n * re-verifying.\n *\n * Never throws. A malformed key, an unsupported runtime and a genuinely invalid signature all\n * produce a `VerificationResult` with a `reason`, because a licence problem must not be able to\n * crash an application.\n */\nexport function verifyLicense(key: string): Promise<VerificationResult> {\n return verifyLicenseWithPublicKey(key, PUBLIC_KEY_B64)\n}\n\n/**\n * {@link verifyLicense} against an explicit public key.\n *\n * Not re-exported from the package root: the shipped key is the only one a consumer should trust.\n * It exists so the test suite can sign with a throwaway keypair.\n */\nexport async function verifyLicenseWithPublicKey(\n key: string,\n publicKeyB64: string,\n): Promise<VerificationResult> {\n const trimmed = key.trim()\n\n if (!trimmed.startsWith(KEY_PREFIX)) {\n return fail(`Key does not start with \"${KEY_PREFIX}\". Check for a copy-paste error.`)\n }\n\n const body = trimmed.slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) {\n return fail('Key is missing its signature separator. It may be truncated.')\n }\n\n const payloadPart = body.slice(0, separator)\n const signaturePart = body.slice(separator + 1)\n\n let payload: LicensePayload\n let payloadBytes: Uint8Array\n try {\n payloadBytes = base64UrlDecode(payloadPart)\n payload = JSON.parse(new TextDecoder().decode(payloadBytes)) as LicensePayload\n } catch {\n return fail('Key payload is not valid base64url-encoded JSON.')\n }\n\n if (publicKeyB64 === PLACEHOLDER_PUBLIC_KEY) {\n // Distinguish \"we haven't shipped a signing key yet\" from \"your key is bad\". Conflating the two\n // would send customers to support for a problem that is entirely ours.\n return {\n valid: false,\n payload,\n edition: 'free',\n reason:\n 'License verification is unconfigured in this build of @ansh-ui/license. ' +\n `This is not a problem with your key. See ${LICENSING_URL}`,\n }\n }\n\n let signatureValid: boolean\n try {\n signatureValid = await verifySignature(\n publicKeyB64,\n payloadBytes,\n base64UrlDecode(signaturePart),\n )\n } catch (error) {\n return fail(\n `Signature verification is unavailable in this runtime ` +\n `(${error instanceof Error ? error.message : 'unknown error'}). ` +\n `Ed25519 via Web Crypto requires Node 18.4+, Chrome 137+, Firefox 129+ or Safari 17+. ` +\n `Verify at build time instead if you must support older runtimes.`,\n )\n }\n\n if (!signatureValid) {\n return fail('Signature does not match. The key has been modified or is not genuine.')\n }\n\n // The signature proves we issued these bytes, not that they're well-formed. Checked after\n // verification so a forged payload can't probe which shapes we accept.\n if (!payload || typeof payload !== 'object' || !EDITIONS.includes(payload.edition)) {\n return fail('Key payload names an unknown edition. It may be from a newer release.')\n }\n\n if (payload.expires !== null) {\n const expiresAt = new Date(payload.expires).getTime()\n // An unparseable date would otherwise compare as NaN and silently never expire.\n if (Number.isNaN(expiresAt)) {\n return fail(`Key payload has an unreadable expiry date (\"${String(payload.expires)}\").`)\n }\n if (expiresAt < Date.now()) {\n return {\n valid: false,\n payload,\n edition: 'free',\n expired: true,\n reason:\n `Licence expired on ${payload.expires}. ` +\n `Previously-installed versions continue to work; renew to access newer releases.`,\n }\n }\n }\n\n return { valid: true, payload, edition: payload.edition }\n}\n\n/**\n * Decode a key's payload **without verifying it**.\n *\n * For diagnostics only — printing \"your licence expires on X\" in a CLI, for instance. Never gate on\n * this: the payload is unauthenticated and trivially forged.\n */\nexport function inspectLicense(key: string): LicensePayload | null {\n try {\n const body = key.trim().slice(KEY_PREFIX.length)\n const separator = body.lastIndexOf('.')\n if (separator <= 0) return null\n return JSON.parse(new TextDecoder().decode(base64UrlDecode(body.slice(0, separator))))\n } catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Crypto\n// ---------------------------------------------------------------------------\n\nconst importedKeys = new Map<string, Promise<CryptoKey>>()\n\nasync function verifySignature(\n publicKeyB64: string,\n payload: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) throw new Error('Web Crypto is unavailable')\n\n let key = importedKeys.get(publicKeyB64)\n if (!key) {\n key = subtle.importKey(\n 'raw',\n base64UrlDecode(publicKeyB64) as unknown as ArrayBuffer,\n { name: 'Ed25519' },\n false,\n ['verify'],\n )\n importedKeys.set(publicKeyB64, key)\n // A failed import must not poison the cache for the next attempt.\n key.catch(() => importedKeys.delete(publicKeyB64))\n }\n\n return subtle.verify(\n { name: 'Ed25519' },\n await key,\n signature as unknown as ArrayBuffer,\n payload as unknown as ArrayBuffer,\n )\n}\n\nfunction fail(reason: string): VerificationResult {\n return { valid: false, payload: null, edition: 'free', reason }\n}\n","import { getRegistry } from '@ansh-ui/core'\nimport type { Edition, LicensePayload, VerificationResult } from './types'\nimport { LICENSING_URL, verifyLicense } from './verify'\n\n/** Capabilities each edition grants. */\nexport const EDITION_CAPABILITIES: Record<Edition, readonly string[]> = {\n free: ['effect:core', 'behaviour:core', 'renderer:canvas2d'],\n pro: [\n 'effect:core',\n 'effect:pro',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n ],\n enterprise: [\n 'effect:core',\n 'effect:pro',\n 'effect:enterprise',\n 'behaviour:core',\n 'behaviour:pro',\n 'renderer:canvas2d',\n 'renderer:webgl',\n 'renderer:webgpu',\n 'worker',\n ],\n}\n\nlet activeEdition: Edition = 'free'\nlet activePayload: LicensePayload | null = null\n\n/**\n * Activate a licence, granting its capabilities in the registry.\n *\n * This is the single call an application makes. After it resolves, Pro components mount normally\n * and `registry.requireCapability('effect:pro')` succeeds.\n *\n * ```ts\n * import { activateLicense } from '@ansh-ui/license'\n * await activateLicense(process.env.ANSH_LICENSE_KEY!)\n * ```\n */\nexport async function activateLicense(key: string): Promise<VerificationResult> {\n return applyVerification(await verifyLicense(key))\n}\n\n/** The registry half of {@link activateLicense}, split out so tests can feed it a result. */\nexport function applyVerification(result: VerificationResult): VerificationResult {\n if (!result.valid) {\n console.warn(`[ansh] Licence not activated: ${result.reason}`)\n return result\n }\n\n activeEdition = result.edition\n activePayload = result.payload\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES[result.edition]) {\n registry.provide(capability)\n }\n for (const grant of result.payload?.grants ?? []) {\n registry.provide(grant)\n }\n\n return result\n}\n\n/**\n * Apply the Free edition explicitly.\n *\n * Called automatically the first time a capability is checked without activation, so Free works with\n * no setup at all. Exposed for tests and for resetting between app instances.\n */\nexport function useFreeEdition(): void {\n activeEdition = 'free'\n activePayload = null\n\n const registry = getRegistry()\n for (const capability of EDITION_CAPABILITIES.free) registry.provide(capability)\n\n // Revoke paid capabilities with a message that says what to do, rather than leaving the generic\n // \"missing capability\" error to speak for itself.\n const paid = [\n ...new Set([...EDITION_CAPABILITIES.pro, ...EDITION_CAPABILITIES.enterprise]),\n ].filter((capability) => !EDITION_CAPABILITIES.free.includes(capability))\n\n for (const capability of paid) {\n registry.revoke(\n capability,\n `This component requires Ansh Pro. See ${LICENSING_URL} to compare editions.`,\n )\n }\n}\n\n/** The currently active edition. */\nexport function currentEdition(): Edition {\n return activeEdition\n}\n\n/** The active licence payload, or `null` on Free. */\nexport function currentLicense(): LicensePayload | null {\n return activePayload\n}\n\n/** Is a capability available under the active edition? */\nexport function hasCapability(capability: string): boolean {\n return getRegistry().supports(capability)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ansh-ui/license",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Ansh edition gating and license key verification.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@ansh-ui/core": "0.
|
|
28
|
+
"@ansh-ui/core": "0.3.0"
|
|
29
29
|
},
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|