@serviceme/devtools-core 2.0.5 → 2.0.6
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/{device-BVcUUQ9t.mjs → device-Bn2NzajD.mjs} +2 -2
- package/dist/{device-BVcUUQ9t.mjs.map → device-Bn2NzajD.mjs.map} +1 -1
- package/dist/index.js +84 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -27
- package/dist/index.mjs.map +1 -1
- package/dist/{submit-6zQPPrOJ.mjs → submit-Cg7wSa3x.mjs} +2 -2
- package/dist/{submit-6zQPPrOJ.mjs.map → submit-Cg7wSa3x.mjs.map} +1 -1
- package/dist/{toolbox-UG4dA2TI.mjs → toolbox-CbfxpNg-.mjs} +2 -2
- package/dist/{toolbox-UG4dA2TI.mjs.map → toolbox-CbfxpNg-.mjs.map} +1 -1
- package/dist/{userHome-DOMLiHy7.mjs → userHome-CcnAQPG1.mjs} +4 -2
- package/dist/userHome-CcnAQPG1.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/device-uFAQxlNt.js +0 -874
- package/dist/device-uFAQxlNt.js.map +0 -1
- package/dist/submit-Dy0KTHzm.js +0 -98
- package/dist/submit-Dy0KTHzm.js.map +0 -1
- package/dist/toolbox-BV7vLGXZ.js +0 -614
- package/dist/toolbox-BV7vLGXZ.js.map +0 -1
- package/dist/userHome-CLlsXCvW.js +0 -518
- package/dist/userHome-CLlsXCvW.js.map +0 -1
- package/dist/userHome-DOMLiHy7.mjs.map +0 -1
package/dist/device-uFAQxlNt.js
DELETED
|
@@ -1,874 +0,0 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.js");
|
|
2
|
-
const require_userHome = require("./userHome-CLlsXCvW.js");
|
|
3
|
-
let node_fs_promises = require("node:fs/promises");
|
|
4
|
-
node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises);
|
|
5
|
-
let node_path = require("node:path");
|
|
6
|
-
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
7
|
-
let node_os = require("node:os");
|
|
8
|
-
node_os = require_rolldown_runtime.__toESM(node_os);
|
|
9
|
-
let node_crypto = require("node:crypto");
|
|
10
|
-
let node_timers_promises = require("node:timers/promises");
|
|
11
|
-
//#region src/device/deviceAuth.ts
|
|
12
|
-
/**
|
|
13
|
-
* deviceAuth — Device request signing helpers for device auth headers.
|
|
14
|
-
*
|
|
15
|
-
* **Boundary exception copy.** The single source of truth for the
|
|
16
|
-
* `x-ms-device-*` header names + the signature algorithms now lives in
|
|
17
|
-
* `@serviceme/devtools-shared` (`device-auth.ts`), consumed by the
|
|
18
|
-
* extension signer and the server verifier. ADL-003 forbids
|
|
19
|
-
* core → shared (and shared → core), so this module keeps a
|
|
20
|
-
* byte-for-byte copy as the documented exception. Keep it in lock-step
|
|
21
|
-
* with `packages/serviceme-shared/src/device-auth.ts`.
|
|
22
|
-
*
|
|
23
|
-
* See `docs/architecture/phase-5-device-header-spec.md` §5 for the wire
|
|
24
|
-
* format. Legacy (v1) basis is `METHOD\nPATH\nTIMESTAMP\nBODY\nSECRET`
|
|
25
|
-
* hashed with a secret-suffix SHA-256; v2 basis is
|
|
26
|
-
* `METHOD\nPATH?QUERY\nTIMESTAMP\nNONCE\nBODY\nSECRET` MACed with real
|
|
27
|
-
* HMAC-SHA-256 keyed by the device secret and signed over the full
|
|
28
|
-
* path-with-query. Both output lowercase hex.
|
|
29
|
-
*/
|
|
30
|
-
/** Canonical header names — MUST match `@serviceme/devtools-shared`'s `DeviceAuthHeaders`. */
|
|
31
|
-
const DeviceAuthHeaders = {
|
|
32
|
-
deviceId: "x-ms-device-id",
|
|
33
|
-
deviceSecret: "x-ms-device-secret",
|
|
34
|
-
signature: "x-ms-device-signature",
|
|
35
|
-
timestamp: "x-ms-device-timestamp",
|
|
36
|
-
secretVersion: "x-ms-device-secret-version",
|
|
37
|
-
/** v2 only — algorithm self-declaration so the server can verify
|
|
38
|
-
* both schemes during the legacy-client rollout window. */
|
|
39
|
-
sigAlg: "x-ms-device-sig-alg",
|
|
40
|
-
/** v2 only — one-time value folded into the signature basis and
|
|
41
|
-
* checked against a server-side replay cache. */
|
|
42
|
-
nonce: "x-ms-device-nonce"
|
|
43
|
-
};
|
|
44
|
-
/** v2 signature algorithm identifier carried in `x-ms-device-sig-alg`. */
|
|
45
|
-
const DEVICE_SIG_ALG_V2 = "hmac-sha256-v2";
|
|
46
|
-
/**
|
|
47
|
-
* Legacy (v1) secret-suffix SHA-256 over the canonical basis. Retained
|
|
48
|
-
* for backward compatibility with fielded verifiers/clients that
|
|
49
|
-
* predate v2; new code should use `createDeviceRequestSignatureV2`.
|
|
50
|
-
*/
|
|
51
|
-
function createDeviceRequestSignature(params) {
|
|
52
|
-
const basis = [
|
|
53
|
-
params.method.toUpperCase(),
|
|
54
|
-
params.path,
|
|
55
|
-
String(params.timestamp),
|
|
56
|
-
params.body,
|
|
57
|
-
params.secret
|
|
58
|
-
].join("\n");
|
|
59
|
-
return (0, node_crypto.createHash)("sha256").update(basis).digest("hex");
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Compute the v2 HMAC-SHA-256 hex digest over the canonical basis
|
|
63
|
-
* (path-with-query + nonce).
|
|
64
|
-
*
|
|
65
|
-
* Server contract (`apps/server/src/lib/auth/device-signature-guard.ts`)
|
|
66
|
-
* is line-for-line identical: same LF-joined basis, same lowercase hex
|
|
67
|
-
* output. Any divergence breaks `device-signature-guard.test.ts`.
|
|
68
|
-
*/
|
|
69
|
-
function createDeviceRequestSignatureV2(params) {
|
|
70
|
-
const basis = [
|
|
71
|
-
params.method.toUpperCase(),
|
|
72
|
-
params.path,
|
|
73
|
-
String(params.timestamp),
|
|
74
|
-
params.nonce,
|
|
75
|
-
params.body,
|
|
76
|
-
params.secret
|
|
77
|
-
].join("\n");
|
|
78
|
-
return (0, node_crypto.createHmac)("sha256", params.secret).update(basis).digest("hex");
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Build the v2 header map. The `path` parameter MUST include the exact
|
|
82
|
-
* query string sent on the wire, and the `body` parameter MUST be the
|
|
83
|
-
* exact byte sequence sent on the wire (no whitespace
|
|
84
|
-
* re-canonicalization between client serialization and signature basis
|
|
85
|
-
* construction).
|
|
86
|
-
*/
|
|
87
|
-
function buildSignedHeaders(params) {
|
|
88
|
-
const timestamp = params.timestamp ?? Date.now();
|
|
89
|
-
const nonce = params.nonce ?? (0, node_crypto.randomUUID)();
|
|
90
|
-
const signature = createDeviceRequestSignatureV2({
|
|
91
|
-
method: params.method,
|
|
92
|
-
path: params.path,
|
|
93
|
-
timestamp,
|
|
94
|
-
nonce,
|
|
95
|
-
body: params.body,
|
|
96
|
-
secret: params.deviceSecret
|
|
97
|
-
});
|
|
98
|
-
return {
|
|
99
|
-
[DeviceAuthHeaders.deviceId]: params.publicId,
|
|
100
|
-
[DeviceAuthHeaders.deviceSecret]: params.deviceSecret,
|
|
101
|
-
[DeviceAuthHeaders.signature]: signature,
|
|
102
|
-
[DeviceAuthHeaders.timestamp]: String(timestamp),
|
|
103
|
-
[DeviceAuthHeaders.secretVersion]: String(params.secretVersion),
|
|
104
|
-
[DeviceAuthHeaders.sigAlg]: DEVICE_SIG_ALG_V2,
|
|
105
|
-
[DeviceAuthHeaders.nonce]: nonce
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/device/InstallationId.ts
|
|
110
|
-
/**
|
|
111
|
-
* InstallationId — Derive a stable per-machine identifier from
|
|
112
|
-
* `os.hostname()` + `os.userInfo()`.
|
|
113
|
-
*
|
|
114
|
-
* Per `docs/architecture/phase-5-auth-device-toolbox.md § P5-2 (并入本文时对应 § P5-2 拆分)` B2,
|
|
115
|
-
* `installationId` MUST survive Extension re-installs but vary across
|
|
116
|
-
* machines. We compute a UUID v5-style hash over hostname + username +
|
|
117
|
-
* platform so the result is:
|
|
118
|
-
* - deterministic (same machine → same id)
|
|
119
|
-
* - collision-resistant (SHA-256, 128-bit truncated)
|
|
120
|
-
* - browser-safe (no PII survives — username never enters output)
|
|
121
|
-
*
|
|
122
|
-
* Note: this intentionally differs from `vscode.env.machineId`, which
|
|
123
|
-
* is per-Extension-install and uses a different algorithm. The two
|
|
124
|
-
* coexist: `installationId` is what gets sent to the server, while
|
|
125
|
-
* `machineId` (raw `os.hostname()`) is for diagnostics.
|
|
126
|
-
*
|
|
127
|
-
* Refs:
|
|
128
|
-
* - 4.功能规划.md §2.2 — `InstallationId.ts os.hostname() + os.userInfo() 哈希生成`
|
|
129
|
-
* - 3.功能拆分.md B2 — installationId semantics
|
|
130
|
-
*/
|
|
131
|
-
/** Hex-encoded SHA-256 input. Format: `<hostname>|<username>|<platform>|<nodeVersion>`. */
|
|
132
|
-
function fingerprintMaterial() {
|
|
133
|
-
let username = "unknown";
|
|
134
|
-
try {
|
|
135
|
-
username = node_os.userInfo().username;
|
|
136
|
-
} catch {
|
|
137
|
-
username = process.env.USER ?? process.env.USERNAME ?? "unknown";
|
|
138
|
-
}
|
|
139
|
-
return [
|
|
140
|
-
node_os.hostname(),
|
|
141
|
-
username,
|
|
142
|
-
node_os.platform(),
|
|
143
|
-
node_os.arch(),
|
|
144
|
-
process.versions.node ?? "unknown"
|
|
145
|
-
].join("|");
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Returns a deterministic installation id for the current machine.
|
|
149
|
-
* Use this when you need an id that survives Extension reinstalls
|
|
150
|
-
* but stays stable across restarts on the same machine.
|
|
151
|
-
*/
|
|
152
|
-
function deriveInstallationId() {
|
|
153
|
-
const material = fingerprintMaterial();
|
|
154
|
-
return formatAsV4((0, node_crypto.createHash)("sha256").update(material).digest("hex").slice(0, 32));
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Returns a random installation id (UUID v4). Use this for fresh
|
|
158
|
-
* installs when no fingerprint input is available (e.g. containerized
|
|
159
|
-
* CI runners where `os.hostname()` is meaningless).
|
|
160
|
-
*/
|
|
161
|
-
function randomInstallationId() {
|
|
162
|
-
return (0, node_crypto.randomUUID)();
|
|
163
|
-
}
|
|
164
|
-
/** SHA-256 fingerprint material exposed for tests + diagnostics. */
|
|
165
|
-
function fingerprintSource() {
|
|
166
|
-
return fingerprintMaterial();
|
|
167
|
-
}
|
|
168
|
-
function formatAsV4(hex32) {
|
|
169
|
-
const chars = hex32.split("");
|
|
170
|
-
const versionIdx = 12;
|
|
171
|
-
const variantIdx = 16;
|
|
172
|
-
chars[versionIdx] = (parseInt(chars[versionIdx] ?? "8", 16) & 0 | 4).toString(16);
|
|
173
|
-
chars[variantIdx] = (parseInt(chars[variantIdx] ?? "8", 16) & 3 | 8).toString(16);
|
|
174
|
-
const formatted = chars.join("");
|
|
175
|
-
return `${formatted.slice(0, 8)}-${formatted.slice(8, 12)}-${formatted.slice(12, 16)}-${formatted.slice(16, 20)}-${formatted.slice(20, 32)}`;
|
|
176
|
-
}
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region src/device/Enroller.ts
|
|
179
|
-
/**
|
|
180
|
-
* Enroller — State machine for `device.enroll` and `device.rotate-secret`.
|
|
181
|
-
*
|
|
182
|
-
* States per `2.需求澄清.md` §1.2:
|
|
183
|
-
* anonymous → pending → claimed → expired
|
|
184
|
-
*
|
|
185
|
-
* - `anonymous` (initial): no device has ever enrolled. Server returns
|
|
186
|
-
* a fresh `publicId` + secret.
|
|
187
|
-
* - `pending`: enrollment HTTP call has been issued but the server
|
|
188
|
-
* hasn't confirmed yet. In-flight state — never persisted.
|
|
189
|
-
* - `claimed`: user has linked this device to their account (via
|
|
190
|
-
* `/api/v1/devices/claim`). Sticky binding locks future re-enrolls
|
|
191
|
-
* to the same `userId` (server-side matrix).
|
|
192
|
-
* - `expired`: server returned a device-expiry error. Forces a fresh
|
|
193
|
-
* enroll on next call.
|
|
194
|
-
*
|
|
195
|
-
* `--force` semantics: any non-anonymous state can be force-reset to
|
|
196
|
-
* `anonymous` by wiping the local identity file. The next enroll will
|
|
197
|
-
* be treated as a brand-new install by the server (no sticky binding).
|
|
198
|
-
*
|
|
199
|
-
* The Enroller is the **state machine**; the actual HTTP I/O is the
|
|
200
|
-
* caller's responsibility (the `DeviceSyncClient` in Phase 5.4 wires
|
|
201
|
-
* the server). This split keeps the Enroller unit-testable without
|
|
202
|
-
* a live server.
|
|
203
|
-
*
|
|
204
|
-
* Refs:
|
|
205
|
-
* - 4.功能规划.md §2.2 — `Enroller.ts anonymous → pending → claimed → expired`
|
|
206
|
-
* - `2.需求澄清.md` §1.2 — binding-state machine
|
|
207
|
-
*/
|
|
208
|
-
/** 32 bytes of HMAC secret material — matches the server's `device-registration.ts:73-80` generator. */
|
|
209
|
-
const SECRET_BYTES = 32;
|
|
210
|
-
/** Server returns `publicId` as 32-char hex (16 bytes). Match the wire length. */
|
|
211
|
-
const PUBLIC_ID_BYTES = 16;
|
|
212
|
-
const defaultRandomBytes = (size) => {
|
|
213
|
-
return (0, node_crypto.randomBytes)(size);
|
|
214
|
-
};
|
|
215
|
-
/** Sentinel error — re-enroll on a claimed device without auth. */
|
|
216
|
-
var DeviceReenrollRequiresAuthError = class extends Error {
|
|
217
|
-
constructor(message = "Re-enroll on a claimed device requires current device credentials or the bound user") {
|
|
218
|
-
super(message);
|
|
219
|
-
this.name = "DeviceReenrollRequiresAuthError";
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
/** Sentinel error — server returned a 410 / version-mismatch after rotation. */
|
|
223
|
-
var DeviceSecretVersionMismatchError = class extends Error {
|
|
224
|
-
constructor(message = "Device secret version mismatch — server has rotated past the local copy") {
|
|
225
|
-
super(message);
|
|
226
|
-
this.name = "DeviceSecretVersionMismatchError";
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
var Enroller = class {
|
|
230
|
-
constructor(opts) {
|
|
231
|
-
this.inflight = null;
|
|
232
|
-
this.identity = opts.identityStore;
|
|
233
|
-
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
234
|
-
this.random = opts.randomBytes ?? defaultRandomBytes;
|
|
235
|
-
this.enrollRequest = opts.enrollRequest;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Read the current binding state without touching the disk.
|
|
239
|
-
* Returns `anonymous` when no identity is stored.
|
|
240
|
-
*/
|
|
241
|
-
async currentState() {
|
|
242
|
-
return (await this.identity.read())?.bindingState ?? "anonymous";
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Drive the enrollment flow.
|
|
246
|
-
*
|
|
247
|
-
* @param force when true, drop the local identity and start fresh
|
|
248
|
-
* (server treats this as a brand-new install).
|
|
249
|
-
* @param requireAuth when true, refuse to silently re-enroll an
|
|
250
|
-
* existing claimed device — throw
|
|
251
|
-
* `DeviceReenrollRequiresAuthError` instead.
|
|
252
|
-
*/
|
|
253
|
-
/**
|
|
254
|
-
* Resolve when any in-flight enrollment completes. Returns immediately
|
|
255
|
-
* when no enrollment is in progress. Allows callers (e.g. the extension's
|
|
256
|
-
* `buildDeviceAuthHeaders`) to wait for a concurrent `syncDeviceInfo()`
|
|
257
|
-
* enrollment before attempting to read the identity from the store.
|
|
258
|
-
*/
|
|
259
|
-
async waitForEnrollment() {
|
|
260
|
-
if (this.inflight) await this.inflight;
|
|
261
|
-
}
|
|
262
|
-
async enroll(opts = {}) {
|
|
263
|
-
if (this.inflight) return this.inflight;
|
|
264
|
-
const promise = this.runEnroll(opts);
|
|
265
|
-
this.inflight = promise;
|
|
266
|
-
try {
|
|
267
|
-
return await promise;
|
|
268
|
-
} finally {
|
|
269
|
-
if (this.inflight === promise) this.inflight = null;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/** Test seam — surface the underlying identity store. */
|
|
273
|
-
getIdentityStore() {
|
|
274
|
-
return this.identity;
|
|
275
|
-
}
|
|
276
|
-
/** True when an enrollment is currently in-flight. Used by callers (e.g. the extension's `buildDeviceAuthHeaders`) to skip triggering a competing enrollment. */
|
|
277
|
-
isEnrolling() {
|
|
278
|
-
return this.inflight !== null;
|
|
279
|
-
}
|
|
280
|
-
async runEnroll(opts) {
|
|
281
|
-
const { written } = await this.identity.mutate(async (current) => {
|
|
282
|
-
const existing = opts.force ? null : current;
|
|
283
|
-
if (!opts.force && current) {
|
|
284
|
-
if (current.bindingState === "expired") {} else if (opts.requireAuth && (current.bindingState === "claimed" || current.bindingState === "pending")) throw new DeviceReenrollRequiresAuthError();
|
|
285
|
-
}
|
|
286
|
-
const installationId = current?.installationId ?? deriveInstallationId();
|
|
287
|
-
const machineId = current?.machineId ?? "unknown";
|
|
288
|
-
const platform = current?.platform ?? "unknown";
|
|
289
|
-
let response;
|
|
290
|
-
if (this.enrollRequest) response = await this.enrollRequest({
|
|
291
|
-
installationId,
|
|
292
|
-
machineId,
|
|
293
|
-
platform,
|
|
294
|
-
existing,
|
|
295
|
-
force: Boolean(opts.force),
|
|
296
|
-
requireAuth: Boolean(opts.requireAuth)
|
|
297
|
-
});
|
|
298
|
-
else response = synthesizeEnrollResponse(this.random, existing);
|
|
299
|
-
return { next: {
|
|
300
|
-
version: current?.version ?? 1,
|
|
301
|
-
installationId,
|
|
302
|
-
machineId,
|
|
303
|
-
platform,
|
|
304
|
-
hostname: current?.hostname,
|
|
305
|
-
publicId: response.publicId,
|
|
306
|
-
secretVersion: response.secretVersion,
|
|
307
|
-
bindingState: response.bindingState,
|
|
308
|
-
deviceSecret: response.deviceSecret,
|
|
309
|
-
previousDeviceSecret: existing?.deviceSecret,
|
|
310
|
-
previousSecretExpiresAt: opts.force || response.secretVersion === (existing?.secretVersion ?? 0) + 1 ? void 0 : existing?.previousSecretExpiresAt,
|
|
311
|
-
lastEnrollAt: this.now().toISOString(),
|
|
312
|
-
lastSyncAt: existing?.lastSyncAt,
|
|
313
|
-
lastSyncError: void 0
|
|
314
|
-
} };
|
|
315
|
-
});
|
|
316
|
-
return {
|
|
317
|
-
publicId: written.publicId,
|
|
318
|
-
bindingState: written.bindingState,
|
|
319
|
-
expiresAt: deriveExpiresAt(written, this.now)
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Rotate the HMAC secret. Keeps the previous secret for the grace
|
|
324
|
-
* window (default 7 days per `2.需求澄清.md` §1.2) — the
|
|
325
|
-
* `previousSecretExpiresAt` is stamped on the persisted identity.
|
|
326
|
-
*/
|
|
327
|
-
async rotateSecret(opts = {}) {
|
|
328
|
-
const gracePeriodDays = opts.gracePeriodDays ?? 7;
|
|
329
|
-
const now = this.now();
|
|
330
|
-
const newSecret = this.random(SECRET_BYTES).toString("hex");
|
|
331
|
-
const { written } = await this.identity.mutate(async (current) => {
|
|
332
|
-
if (!current) throw new Error("Cannot rotate-secret without a prior enrollment");
|
|
333
|
-
const graceExpiresAt = new Date(now.getTime() + gracePeriodDays * 24 * 60 * 60 * 1e3);
|
|
334
|
-
return { next: {
|
|
335
|
-
...current,
|
|
336
|
-
deviceSecret: newSecret,
|
|
337
|
-
previousDeviceSecret: current.deviceSecret,
|
|
338
|
-
previousSecretExpiresAt: graceExpiresAt.toISOString(),
|
|
339
|
-
secretVersion: current.secretVersion + 1,
|
|
340
|
-
lastEnrollAt: now.toISOString()
|
|
341
|
-
} };
|
|
342
|
-
});
|
|
343
|
-
return {
|
|
344
|
-
publicId: written.publicId,
|
|
345
|
-
secretVersion: written.secretVersion,
|
|
346
|
-
gracePeriodDays
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Mark the device as `expired`. Used when the server returns a
|
|
351
|
-
* device-expiry response; the next `enroll()` call forces a fresh
|
|
352
|
-
* round-trip.
|
|
353
|
-
*/
|
|
354
|
-
async markExpired() {
|
|
355
|
-
await this.identity.mutate(async (current) => {
|
|
356
|
-
if (!current) return {
|
|
357
|
-
next: current ?? await emptyIdentity(this.random),
|
|
358
|
-
result: void 0
|
|
359
|
-
};
|
|
360
|
-
return { next: {
|
|
361
|
-
...current,
|
|
362
|
-
bindingState: "expired"
|
|
363
|
-
} };
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Mark the device as `claimed`. Called by the bridge after a
|
|
368
|
-
* successful `device.claim` server response.
|
|
369
|
-
*/
|
|
370
|
-
async markClaimed() {
|
|
371
|
-
await this.identity.mutate(async (current) => {
|
|
372
|
-
if (!current) throw new Error("Cannot mark-claimed without a prior enrollment");
|
|
373
|
-
return { next: {
|
|
374
|
-
...current,
|
|
375
|
-
bindingState: "claimed",
|
|
376
|
-
lastSyncAt: this.now().toISOString(),
|
|
377
|
-
lastSyncError: void 0
|
|
378
|
-
} };
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
function deriveExpiresAt(_identity, _now) {}
|
|
383
|
-
function synthesizeEnrollResponse(random, existing) {
|
|
384
|
-
const publicId = existing?.publicId ?? random(PUBLIC_ID_BYTES).toString("hex");
|
|
385
|
-
const secretVersion = (existing?.secretVersion ?? 0) + 1;
|
|
386
|
-
return {
|
|
387
|
-
publicId,
|
|
388
|
-
deviceSecret: random(SECRET_BYTES).toString("hex"),
|
|
389
|
-
secretVersion,
|
|
390
|
-
bindingState: existing?.bindingState === "claimed" ? "claimed" : "anonymous"
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
async function emptyIdentity(random) {
|
|
394
|
-
return {
|
|
395
|
-
version: 1,
|
|
396
|
-
installationId: deriveInstallationId(),
|
|
397
|
-
machineId: "unknown",
|
|
398
|
-
platform: "unknown",
|
|
399
|
-
publicId: random(PUBLIC_ID_BYTES).toString("hex"),
|
|
400
|
-
secretVersion: 1,
|
|
401
|
-
bindingState: "anonymous",
|
|
402
|
-
deviceSecret: random(SECRET_BYTES).toString("hex"),
|
|
403
|
-
lastEnrollAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
//#endregion
|
|
407
|
-
//#region src/device/types.ts
|
|
408
|
-
/**
|
|
409
|
-
* Schema version of the on-disk `device.json` file. Bumped when the
|
|
410
|
-
* shape changes incompatibly. IdentityStore checks this on read and
|
|
411
|
-
* either migrates (versions ≤ 1) or refuses (versions > supported).
|
|
412
|
-
*/
|
|
413
|
-
const DEVICE_JSON_SCHEMA_VERSION = 1;
|
|
414
|
-
//#endregion
|
|
415
|
-
//#region src/device/IdentityStore.ts
|
|
416
|
-
/**
|
|
417
|
-
* IdentityStore — Atomic JSON persistence for the device identity file.
|
|
418
|
-
*
|
|
419
|
-
* Stores the `PersistedDeviceIdentity` (incl. the HMAC secret cleartext)
|
|
420
|
-
* at `~/.serviceme/device.json` (per `phase-5-device-header-spec.md`
|
|
421
|
-
* §3.1). Writes are atomic via `write-tmp + fsync + rename`, matching
|
|
422
|
-
* the `SkillStore` / `ToolboxStore` precedent. Concurrent writes are
|
|
423
|
-
* serialized with a mkdir-based file lock (POSIX-atomic) — proper
|
|
424
|
-
* cross-process locking is deferred to Phase 6+ per the open spec.
|
|
425
|
-
*
|
|
426
|
-
* The file mode is `0600` (owner read/write only) so the cleartext
|
|
427
|
-
* secret stays safe at rest. On Windows the mode hint is a no-op
|
|
428
|
-
* (Windows uses ACLs) but `writeFile` still succeeds.
|
|
429
|
-
*
|
|
430
|
-
* Migration — IdentityStore auto-detects a v0-shape (pre-Phase-5.2)
|
|
431
|
-
* file written by the Extension's old `globalState` blob:
|
|
432
|
-
* { version: 1, claimed: false, publicKeyFingerprint: null }
|
|
433
|
-
* In that case the file is migrated forward to the v1 schema on the
|
|
434
|
-
* next write (the data fields are empty and a fresh enroll is required).
|
|
435
|
-
* The full Extension `globalState` → JSON migration happens in the
|
|
436
|
-
* Phase 5.5 adapter (`apps/extension/.../DeviceService.ts`) since the
|
|
437
|
-
* adapter holds the live `globalState` access.
|
|
438
|
-
*
|
|
439
|
-
* Refs:
|
|
440
|
-
* - 4.功能规划.md §2.2 — `IdentityStore.ts 持久化到 ~/.config/serviceme/device.json, 原子写`
|
|
441
|
-
* - `docs/architecture/phase-5-device-header-spec.md` §3.1, §2.5
|
|
442
|
-
*/
|
|
443
|
-
const FILE_MODE = 384;
|
|
444
|
-
const LOCK_DIR_MODE = 448;
|
|
445
|
-
const DEFAULT_LOCK_TIMEOUT_MS = 5e3;
|
|
446
|
-
const DEFAULT_LOCK_RETRY_MS = 25;
|
|
447
|
-
const LOCK_STALE_GRACE_MS = 200;
|
|
448
|
-
const TMP_SUFFIX = ".tmp";
|
|
449
|
-
/**
|
|
450
|
-
* Default file backend — uses `node:fs/promises` with the canonical
|
|
451
|
-
* tmp-then-rename atomic-write pattern.
|
|
452
|
-
*/
|
|
453
|
-
var FsIdentityFileBackend = class {
|
|
454
|
-
async exists(filePath) {
|
|
455
|
-
try {
|
|
456
|
-
await node_fs_promises.access(filePath);
|
|
457
|
-
return true;
|
|
458
|
-
} catch {
|
|
459
|
-
return false;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
async read(filePath) {
|
|
463
|
-
try {
|
|
464
|
-
const buf = await node_fs_promises.readFile(filePath, "utf8");
|
|
465
|
-
return migratePersistedIdentity(JSON.parse(buf));
|
|
466
|
-
} catch (err) {
|
|
467
|
-
if (isNodeError(err) && err.code === "ENOENT") return null;
|
|
468
|
-
throw err;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
async write(filePath, payload) {
|
|
472
|
-
await node_fs_promises.mkdir(node_path.dirname(filePath), { recursive: true });
|
|
473
|
-
const tmpPath = `${filePath}${TMP_SUFFIX}`;
|
|
474
|
-
const bytes = Buffer.from(JSON.stringify(payload, null, " "), "utf8");
|
|
475
|
-
await node_fs_promises.rm(tmpPath, { force: true });
|
|
476
|
-
const handle = await node_fs_promises.open(tmpPath, "w", FILE_MODE);
|
|
477
|
-
try {
|
|
478
|
-
await handle.writeFile(bytes);
|
|
479
|
-
await handle.sync();
|
|
480
|
-
} finally {
|
|
481
|
-
await handle.close();
|
|
482
|
-
}
|
|
483
|
-
await node_fs_promises.rename(tmpPath, filePath);
|
|
484
|
-
await node_fs_promises.chmod(filePath, FILE_MODE).catch(() => void 0);
|
|
485
|
-
return {
|
|
486
|
-
bytesWritten: bytes.byteLength,
|
|
487
|
-
tmpPath
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
async delete(filePath) {
|
|
491
|
-
await node_fs_promises.rm(filePath, { force: true });
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
/**
|
|
495
|
-
* Reconcile an unknown on-disk shape into the current `PersistedDeviceIdentity`.
|
|
496
|
-
*
|
|
497
|
-
* - v1 IdentityStore files (current shape) pass through unchanged.
|
|
498
|
-
* - v0 bootstrap files (`{ version: 1, claimed: false, publicKeyFingerprint: null }`)
|
|
499
|
-
* are recognized by their placeholder keys and discarded; the next
|
|
500
|
-
* enroll writes a fresh identity.
|
|
501
|
-
* - Anything else throws — refuse to silently drop user data.
|
|
502
|
-
*/
|
|
503
|
-
function migratePersistedIdentity(parsed) {
|
|
504
|
-
if (!isRecord(parsed)) throw new Error("device.json: top-level must be an object");
|
|
505
|
-
const version = parsed.version;
|
|
506
|
-
if (version === 1) {
|
|
507
|
-
if (parsed.publicId === void 0 && parsed.deviceSecret === void 0 && ("claimed" in parsed || "publicKeyFingerprint" in parsed)) return null;
|
|
508
|
-
return parsed;
|
|
509
|
-
}
|
|
510
|
-
if (typeof version === "number" && version < 1) return null;
|
|
511
|
-
throw new Error(`device.json: unsupported schema version ${String(version)}`);
|
|
512
|
-
}
|
|
513
|
-
function isRecord(value) {
|
|
514
|
-
return typeof value === "object" && value !== null;
|
|
515
|
-
}
|
|
516
|
-
function isNodeError(value) {
|
|
517
|
-
return value instanceof Error && typeof value.code === "string";
|
|
518
|
-
}
|
|
519
|
-
const LOCK_PID_FILE = "pid";
|
|
520
|
-
/**
|
|
521
|
-
* Check whether a process is still alive (best-effort, cross-platform).
|
|
522
|
-
* Returns `false` for any PID we cannot verify as alive.
|
|
523
|
-
*/
|
|
524
|
-
function isProcessAlive(pid) {
|
|
525
|
-
try {
|
|
526
|
-
process.kill(pid, 0);
|
|
527
|
-
return true;
|
|
528
|
-
} catch {
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* mkdir-based advisory file lock with stale-lock recovery.
|
|
534
|
-
*
|
|
535
|
-
* POSIX mkdir is atomic; on Windows modern filesystems (NTFS) it's also
|
|
536
|
-
* atomic at the API level. Sufficient for single-host, single-user
|
|
537
|
-
* scenarios (which is the SERVICEME threat model).
|
|
538
|
-
*
|
|
539
|
-
* Stale lock recovery: a `pid` file inside the lock directory records the
|
|
540
|
-
* owner's PID. On `EEXIST`, if the recorded PID is no longer alive, the
|
|
541
|
-
* lock directory is forcibly removed and acquisition retried immediately.
|
|
542
|
-
* This prevents permanent lockout when a process crashes without calling
|
|
543
|
-
* `release()`.
|
|
544
|
-
*/
|
|
545
|
-
var FileLock = class {
|
|
546
|
-
constructor(filePath, timeoutMs, retryMs) {
|
|
547
|
-
this.acquired = false;
|
|
548
|
-
this.dirPath = `${filePath}.lock`;
|
|
549
|
-
this.pidFilePath = node_path.join(this.dirPath, LOCK_PID_FILE);
|
|
550
|
-
this.timeoutMs = timeoutMs;
|
|
551
|
-
this.retryMs = retryMs;
|
|
552
|
-
}
|
|
553
|
-
async acquire() {
|
|
554
|
-
const start = Date.now();
|
|
555
|
-
while (true) try {
|
|
556
|
-
await node_fs_promises.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
|
|
557
|
-
await node_fs_promises.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
558
|
-
this.acquired = true;
|
|
559
|
-
return;
|
|
560
|
-
} catch (err) {
|
|
561
|
-
if (!isNodeError(err) || err.code !== "EEXIST") throw err;
|
|
562
|
-
if (await this.isStaleLock()) {
|
|
563
|
-
await node_fs_promises.rm(this.dirPath, {
|
|
564
|
-
recursive: true,
|
|
565
|
-
force: true
|
|
566
|
-
});
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
if (Date.now() - start >= this.timeoutMs) throw new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);
|
|
570
|
-
await (0, node_timers_promises.setTimeout)(this.retryMs);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
async isStaleLock() {
|
|
574
|
-
let pidStr;
|
|
575
|
-
try {
|
|
576
|
-
pidStr = await node_fs_promises.readFile(this.pidFilePath, "utf8");
|
|
577
|
-
} catch {
|
|
578
|
-
try {
|
|
579
|
-
const stat = await node_fs_promises.stat(this.dirPath);
|
|
580
|
-
return Date.now() - stat.mtimeMs > LOCK_STALE_GRACE_MS;
|
|
581
|
-
} catch {
|
|
582
|
-
return false;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
const pid = Number.parseInt(pidStr.trim(), 10);
|
|
586
|
-
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
587
|
-
return !isProcessAlive(pid);
|
|
588
|
-
}
|
|
589
|
-
async release() {
|
|
590
|
-
if (!this.acquired) return;
|
|
591
|
-
this.acquired = false;
|
|
592
|
-
await node_fs_promises.rm(this.dirPath, {
|
|
593
|
-
recursive: true,
|
|
594
|
-
force: true
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
var IdentityStore = class {
|
|
599
|
-
constructor(opts = {}) {
|
|
600
|
-
this.filePath = opts.filePath ?? require_userHome.getDeviceJsonPath();
|
|
601
|
-
this.backend = opts.backend ?? new FsIdentityFileBackend();
|
|
602
|
-
this.hooks = opts.hooks ?? {};
|
|
603
|
-
this.lockTimeoutMs = opts.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
|
|
604
|
-
this.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;
|
|
605
|
-
}
|
|
606
|
-
/** Absolute path to the underlying JSON file (test seam). */
|
|
607
|
-
getFilePath() {
|
|
608
|
-
return this.filePath;
|
|
609
|
-
}
|
|
610
|
-
/** True when the JSON file already exists on disk. */
|
|
611
|
-
async exists() {
|
|
612
|
-
return this.backend.exists(this.filePath);
|
|
613
|
-
}
|
|
614
|
-
/** Read the persisted identity; returns `null` when no identity is stored. */
|
|
615
|
-
async read() {
|
|
616
|
-
return this.backend.read(this.filePath);
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Atomically write the given identity. Concurrent writers are
|
|
620
|
-
* serialized via the file lock; the read-modify-write happens
|
|
621
|
-
* inside the lock so callers can't see a partial state.
|
|
622
|
-
*/
|
|
623
|
-
async write(next) {
|
|
624
|
-
await this.hooks.beforeWrite?.(next);
|
|
625
|
-
const lock = new FileLock(this.filePath, this.lockTimeoutMs, this.lockRetryMs);
|
|
626
|
-
await lock.acquire();
|
|
627
|
-
try {
|
|
628
|
-
const stamped = {
|
|
629
|
-
...next,
|
|
630
|
-
version: 1
|
|
631
|
-
};
|
|
632
|
-
const result = await this.backend.write(this.filePath, stamped);
|
|
633
|
-
await this.hooks.afterWrite?.(stamped);
|
|
634
|
-
return result;
|
|
635
|
-
} finally {
|
|
636
|
-
await lock.release();
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
/**
|
|
640
|
-
* Read-modify-write under the same lock. The mutator receives the
|
|
641
|
-
* current identity (or `null` on first call) and returns the
|
|
642
|
-
* replacement. Throwing inside the mutator aborts the write.
|
|
643
|
-
*/
|
|
644
|
-
async mutate(mutator) {
|
|
645
|
-
const lock = new FileLock(this.filePath, this.lockTimeoutMs, this.lockRetryMs);
|
|
646
|
-
await lock.acquire();
|
|
647
|
-
try {
|
|
648
|
-
const { next, result } = await mutator(await this.backend.read(this.filePath));
|
|
649
|
-
const stamped = {
|
|
650
|
-
...next,
|
|
651
|
-
version: 1
|
|
652
|
-
};
|
|
653
|
-
await this.hooks.beforeWrite?.(stamped);
|
|
654
|
-
await this.backend.write(this.filePath, stamped);
|
|
655
|
-
await this.hooks.afterWrite?.(stamped);
|
|
656
|
-
return {
|
|
657
|
-
result,
|
|
658
|
-
written: stamped
|
|
659
|
-
};
|
|
660
|
-
} finally {
|
|
661
|
-
await lock.release();
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
/** Wipe the persisted identity (used by `device.enroll --force`). */
|
|
665
|
-
async clear() {
|
|
666
|
-
await this.backend.delete(this.filePath);
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* Resolve the installation metadata for the current machine.
|
|
670
|
-
* Pure helper — no I/O, just `os.*` calls.
|
|
671
|
-
*/
|
|
672
|
-
resolveInstallationMetadata() {
|
|
673
|
-
return {
|
|
674
|
-
installationId: "",
|
|
675
|
-
machineId: node_os.hostname(),
|
|
676
|
-
platform: node_os.platform()
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* Ensure the parent directory exists (`~/.serviceme/`). Idempotent.
|
|
681
|
-
* Useful when the bootstrap phase5 placeholder wasn't run yet.
|
|
682
|
-
*/
|
|
683
|
-
async ensureHome() {
|
|
684
|
-
await node_fs_promises.mkdir(require_userHome.getServicemeHome(), { recursive: true });
|
|
685
|
-
await node_fs_promises.mkdir(node_path.dirname(this.filePath), { recursive: true });
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
//#endregion
|
|
689
|
-
//#region src/device/DeviceCore.ts
|
|
690
|
-
var DeviceCore = class {
|
|
691
|
-
constructor(opts = {}) {
|
|
692
|
-
this.identity = opts.identityStore ?? new IdentityStore();
|
|
693
|
-
this.enroller = new Enroller({
|
|
694
|
-
identityStore: this.identity,
|
|
695
|
-
enrollRequest: opts.enrollRequest,
|
|
696
|
-
now: opts.now
|
|
697
|
-
});
|
|
698
|
-
this.resolveInstallationId = opts.resolveInstallationId ?? deriveInstallationId;
|
|
699
|
-
}
|
|
700
|
-
/** Read-only snapshot of the device status (matches `device.status` wire shape). */
|
|
701
|
-
async status() {
|
|
702
|
-
const stored = await this.identity.read();
|
|
703
|
-
return stored ? {
|
|
704
|
-
bindingState: stored.bindingState,
|
|
705
|
-
identity: projectIdentity(stored),
|
|
706
|
-
metadata: projectMetadata(stored),
|
|
707
|
-
lastSyncAt: stored.lastSyncAt,
|
|
708
|
-
lastSyncError: stored.lastSyncError
|
|
709
|
-
} : { bindingState: "anonymous" };
|
|
710
|
-
}
|
|
711
|
-
/** Enroll (or re-enroll) the device. */
|
|
712
|
-
async enroll(opts = {}) {
|
|
713
|
-
return this.enroller.enroll(opts);
|
|
714
|
-
}
|
|
715
|
-
/** Wait for any in-flight enrollment to finish. Use before `buildSignedHeaders` so that a concurrent `syncDeviceInfo` enrollment has time to write the identity to the store. */
|
|
716
|
-
async waitForEnrollment() {
|
|
717
|
-
await this.enroller.waitForEnrollment();
|
|
718
|
-
}
|
|
719
|
-
/** True when an enrollment is currently in-flight. Used by callers to skip triggering a competing enrollment. */
|
|
720
|
-
isEnrolling() {
|
|
721
|
-
return this.enroller.isEnrolling();
|
|
722
|
-
}
|
|
723
|
-
/** Rotate the HMAC secret while keeping the previous one for the grace window. */
|
|
724
|
-
async rotateSecret(opts = {}) {
|
|
725
|
-
const result = await this.enroller.rotateSecret(opts);
|
|
726
|
-
const stored = await this.identity.read();
|
|
727
|
-
return {
|
|
728
|
-
...result,
|
|
729
|
-
gracePeriodEndsAt: stored?.previousSecretExpiresAt
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
/** Build the v2 signed header map for an outbound request. The path
|
|
733
|
-
* MUST include the query string (v2 signs it). */
|
|
734
|
-
async buildSignedHeaders(input) {
|
|
735
|
-
const stored = await this.identity.read();
|
|
736
|
-
if (!stored) return null;
|
|
737
|
-
return buildSignedHeaders({
|
|
738
|
-
method: input.method,
|
|
739
|
-
path: input.path,
|
|
740
|
-
body: input.body,
|
|
741
|
-
publicId: stored.publicId,
|
|
742
|
-
deviceSecret: stored.deviceSecret,
|
|
743
|
-
secretVersion: stored.secretVersion
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
/** Raw stored identity (CLI/extension internal use). Test seam too. */
|
|
747
|
-
async readIdentity() {
|
|
748
|
-
return this.identity.read();
|
|
749
|
-
}
|
|
750
|
-
/** Wipe the local identity (the `--force` path before re-enroll). */
|
|
751
|
-
async clear() {
|
|
752
|
-
await this.identity.clear();
|
|
753
|
-
}
|
|
754
|
-
/** Mark the device as claimed (called by the bridge after a successful claim). */
|
|
755
|
-
async markClaimed() {
|
|
756
|
-
await this.enroller.markClaimed();
|
|
757
|
-
}
|
|
758
|
-
/** Mark the device as expired (server returned an expiry response). */
|
|
759
|
-
async markExpired() {
|
|
760
|
-
await this.enroller.markExpired();
|
|
761
|
-
}
|
|
762
|
-
/** Expose the identity store (CLI uses it for direct file access in tests). */
|
|
763
|
-
getIdentityStore() {
|
|
764
|
-
return this.identity;
|
|
765
|
-
}
|
|
766
|
-
/** Expose the enroller (CLI uses it for state inspection). */
|
|
767
|
-
getEnroller() {
|
|
768
|
-
return this.enroller;
|
|
769
|
-
}
|
|
770
|
-
/** Header name constants — re-exported from `deviceAuth.ts`. */
|
|
771
|
-
getHeaderNames() {
|
|
772
|
-
return DeviceAuthHeaders;
|
|
773
|
-
}
|
|
774
|
-
/** Compute the installation id for the current machine. */
|
|
775
|
-
getInstallationId() {
|
|
776
|
-
return this.resolveInstallationId();
|
|
777
|
-
}
|
|
778
|
-
};
|
|
779
|
-
function projectIdentity(stored) {
|
|
780
|
-
return {
|
|
781
|
-
publicId: stored.publicId,
|
|
782
|
-
secretVersion: stored.secretVersion,
|
|
783
|
-
bindingState: stored.bindingState
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
function projectMetadata(stored) {
|
|
787
|
-
return {
|
|
788
|
-
installationId: stored.installationId,
|
|
789
|
-
machineId: stored.machineId,
|
|
790
|
-
platform: stored.platform,
|
|
791
|
-
hostname: stored.hostname
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
//#endregion
|
|
795
|
-
Object.defineProperty(exports, "DEVICE_JSON_SCHEMA_VERSION", {
|
|
796
|
-
enumerable: true,
|
|
797
|
-
get: function() {
|
|
798
|
-
return DEVICE_JSON_SCHEMA_VERSION;
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
Object.defineProperty(exports, "DeviceAuthHeaders", {
|
|
802
|
-
enumerable: true,
|
|
803
|
-
get: function() {
|
|
804
|
-
return DeviceAuthHeaders;
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
Object.defineProperty(exports, "DeviceCore", {
|
|
808
|
-
enumerable: true,
|
|
809
|
-
get: function() {
|
|
810
|
-
return DeviceCore;
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
Object.defineProperty(exports, "DeviceReenrollRequiresAuthError", {
|
|
814
|
-
enumerable: true,
|
|
815
|
-
get: function() {
|
|
816
|
-
return DeviceReenrollRequiresAuthError;
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
Object.defineProperty(exports, "DeviceSecretVersionMismatchError", {
|
|
820
|
-
enumerable: true,
|
|
821
|
-
get: function() {
|
|
822
|
-
return DeviceSecretVersionMismatchError;
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
Object.defineProperty(exports, "Enroller", {
|
|
826
|
-
enumerable: true,
|
|
827
|
-
get: function() {
|
|
828
|
-
return Enroller;
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
Object.defineProperty(exports, "FsIdentityFileBackend", {
|
|
832
|
-
enumerable: true,
|
|
833
|
-
get: function() {
|
|
834
|
-
return FsIdentityFileBackend;
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
Object.defineProperty(exports, "IdentityStore", {
|
|
838
|
-
enumerable: true,
|
|
839
|
-
get: function() {
|
|
840
|
-
return IdentityStore;
|
|
841
|
-
}
|
|
842
|
-
});
|
|
843
|
-
Object.defineProperty(exports, "buildSignedHeaders", {
|
|
844
|
-
enumerable: true,
|
|
845
|
-
get: function() {
|
|
846
|
-
return buildSignedHeaders;
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
Object.defineProperty(exports, "createDeviceRequestSignature", {
|
|
850
|
-
enumerable: true,
|
|
851
|
-
get: function() {
|
|
852
|
-
return createDeviceRequestSignature;
|
|
853
|
-
}
|
|
854
|
-
});
|
|
855
|
-
Object.defineProperty(exports, "deriveInstallationId", {
|
|
856
|
-
enumerable: true,
|
|
857
|
-
get: function() {
|
|
858
|
-
return deriveInstallationId;
|
|
859
|
-
}
|
|
860
|
-
});
|
|
861
|
-
Object.defineProperty(exports, "fingerprintSource", {
|
|
862
|
-
enumerable: true,
|
|
863
|
-
get: function() {
|
|
864
|
-
return fingerprintSource;
|
|
865
|
-
}
|
|
866
|
-
});
|
|
867
|
-
Object.defineProperty(exports, "randomInstallationId", {
|
|
868
|
-
enumerable: true,
|
|
869
|
-
get: function() {
|
|
870
|
-
return randomInstallationId;
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
//# sourceMappingURL=device-uFAQxlNt.js.map
|