@moonbase.sh/licensing 2.0.1 → 3.0.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/FINGERPRINT_SPEC.md +414 -0
- package/README.md +133 -6
- package/dist/index.cjs +512 -66
- package/dist/index.d.cts +314 -14
- package/dist/index.d.ts +314 -14
- package/dist/index.js +496 -67
- package/fingerprint-vectors.json +744 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
3
|
+
import process3 from "process";
|
|
4
|
+
|
|
1
5
|
// src/client.ts
|
|
2
6
|
import fetch from "cross-fetch";
|
|
3
7
|
|
|
8
|
+
// src/schemas.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
4
11
|
// src/types.ts
|
|
5
12
|
var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
|
|
6
13
|
ActivationMethod2["Online"] = "Online";
|
|
@@ -9,7 +16,6 @@ var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
|
|
|
9
16
|
})(ActivationMethod || {});
|
|
10
17
|
|
|
11
18
|
// src/schemas.ts
|
|
12
|
-
import { z } from "zod";
|
|
13
19
|
var activationRequestResponseSchema = z.object({
|
|
14
20
|
id: z.string(),
|
|
15
21
|
request: z.string(),
|
|
@@ -63,7 +69,7 @@ var LicenseClient = class {
|
|
|
63
69
|
deviceName: await this.deviceIdResolver.resolveDeviceName(),
|
|
64
70
|
deviceSignature: await this.deviceIdResolver.resolveDeviceId()
|
|
65
71
|
};
|
|
66
|
-
const response = await fetch(this.configuration.endpoint
|
|
72
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/activations/${this.configuration.productId}/request${this.buildQueryString()}`, {
|
|
67
73
|
...defaultFetchOptions,
|
|
68
74
|
method: "POST",
|
|
69
75
|
body: JSON.stringify(content)
|
|
@@ -85,7 +91,8 @@ var LicenseClient = class {
|
|
|
85
91
|
}
|
|
86
92
|
async getRequestedActivation(request) {
|
|
87
93
|
const response = await fetch(request.request, defaultFetchOptions);
|
|
88
|
-
if (response.status === 204 || response.status === 404)
|
|
94
|
+
if (response.status === 204 || response.status === 404)
|
|
95
|
+
return null;
|
|
89
96
|
return await this.handleLicenseResponse(response);
|
|
90
97
|
}
|
|
91
98
|
async requestTrial() {
|
|
@@ -93,7 +100,7 @@ var LicenseClient = class {
|
|
|
93
100
|
deviceName: await this.deviceIdResolver.resolveDeviceName(),
|
|
94
101
|
deviceSignature: await this.deviceIdResolver.resolveDeviceId()
|
|
95
102
|
};
|
|
96
|
-
const response = await fetch(this.configuration.endpoint
|
|
103
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/trials/${this.configuration.productId}/request${this.buildQueryString()}`, {
|
|
97
104
|
...defaultFetchOptions,
|
|
98
105
|
method: "POST",
|
|
99
106
|
headers: {
|
|
@@ -105,7 +112,7 @@ var LicenseClient = class {
|
|
|
105
112
|
return await this.handleLicenseResponse(response);
|
|
106
113
|
}
|
|
107
114
|
async validateLicense(license) {
|
|
108
|
-
const response = await fetch(this.configuration.endpoint
|
|
115
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/validate${this.buildQueryString()}`, {
|
|
109
116
|
...defaultFetchOptions,
|
|
110
117
|
method: "POST",
|
|
111
118
|
headers: {
|
|
@@ -118,11 +125,12 @@ var LicenseClient = class {
|
|
|
118
125
|
}
|
|
119
126
|
async validateRawLicense(rawLicense) {
|
|
120
127
|
const license = await this.licenseValidator.validateLicense(rawLicense.toString("utf8"));
|
|
121
|
-
if (license.activationMethod === "Offline" /* Offline */)
|
|
128
|
+
if (license.activationMethod === "Offline" /* Offline */)
|
|
129
|
+
return license;
|
|
122
130
|
return await this.validateLicense(license);
|
|
123
131
|
}
|
|
124
132
|
async revokeLicense(license) {
|
|
125
|
-
const response = await fetch(this.configuration.endpoint
|
|
133
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/revoke?format=JWT`, {
|
|
126
134
|
...defaultFetchOptions,
|
|
127
135
|
method: "POST",
|
|
128
136
|
headers: {
|
|
@@ -144,11 +152,14 @@ var LicenseClient = class {
|
|
|
144
152
|
buildQueryString() {
|
|
145
153
|
const parts = ["format=JWT"];
|
|
146
154
|
const { platform, appVersion, metadata } = this.configuration;
|
|
147
|
-
if (platform)
|
|
148
|
-
|
|
155
|
+
if (platform)
|
|
156
|
+
parts.push(`platform=${encodeURIComponent(platform)}`);
|
|
157
|
+
if (appVersion)
|
|
158
|
+
parts.push(`appVersion=${encodeURIComponent(appVersion)}`);
|
|
149
159
|
if (metadata) {
|
|
150
160
|
for (const [key, value] of Object.entries(metadata)) {
|
|
151
|
-
if (value === null || value === void 0 || value === "")
|
|
161
|
+
if (value === null || value === void 0 || value === "")
|
|
162
|
+
continue;
|
|
152
163
|
parts.push(`meta[${encodeURIComponent(key)}]=${encodeURIComponent(value)}`);
|
|
153
164
|
}
|
|
154
165
|
}
|
|
@@ -157,32 +168,426 @@ var LicenseClient = class {
|
|
|
157
168
|
};
|
|
158
169
|
|
|
159
170
|
// src/deviceIdResolver.ts
|
|
171
|
+
import cp2 from "child_process";
|
|
172
|
+
import { createHash as createHash2 } from "crypto";
|
|
173
|
+
import os2 from "os";
|
|
174
|
+
import process2 from "process";
|
|
160
175
|
import si from "systeminformation";
|
|
161
|
-
|
|
176
|
+
|
|
177
|
+
// src/errors.ts
|
|
178
|
+
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
179
|
+
ErrorType2["None"] = "None";
|
|
180
|
+
ErrorType2["ApiError"] = "ApiError";
|
|
181
|
+
ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
|
|
182
|
+
ErrorType2["LicenseInvalid"] = "LicenseInvalid";
|
|
183
|
+
ErrorType2["LicenseRevoked"] = "LicenseRevoked";
|
|
184
|
+
ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
|
|
185
|
+
ErrorType2["LicenseExpired"] = "LicenseExpired";
|
|
186
|
+
ErrorType2["LicenseDeviceMismatch"] = "LicenseDeviceMismatch";
|
|
187
|
+
ErrorType2["DeviceIdentityUnavailable"] = "DeviceIdentityUnavailable";
|
|
188
|
+
return ErrorType2;
|
|
189
|
+
})(ErrorType || {});
|
|
190
|
+
var MoonbaseError = class extends Error {
|
|
191
|
+
constructor(title, detail, type, inner) {
|
|
192
|
+
super();
|
|
193
|
+
this.title = title;
|
|
194
|
+
this.detail = detail;
|
|
195
|
+
this.type = type;
|
|
196
|
+
this.inner = inner;
|
|
197
|
+
this.name = "MoonbaseError";
|
|
198
|
+
this.message = detail != null ? detail : title;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var InsufficientDeviceIdentityError = class extends MoonbaseError {
|
|
202
|
+
constructor(platform, reason = "no identity parameter could be read") {
|
|
203
|
+
super(
|
|
204
|
+
"No device identity",
|
|
205
|
+
`Could not identify this device (platform: ${platform}): ${reason}`,
|
|
206
|
+
"DeviceIdentityUnavailable" /* DeviceIdentityUnavailable */
|
|
207
|
+
);
|
|
208
|
+
this.platform = platform;
|
|
209
|
+
this.name = "InsufficientDeviceIdentityError";
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/fingerprint.ts
|
|
214
|
+
import { Buffer } from "buffer";
|
|
162
215
|
import cp from "child_process";
|
|
163
216
|
import { createHash } from "crypto";
|
|
164
|
-
|
|
217
|
+
import fs from "fs";
|
|
218
|
+
import os from "os";
|
|
219
|
+
import process from "process";
|
|
220
|
+
var FINGERPRINT_PREFIX = "moonbase:fingerprint:v2";
|
|
221
|
+
var FINGERPRINT_VERSION = 2;
|
|
222
|
+
var MAX_VALUE_LENGTH = 128;
|
|
223
|
+
var PRINTABLE_ASCII_MIN = 32;
|
|
224
|
+
var PRINTABLE_ASCII_MAX = 126;
|
|
225
|
+
var STAMP_PATTERN = /^mbd(\d+)(n?)_([0-9a-f]{64})$/;
|
|
226
|
+
function canonicalizeValue(value) {
|
|
227
|
+
var _a;
|
|
228
|
+
let printable = "";
|
|
229
|
+
for (const char of value.normalize("NFC")) {
|
|
230
|
+
const code = (_a = char.codePointAt(0)) != null ? _a : 0;
|
|
231
|
+
if (code >= PRINTABLE_ASCII_MIN && code <= PRINTABLE_ASCII_MAX)
|
|
232
|
+
printable += char;
|
|
233
|
+
}
|
|
234
|
+
return printable.slice(0, MAX_VALUE_LENGTH).replace(/^ +| +$/g, "");
|
|
235
|
+
}
|
|
236
|
+
function platformTag(platform = process.platform) {
|
|
237
|
+
switch (platform) {
|
|
238
|
+
case "darwin":
|
|
239
|
+
return "mac";
|
|
240
|
+
case "win32":
|
|
241
|
+
return "windows";
|
|
242
|
+
case "android":
|
|
243
|
+
return "android";
|
|
244
|
+
case "linux":
|
|
245
|
+
return "linux";
|
|
246
|
+
case "freebsd":
|
|
247
|
+
case "openbsd":
|
|
248
|
+
case "netbsd":
|
|
249
|
+
return "bsd";
|
|
250
|
+
default:
|
|
251
|
+
return "unknown";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
|
|
255
|
+
"ioPlatformUuid",
|
|
256
|
+
"machineId",
|
|
257
|
+
"systemUuid",
|
|
258
|
+
"baseboardSerialNumber",
|
|
259
|
+
"deviceName"
|
|
260
|
+
]);
|
|
261
|
+
var IDENTIFYING_PARAM_NAMES = Object.freeze([...IDENTIFYING_PARAMS]);
|
|
262
|
+
var NOT_PROGRAMMED_VALUES = /* @__PURE__ */ new Set([
|
|
263
|
+
"to be filled by o.e.m.",
|
|
264
|
+
// The same filler without the dots, which plenty of firmware writes instead.
|
|
265
|
+
"to be filled by oem",
|
|
266
|
+
"default string",
|
|
267
|
+
"system serial number",
|
|
268
|
+
"base board serial number",
|
|
269
|
+
"chassis serial number",
|
|
270
|
+
"not specified",
|
|
271
|
+
"not applicable",
|
|
272
|
+
"not available",
|
|
273
|
+
"none",
|
|
274
|
+
"unknown",
|
|
275
|
+
"invalid",
|
|
276
|
+
"n/a",
|
|
277
|
+
"0123456789",
|
|
278
|
+
// `machine-id(5)`: the literal marker systemd writes to say "no id yet",
|
|
279
|
+
// e.g. in an initrd or a golden image awaiting first boot. Every machine
|
|
280
|
+
// deployed from such an image reads it, so it is the opposite of an identifier.
|
|
281
|
+
"uninitialized"
|
|
282
|
+
]);
|
|
283
|
+
function isNotProgrammed(value) {
|
|
284
|
+
return NOT_PROGRAMMED_VALUES.has(value.toLowerCase()) || /^0+$/.test(value) || /^f+$/i.test(value);
|
|
285
|
+
}
|
|
286
|
+
function canonicalizeParams(params) {
|
|
287
|
+
const kept = [];
|
|
288
|
+
const seen = /* @__PURE__ */ new Set();
|
|
289
|
+
for (const [name, rawValue] of params) {
|
|
290
|
+
const value = canonicalizeValue(rawValue);
|
|
291
|
+
if (value.length === 0)
|
|
292
|
+
continue;
|
|
293
|
+
if (IDENTIFYING_PARAMS.has(name) && isNotProgrammed(value))
|
|
294
|
+
continue;
|
|
295
|
+
if (seen.has(name))
|
|
296
|
+
throw new Error(`Duplicate fingerprint parameter name: ${name}`);
|
|
297
|
+
seen.add(name);
|
|
298
|
+
kept.push([name, value]);
|
|
299
|
+
}
|
|
300
|
+
return kept;
|
|
301
|
+
}
|
|
302
|
+
function buildFingerprintMaterial(platform, params) {
|
|
303
|
+
const kept = canonicalizeParams(params);
|
|
304
|
+
if (kept.length === 0)
|
|
305
|
+
throw new InsufficientDeviceIdentityError(platform, "no identity parameter could be read");
|
|
306
|
+
if (!kept.some(([name]) => IDENTIFYING_PARAMS.has(name))) {
|
|
307
|
+
throw new InsufficientDeviceIdentityError(
|
|
308
|
+
platform,
|
|
309
|
+
`only model-level parameters could be read (${kept.map(([name]) => name).join(", ")}), none of which identify this individual machine`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const lines = [FINGERPRINT_PREFIX, `platform=${platform}`];
|
|
313
|
+
for (const [name, value] of kept)
|
|
314
|
+
lines.push(`${name}=${value}`);
|
|
315
|
+
return lines.join("\n");
|
|
316
|
+
}
|
|
317
|
+
function fingerprintDigest(material) {
|
|
318
|
+
return createHash("sha256").update(material, "utf8").digest("hex");
|
|
319
|
+
}
|
|
320
|
+
function stampDeviceId(digest, source = "identity") {
|
|
321
|
+
return `mbd${FINGERPRINT_VERSION}${source === "deviceName" ? "n" : ""}_${digest}`;
|
|
322
|
+
}
|
|
323
|
+
function fingerprintDeviceId(material, source = "identity") {
|
|
324
|
+
return stampDeviceId(fingerprintDigest(material), source);
|
|
325
|
+
}
|
|
326
|
+
function parseDeviceIdStamp(deviceId) {
|
|
327
|
+
const match = STAMP_PATTERN.exec(deviceId);
|
|
328
|
+
if (!match)
|
|
329
|
+
return null;
|
|
330
|
+
return {
|
|
331
|
+
version: Number(match[1]),
|
|
332
|
+
source: match[2] === "n" ? "deviceName" : "identity",
|
|
333
|
+
digest: match[3]
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function commandOutput(command) {
|
|
337
|
+
try {
|
|
338
|
+
return cp.execSync(command, { encoding: "utf8" });
|
|
339
|
+
} catch (e) {
|
|
340
|
+
return "";
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function readSysFile(path2) {
|
|
344
|
+
try {
|
|
345
|
+
return fs.readFileSync(path2, "utf8");
|
|
346
|
+
} catch (e) {
|
|
347
|
+
return "";
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function parseIoregPlatformUuid(ioregOutput) {
|
|
351
|
+
const match = ioregOutput.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
352
|
+
return match ? match[1].replaceAll("-", "").toUpperCase() : "";
|
|
353
|
+
}
|
|
354
|
+
function readMacIdentity() {
|
|
355
|
+
const uuid = parseIoregPlatformUuid(commandOutput("ioreg -rd1 -c IOPlatformExpertDevice"));
|
|
356
|
+
const params = [["ioPlatformUuid", uuid]];
|
|
357
|
+
return { params, deviceName: os.hostname().replace(/\.local$/i, "") };
|
|
358
|
+
}
|
|
359
|
+
function selectMachineId(...sources) {
|
|
360
|
+
var _a;
|
|
361
|
+
return (_a = sources.map(canonicalizeValue).find((value) => /^[0-9a-f]{32}$/.test(value) && !isNotProgrammed(value))) != null ? _a : "";
|
|
362
|
+
}
|
|
363
|
+
function readMachineId() {
|
|
364
|
+
return selectMachineId(
|
|
365
|
+
readSysFile("/etc/machine-id"),
|
|
366
|
+
readSysFile("/var/lib/dbus/machine-id")
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
function readLinuxIdentity() {
|
|
370
|
+
const params = [
|
|
371
|
+
["machineId", readMachineId()],
|
|
372
|
+
["sysVendor", readSysFile("/sys/class/dmi/id/sys_vendor")],
|
|
373
|
+
["productName", readSysFile("/sys/class/dmi/id/product_name")],
|
|
374
|
+
["boardVendor", readSysFile("/sys/class/dmi/id/board_vendor")],
|
|
375
|
+
["boardName", readSysFile("/sys/class/dmi/id/board_name")]
|
|
376
|
+
];
|
|
377
|
+
return { params, deviceName: os.hostname() };
|
|
378
|
+
}
|
|
379
|
+
function parseSmbiosStructures(data) {
|
|
380
|
+
const structures = [];
|
|
381
|
+
let offset = 0;
|
|
382
|
+
while (offset + 4 <= data.length) {
|
|
383
|
+
const type = data[offset];
|
|
384
|
+
const length = data[offset + 1];
|
|
385
|
+
if (length < 4 || offset + length > data.length)
|
|
386
|
+
break;
|
|
387
|
+
const formatted = data.subarray(offset, offset + length);
|
|
388
|
+
const strings = [];
|
|
389
|
+
let p = offset + length;
|
|
390
|
+
if (p + 1 < data.length && data[p] === 0 && data[p + 1] === 0) {
|
|
391
|
+
p += 2;
|
|
392
|
+
} else {
|
|
393
|
+
while (p < data.length) {
|
|
394
|
+
let end = p;
|
|
395
|
+
while (end < data.length && data[end] !== 0)
|
|
396
|
+
end++;
|
|
397
|
+
strings.push(data.toString("latin1", p, end));
|
|
398
|
+
p = end + 1;
|
|
399
|
+
if (p < data.length && data[p] === 0) {
|
|
400
|
+
p += 1;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
structures.push({ type, formatted, strings });
|
|
406
|
+
if (type === 127)
|
|
407
|
+
break;
|
|
408
|
+
offset = p;
|
|
409
|
+
}
|
|
410
|
+
return structures;
|
|
411
|
+
}
|
|
412
|
+
function resolveSmbiosString(structure, fieldOffset) {
|
|
413
|
+
if (fieldOffset >= structure.formatted.length)
|
|
414
|
+
return "";
|
|
415
|
+
const index = structure.formatted[fieldOffset];
|
|
416
|
+
if (index === 0 || index > structure.strings.length)
|
|
417
|
+
return "";
|
|
418
|
+
return structure.strings[index - 1];
|
|
419
|
+
}
|
|
420
|
+
function formatSmbiosUuid(structure, fieldOffset) {
|
|
421
|
+
if (fieldOffset + 16 > structure.formatted.length)
|
|
422
|
+
return "";
|
|
423
|
+
const bytes = structure.formatted.subarray(fieldOffset, fieldOffset + 16);
|
|
424
|
+
if (bytes.every((byte) => byte === 0) || bytes.every((byte) => byte === 255))
|
|
425
|
+
return "";
|
|
426
|
+
let hex = "";
|
|
427
|
+
for (const byte of bytes)
|
|
428
|
+
hex += byte.toString(16).padStart(2, "0").toUpperCase();
|
|
429
|
+
return hex;
|
|
430
|
+
}
|
|
431
|
+
function parseSmbiosParams(smbiosData) {
|
|
432
|
+
const structures = parseSmbiosStructures(smbiosData);
|
|
433
|
+
const params = [];
|
|
434
|
+
const system = structures.find((structure) => structure.type === 1);
|
|
435
|
+
if (system) {
|
|
436
|
+
params.push(["systemManufacturer", resolveSmbiosString(system, 4)]);
|
|
437
|
+
params.push(["systemProductName", resolveSmbiosString(system, 5)]);
|
|
438
|
+
params.push(["systemUuid", formatSmbiosUuid(system, 8)]);
|
|
439
|
+
}
|
|
440
|
+
const baseboard = structures.find((structure) => structure.type === 2);
|
|
441
|
+
if (baseboard) {
|
|
442
|
+
params.push(["baseboardManufacturer", resolveSmbiosString(baseboard, 4)]);
|
|
443
|
+
params.push(["baseboardProduct", resolveSmbiosString(baseboard, 5)]);
|
|
444
|
+
params.push(["baseboardSerialNumber", resolveSmbiosString(baseboard, 7)]);
|
|
445
|
+
}
|
|
446
|
+
return params;
|
|
447
|
+
}
|
|
448
|
+
function readWindowsSmbios() {
|
|
449
|
+
const script = "[Convert]::ToBase64String((Get-CimInstance -Namespace root/wmi -ClassName MSSmBios_RawSMBiosTables).SMBiosData)";
|
|
450
|
+
const output = commandOutput(`powershell -NoProfile -NonInteractive -Command "${script}"`);
|
|
451
|
+
try {
|
|
452
|
+
return Buffer.from(output.trim(), "base64");
|
|
453
|
+
} catch (e) {
|
|
454
|
+
return Buffer.alloc(0);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function readWindowsIdentity() {
|
|
458
|
+
return { params: parseSmbiosParams(readWindowsSmbios()), deviceName: os.hostname() };
|
|
459
|
+
}
|
|
460
|
+
function defaultDeviceIdentityReader(platform = process.platform) {
|
|
461
|
+
return {
|
|
462
|
+
read() {
|
|
463
|
+
switch (platform) {
|
|
464
|
+
case "darwin":
|
|
465
|
+
return readMacIdentity();
|
|
466
|
+
case "linux":
|
|
467
|
+
return readLinuxIdentity();
|
|
468
|
+
case "win32":
|
|
469
|
+
return readWindowsIdentity();
|
|
470
|
+
// android, bsd and anything else define no identity params: the spec
|
|
471
|
+
// has no stable hardware source for them, so they resolve to an
|
|
472
|
+
// insufficient-identity error unless the deviceName fallback is enabled.
|
|
473
|
+
default:
|
|
474
|
+
return { params: [], deviceName: os.hostname() };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/deviceIdResolver.ts
|
|
481
|
+
var MoonbaseDeviceIdResolver = class {
|
|
482
|
+
constructor(options) {
|
|
483
|
+
var _a, _b, _c;
|
|
484
|
+
const platform = (_a = options == null ? void 0 : options.platform) != null ? _a : process2.platform;
|
|
485
|
+
this.platform = platformTag(platform);
|
|
486
|
+
this.reader = (_b = options == null ? void 0 : options.reader) != null ? _b : defaultDeviceIdentityReader(platform);
|
|
487
|
+
this.fallback = (_c = options == null ? void 0 : options.fallback) != null ? _c : "none";
|
|
488
|
+
}
|
|
489
|
+
async resolveDeviceName() {
|
|
490
|
+
return this.readIdentity().deviceName;
|
|
491
|
+
}
|
|
492
|
+
async resolveDeviceId() {
|
|
493
|
+
return this.compute().deviceId;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* A fresh copy each call. The device binding is what activation sends and what
|
|
497
|
+
* validation compares, so handing out the resolver's own object would let a
|
|
498
|
+
* consumer that edits a diagnostic — or logs it through something that
|
|
499
|
+
* normalizes in place — silently change the id every later call returns.
|
|
500
|
+
*/
|
|
501
|
+
async describeDevice() {
|
|
502
|
+
const described = this.compute();
|
|
503
|
+
return { ...described, paramNames: [...described.paramNames] };
|
|
504
|
+
}
|
|
505
|
+
compute() {
|
|
506
|
+
var _a;
|
|
507
|
+
(_a = this.described) != null ? _a : this.described = this.computeDescription();
|
|
508
|
+
return this.described;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Read identity at most once. Both halves of an activation request ask for it —
|
|
512
|
+
* the name and then the id — and a read can mean spawning `ioreg` or
|
|
513
|
+
* PowerShell, so reading per call would double the cost of every request and
|
|
514
|
+
* let the name and the id come from two different reads of the machine.
|
|
515
|
+
*/
|
|
516
|
+
readIdentity() {
|
|
517
|
+
var _a;
|
|
518
|
+
(_a = this.identity) != null ? _a : this.identity = this.reader.read();
|
|
519
|
+
return this.identity;
|
|
520
|
+
}
|
|
521
|
+
computeDescription() {
|
|
522
|
+
const { params, deviceName } = this.readIdentity();
|
|
523
|
+
try {
|
|
524
|
+
return this.describe(params, "identity");
|
|
525
|
+
} catch (err) {
|
|
526
|
+
if (this.fallback !== "deviceName" || !(err instanceof InsufficientDeviceIdentityError))
|
|
527
|
+
throw err;
|
|
528
|
+
return this.describe([["deviceName", deviceName]], "deviceName");
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
describe(params, source) {
|
|
532
|
+
return {
|
|
533
|
+
deviceId: fingerprintDeviceId(buildFingerprintMaterial(this.platform, params), source),
|
|
534
|
+
version: FINGERPRINT_VERSION,
|
|
535
|
+
platform: this.platform,
|
|
536
|
+
source,
|
|
537
|
+
paramNames: canonicalizeParams(params).map(([name]) => name)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
var MigratingDeviceIdResolver = class {
|
|
542
|
+
constructor(current, ...previous) {
|
|
543
|
+
this.current = current;
|
|
544
|
+
this.previous = previous;
|
|
545
|
+
const describe = current.describeDevice;
|
|
546
|
+
if (typeof describe === "function")
|
|
547
|
+
this.describeDevice = () => describe.call(current);
|
|
548
|
+
}
|
|
549
|
+
async resolveDeviceName() {
|
|
550
|
+
return this.current.resolveDeviceName();
|
|
551
|
+
}
|
|
552
|
+
async resolveDeviceId() {
|
|
553
|
+
return this.current.resolveDeviceId();
|
|
554
|
+
}
|
|
555
|
+
async acceptsDeviceId(deviceId) {
|
|
556
|
+
if (!this.previousIds) {
|
|
557
|
+
this.previousIds = Promise.all(this.previous.map(async (resolver) => {
|
|
558
|
+
try {
|
|
559
|
+
return await resolver.resolveDeviceId();
|
|
560
|
+
} catch (e) {
|
|
561
|
+
return "";
|
|
562
|
+
}
|
|
563
|
+
}));
|
|
564
|
+
}
|
|
565
|
+
return (await this.previousIds).some((previous) => previous.length > 0 && previous === deviceId);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
var LegacyDeviceIdResolver = class {
|
|
165
569
|
async resolveDeviceName() {
|
|
166
|
-
switch (
|
|
570
|
+
switch (process2.platform) {
|
|
167
571
|
case "darwin":
|
|
168
572
|
try {
|
|
169
|
-
return
|
|
573
|
+
return cp2.execSync("scutil --get ComputerName").toString().trim();
|
|
170
574
|
} catch (e) {
|
|
171
|
-
return
|
|
575
|
+
return os2.hostname();
|
|
172
576
|
}
|
|
173
577
|
case "win32":
|
|
174
|
-
return
|
|
175
|
-
case "linux":
|
|
176
|
-
const prettyname =
|
|
177
|
-
return prettyname ||
|
|
578
|
+
return process2.env.COMPUTERNAME || os2.hostname();
|
|
579
|
+
case "linux": {
|
|
580
|
+
const prettyname = cp2.execSync("hostnamectl --pretty").toString().trim();
|
|
581
|
+
return prettyname || os2.hostname();
|
|
582
|
+
}
|
|
178
583
|
default:
|
|
179
|
-
return
|
|
584
|
+
return os2.hostname();
|
|
180
585
|
}
|
|
181
586
|
}
|
|
182
587
|
async resolveDeviceId() {
|
|
183
588
|
const [cpu, hw] = await Promise.all([si.cpu(), si.system()]);
|
|
184
589
|
const parts = [
|
|
185
|
-
|
|
590
|
+
os2.hostname(),
|
|
186
591
|
hw.manufacturer,
|
|
187
592
|
hw.model,
|
|
188
593
|
hw.serial,
|
|
@@ -194,40 +599,15 @@ var DefaultDeviceIdResolver = class {
|
|
|
194
599
|
cpu.family,
|
|
195
600
|
cpu.model
|
|
196
601
|
];
|
|
197
|
-
const hash =
|
|
602
|
+
const hash = createHash2("sha256").update(parts.join("")).digest();
|
|
198
603
|
return hash.toString("base64").replaceAll("=", "");
|
|
199
604
|
}
|
|
200
605
|
};
|
|
201
606
|
|
|
202
607
|
// src/store.ts
|
|
203
608
|
import fsSync from "fs";
|
|
204
|
-
import
|
|
609
|
+
import fs2 from "fs/promises";
|
|
205
610
|
import path from "path";
|
|
206
|
-
|
|
207
|
-
// src/errors.ts
|
|
208
|
-
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
209
|
-
ErrorType2["None"] = "None";
|
|
210
|
-
ErrorType2["ApiError"] = "ApiError";
|
|
211
|
-
ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
|
|
212
|
-
ErrorType2["LicenseInvalid"] = "LicenseInvalid";
|
|
213
|
-
ErrorType2["LicenseRevoked"] = "LicenseRevoked";
|
|
214
|
-
ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
|
|
215
|
-
ErrorType2["LicenseExpired"] = "LicenseExpired";
|
|
216
|
-
return ErrorType2;
|
|
217
|
-
})(ErrorType || {});
|
|
218
|
-
var MoonbaseError = class extends Error {
|
|
219
|
-
constructor(title, detail, type, inner) {
|
|
220
|
-
super();
|
|
221
|
-
this.title = title;
|
|
222
|
-
this.detail = detail;
|
|
223
|
-
this.type = type;
|
|
224
|
-
this.inner = inner;
|
|
225
|
-
this.name = "MoonbaseError";
|
|
226
|
-
this.message = detail != null ? detail : title;
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// src/store.ts
|
|
231
611
|
var InMemoryLicenseStore = class {
|
|
232
612
|
async loadLocalLicense() {
|
|
233
613
|
var _a;
|
|
@@ -245,9 +625,10 @@ var FileLicenseStore = class {
|
|
|
245
625
|
this.options = options;
|
|
246
626
|
}
|
|
247
627
|
async loadLocalLicense() {
|
|
248
|
-
if (!fsSync.existsSync(this.path))
|
|
628
|
+
if (!fsSync.existsSync(this.path))
|
|
629
|
+
return null;
|
|
249
630
|
try {
|
|
250
|
-
const file = await
|
|
631
|
+
const file = await fs2.readFile(this.path, { encoding: "utf-8" });
|
|
251
632
|
const license = licenseSchema.parse(JSON.parse(file));
|
|
252
633
|
return license;
|
|
253
634
|
} catch (err) {
|
|
@@ -255,11 +636,12 @@ var FileLicenseStore = class {
|
|
|
255
636
|
}
|
|
256
637
|
}
|
|
257
638
|
async storeLocalLicense(license) {
|
|
258
|
-
await
|
|
639
|
+
await fs2.writeFile(this.path, JSON.stringify(license), { encoding: "utf-8" });
|
|
259
640
|
}
|
|
260
641
|
async deleteLocalLicense() {
|
|
261
|
-
if (!fsSync.existsSync(this.path))
|
|
262
|
-
|
|
642
|
+
if (!fsSync.existsSync(this.path))
|
|
643
|
+
return;
|
|
644
|
+
await fs2.rm(this.path);
|
|
263
645
|
}
|
|
264
646
|
get path() {
|
|
265
647
|
var _a, _b, _c, _d;
|
|
@@ -268,8 +650,8 @@ var FileLicenseStore = class {
|
|
|
268
650
|
};
|
|
269
651
|
|
|
270
652
|
// src/validator.ts
|
|
271
|
-
import { z as z2 } from "zod";
|
|
272
653
|
import jwt from "jsonwebtoken";
|
|
654
|
+
import { z as z2 } from "zod";
|
|
273
655
|
var licenseClaimsSchema = z2.object({
|
|
274
656
|
["l:id" /* LicenseId */]: z2.string(),
|
|
275
657
|
["trial" /* Trial */]: z2.string().or(z2.boolean()).transform((v) => v === true || v === "true").pipe(z2.boolean()),
|
|
@@ -306,15 +688,20 @@ var LicenseValidator = class {
|
|
|
306
688
|
);
|
|
307
689
|
}
|
|
308
690
|
const expectedSignature = await this.deviceIdResolver.resolveDeviceId();
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"This license is not for this device",
|
|
313
|
-
"LicenseExpired" /* LicenseExpired */
|
|
314
|
-
);
|
|
315
|
-
}
|
|
691
|
+
const boundSignature = claims["sig" /* ComputerSignature */];
|
|
692
|
+
if (expectedSignature !== boundSignature && !await this.acceptsHistoricalDeviceId(boundSignature))
|
|
693
|
+
throw deviceMismatchError(expectedSignature, boundSignature);
|
|
316
694
|
return license;
|
|
317
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* Give a {@link IMigratingDeviceIdResolver} the chance to vouch for a device id
|
|
698
|
+
* this machine used to have. Only consulted after the fast path fails, so
|
|
699
|
+
* apps that have not opted into a migration pay nothing.
|
|
700
|
+
*/
|
|
701
|
+
async acceptsHistoricalDeviceId(boundSignature) {
|
|
702
|
+
const accepts = this.deviceIdResolver.acceptsDeviceId;
|
|
703
|
+
return typeof accepts === "function" && await accepts.call(this.deviceIdResolver, boundSignature);
|
|
704
|
+
}
|
|
318
705
|
parseLicenseToken(token) {
|
|
319
706
|
return new Promise((resolve, reject) => {
|
|
320
707
|
jwt.verify(token, this.configuration.publicKey, {
|
|
@@ -379,11 +766,34 @@ var LicenseValidator = class {
|
|
|
379
766
|
});
|
|
380
767
|
}
|
|
381
768
|
};
|
|
769
|
+
function deviceMismatchError(expected, bound) {
|
|
770
|
+
const detail = "This license is not for this device";
|
|
771
|
+
const versionNote = describeVersionDifference(expected, bound);
|
|
772
|
+
return new MoonbaseError(
|
|
773
|
+
"License is for another device",
|
|
774
|
+
versionNote ? `${detail}. ${versionNote}` : detail,
|
|
775
|
+
"LicenseDeviceMismatch" /* LicenseDeviceMismatch */
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
function describeVersionDifference(expected, bound) {
|
|
779
|
+
const expectedStamp = parseDeviceIdStamp(expected);
|
|
780
|
+
if (!expectedStamp)
|
|
781
|
+
return null;
|
|
782
|
+
const boundStamp = parseDeviceIdStamp(bound);
|
|
783
|
+
if (boundStamp && boundStamp.version === expectedStamp.version)
|
|
784
|
+
return null;
|
|
785
|
+
if (boundStamp && boundStamp.version > expectedStamp.version) {
|
|
786
|
+
return `The binding was created by device fingerprint v${boundStamp.version}, which is newer than the v${expectedStamp.version} this SDK computes \u2014 update the SDK rather than re-activating, which would rebind the device to the older algorithm.`;
|
|
787
|
+
}
|
|
788
|
+
const boundVersion = boundStamp ? `device fingerprint v${boundStamp.version}` : "an SDK predating versioned device fingerprints";
|
|
789
|
+
return `The binding was created by ${boundVersion}, while this SDK computes v${expectedStamp.version}, so this may instead be the same machine bound under the older algorithm \u2014 re-activate to find out, or configure a MigratingDeviceIdResolver to keep accepting the previous id.`;
|
|
790
|
+
}
|
|
382
791
|
|
|
383
792
|
// src/index.ts
|
|
384
793
|
function detectPlatform() {
|
|
385
|
-
if (typeof
|
|
386
|
-
|
|
794
|
+
if (typeof process3 === "undefined")
|
|
795
|
+
return void 0;
|
|
796
|
+
switch (process3.platform) {
|
|
387
797
|
case "darwin":
|
|
388
798
|
return "Mac";
|
|
389
799
|
case "win32":
|
|
@@ -403,7 +813,7 @@ var MoonbaseLicensing = class {
|
|
|
403
813
|
platform: configuration.platform === void 0 ? detectPlatform() : configuration.platform
|
|
404
814
|
};
|
|
405
815
|
this.store = (_a = configuration.licenseStore) != null ? _a : new InMemoryLicenseStore();
|
|
406
|
-
this.deviceIdResolver = (_b = configuration.
|
|
816
|
+
this.deviceIdResolver = (_b = configuration.deviceIdResolver) != null ? _b : new MoonbaseDeviceIdResolver();
|
|
407
817
|
this.validator = new LicenseValidator(this.configuration, this.deviceIdResolver);
|
|
408
818
|
this.client = new LicenseClient(this.configuration, this.deviceIdResolver, this.validator);
|
|
409
819
|
}
|
|
@@ -415,7 +825,7 @@ var MoonbaseLicensing = class {
|
|
|
415
825
|
format: "JWT"
|
|
416
826
|
};
|
|
417
827
|
const json = JSON.stringify(token);
|
|
418
|
-
return
|
|
828
|
+
return Buffer2.from(Buffer2.from(json).toString("base64"));
|
|
419
829
|
}
|
|
420
830
|
async readRawLicense(license) {
|
|
421
831
|
return await this.client.validateRawLicense(license);
|
|
@@ -423,12 +833,31 @@ var MoonbaseLicensing = class {
|
|
|
423
833
|
};
|
|
424
834
|
export {
|
|
425
835
|
ActivationMethod,
|
|
426
|
-
DefaultDeviceIdResolver,
|
|
427
836
|
ErrorType,
|
|
837
|
+
FINGERPRINT_PREFIX,
|
|
838
|
+
FINGERPRINT_VERSION,
|
|
428
839
|
FileLicenseStore,
|
|
840
|
+
IDENTIFYING_PARAM_NAMES,
|
|
429
841
|
InMemoryLicenseStore,
|
|
842
|
+
InsufficientDeviceIdentityError,
|
|
843
|
+
LegacyDeviceIdResolver,
|
|
430
844
|
LicenseClient,
|
|
431
845
|
LicenseValidator,
|
|
846
|
+
MAX_VALUE_LENGTH,
|
|
847
|
+
MigratingDeviceIdResolver,
|
|
848
|
+
MoonbaseDeviceIdResolver,
|
|
432
849
|
MoonbaseError,
|
|
433
|
-
MoonbaseLicensing
|
|
850
|
+
MoonbaseLicensing,
|
|
851
|
+
buildFingerprintMaterial,
|
|
852
|
+
canonicalizeParams,
|
|
853
|
+
canonicalizeValue,
|
|
854
|
+
defaultDeviceIdentityReader,
|
|
855
|
+
fingerprintDeviceId,
|
|
856
|
+
fingerprintDigest,
|
|
857
|
+
parseDeviceIdStamp,
|
|
858
|
+
parseIoregPlatformUuid,
|
|
859
|
+
parseSmbiosParams,
|
|
860
|
+
platformTag,
|
|
861
|
+
selectMachineId,
|
|
862
|
+
stampDeviceId
|
|
434
863
|
};
|