rovloom 0.5.0 → 0.6.1
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/README.md +9 -1
- package/dist/src/admin/index.js +1 -1
- package/dist/src/{chunk-emcefrc1.js → chunk-0e9gg4sg.js} +3 -4
- package/dist/src/chunk-1xer1ent.js +102 -0
- package/dist/src/{chunk-863tk45b.js → chunk-41fny9qm.js} +5 -5
- package/dist/src/{chunk-zjzrcc59.js → chunk-59h200cr.js} +139 -7
- package/dist/src/chunk-5w5srj3b.js +93 -0
- package/dist/src/{chunk-d4ejrtjq.js → chunk-6n6b1xpy.js} +1 -1
- package/dist/src/{chunk-ympzv86c.js → chunk-6w4g9fw8.js} +16 -4
- package/dist/src/{chunk-smpevv7n.js → chunk-73aq2qkz.js} +9 -16
- package/dist/src/{chunk-0ake7e4r.js → chunk-8c9yc0ge.js} +9 -17
- package/dist/src/{chunk-57ayxqnb.js → chunk-8fy8w7ds.js} +2 -23
- package/dist/src/{chunk-64ftwe9b.js → chunk-8ykrqhxh.js} +8 -2
- package/dist/src/{chunk-2tn0r64b.js → chunk-9h9x79bx.js} +5 -5
- package/dist/src/{chunk-nyn75b8v.js → chunk-av31bx1e.js} +31 -2
- package/dist/src/{chunk-0xgpxdgc.js → chunk-bbdtsg4b.js} +7 -5
- package/dist/src/{chunk-q5pgbwb3.js → chunk-e7jgpxkf.js} +2 -2
- package/dist/src/{chunk-zj48jvfe.js → chunk-efty77sb.js} +3 -40
- package/dist/src/{chunk-abd4envz.js → chunk-enj7s8cv.js} +71 -78
- package/dist/src/{chunk-4etdq0cp.js → chunk-ftpy8xx3.js} +313 -28
- package/dist/src/{chunk-zdxww7ms.js → chunk-h3xyq7zk.js} +1 -1
- package/dist/src/{chunk-ttsrctxg.js → chunk-hxmq28pg.js} +9 -7
- package/dist/src/{chunk-r0nfthsf.js → chunk-j59ac0xs.js} +2 -2
- package/dist/src/{chunk-5s2emvww.js → chunk-j6svdqr8.js} +10 -302
- package/dist/src/{chunk-9y36gntb.js → chunk-jkzfck98.js} +10 -5
- package/dist/src/{chunk-sj24fpz3.js → chunk-m76cmkfy.js} +28 -30
- package/dist/src/{chunk-5n8csngz.js → chunk-m96mbzcw.js} +816 -305
- package/dist/src/{chunk-xhq21g24.js → chunk-m9c1pnv9.js} +3 -3
- package/dist/src/{chunk-fnakrk0a.js → chunk-n3z3jaxy.js} +39 -36
- package/dist/src/{chunk-e7x51y0t.js → chunk-ndj0ay2p.js} +19 -18
- package/dist/src/chunk-pgs9jp6t.js +320 -0
- package/dist/src/chunk-pksaqt9a.js +25 -0
- package/dist/src/{chunk-ebm3kvnq.js → chunk-pnep7yhj.js} +31 -100
- package/dist/src/chunk-qn5xae7g.js +66 -0
- package/dist/src/{chunk-0pewfj89.js → chunk-qp8tgms2.js} +1 -1
- package/dist/src/{chunk-7k9g0x5e.js → chunk-rmgw8h27.js} +10 -8
- package/dist/src/{chunk-gbsbwssz.js → chunk-sf4ztxba.js} +3 -3
- package/dist/src/{chunk-7q96e161.js → chunk-shez7hkc.js} +6 -12
- package/dist/src/chunk-sqz614bh.js +30 -0
- package/dist/src/{chunk-09f9q26d.js → chunk-vdwzb63d.js} +2 -2
- package/dist/src/chunk-xtvgm6kf.js +48 -0
- package/dist/src/chunk-y8zm75jt.js +157 -0
- package/dist/src/chunk-yasqw2vj.js +756 -0
- package/dist/src/{chunk-ybte82d3.js → chunk-z0qmfqvd.js} +4 -3
- package/dist/src/chunk-zm705snh.js +53 -0
- package/dist/src/cli/index.js +39 -30
- package/dist/src/cli/main.js +14 -9
- package/dist/src/connector/herdr/installation.js +4 -4
- package/dist/src/connector/herdr/provider.js +9 -9
- package/dist/src/connector/herdr/runtime.js +7 -7
- package/dist/src/connector/luvus/index.js +7 -6
- package/dist/src/mcp/index.js +16 -14
- package/dist/src/runtime/index.js +16 -15
- package/dist/src/service/index.js +3 -4
- package/dist/src/store.js +4 -4
- package/dist/src/transport/index.js +9 -9
- package/dist/src/webui/index.js +975 -834
- package/package.json +1 -1
- package/dist/src/chunk-5j0gj6w2.js +0 -7
- package/dist/src/chunk-9yg6x3gt.js +0 -24
- package/dist/src/chunk-erdq0rn8.js +0 -1300
- package/dist/src/chunk-n073xnr1.js +0 -1117
- package/dist/src/chunk-rwpynx2j.js +0 -51
- package/dist/src/chunk-vwcntax3.js +0 -228
|
@@ -1,1117 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
parseNativeJson2
|
|
4
|
-
} from "./chunk-7crhmqn2.js";
|
|
5
|
-
import {
|
|
6
|
-
object2,
|
|
7
|
-
fields2,
|
|
8
|
-
id2,
|
|
9
|
-
sha2,
|
|
10
|
-
boundedJson2,
|
|
11
|
-
hash2
|
|
12
|
-
} from "./chunk-qt9m2kg2.js";
|
|
13
|
-
import {
|
|
14
|
-
HERDR_WORKFLOW_METHODS
|
|
15
|
-
} from "./chunk-9tt13dm0.js";
|
|
16
|
-
import {
|
|
17
|
-
LUVUS_OPERATION_METHODS2
|
|
18
|
-
} from "./chunk-zj48jvfe.js";
|
|
19
|
-
import {
|
|
20
|
-
compileHerdrSchemas2
|
|
21
|
-
} from "./chunk-5h82060k.js";
|
|
22
|
-
import {
|
|
23
|
-
createHerdrCodec2
|
|
24
|
-
} from "./chunk-zb641qa3.js";
|
|
25
|
-
import {
|
|
26
|
-
parseHerdrProviderRefConfig2,
|
|
27
|
-
createHerdrGatewayResources2
|
|
28
|
-
} from "./chunk-2tn0r64b.js";
|
|
29
|
-
import {
|
|
30
|
-
checkedRemoteLuvusConfig,
|
|
31
|
-
createPinnedRemoteLuvus
|
|
32
|
-
} from "./chunk-5s2emvww.js";
|
|
33
|
-
import {
|
|
34
|
-
writeAdminFiles2,
|
|
35
|
-
managementSourceExists2
|
|
36
|
-
} from "./chunk-fnakrk0a.js";
|
|
37
|
-
import {
|
|
38
|
-
setupFileStamp,
|
|
39
|
-
writeSetupFile,
|
|
40
|
-
readSetupJson,
|
|
41
|
-
writeSetupJson,
|
|
42
|
-
REGISTRY_MAX_BYTES,
|
|
43
|
-
parseRegistry,
|
|
44
|
-
isAdminPlaceholder
|
|
45
|
-
} from "./chunk-ebm3kvnq.js";
|
|
46
|
-
import {
|
|
47
|
-
readRuntimeConfig2
|
|
48
|
-
} from "./chunk-bspzg8er.js";
|
|
49
|
-
import {
|
|
50
|
-
runtimeReference,
|
|
51
|
-
readRuntimeFile,
|
|
52
|
-
readRuntimeJson
|
|
53
|
-
} from "./chunk-z7kkfrgz.js";
|
|
54
|
-
import {
|
|
55
|
-
luvusMode,
|
|
56
|
-
CliError2
|
|
57
|
-
} from "./chunk-hpzr94hm.js";
|
|
58
|
-
import {
|
|
59
|
-
canonicalLoopbackHost
|
|
60
|
-
} from "./chunk-6n9s7nfe.js";
|
|
61
|
-
import {
|
|
62
|
-
PairingAdmin2,
|
|
63
|
-
createPairingDelivery2
|
|
64
|
-
} from "./chunk-vwcntax3.js";
|
|
65
|
-
|
|
66
|
-
// src/cli/pairing-http.ts
|
|
67
|
-
import { createHash, timingSafeEqual } from "crypto";
|
|
68
|
-
|
|
69
|
-
// src/cli/setup-policy.ts
|
|
70
|
-
function setupSessions(input) {
|
|
71
|
-
if (input === undefined) {
|
|
72
|
-
return Object.freeze({});
|
|
73
|
-
}
|
|
74
|
-
const row = input;
|
|
75
|
-
if (row === null || typeof row !== "object" || Array.isArray(row) || Object.keys(row).some((key) => key !== "luvus") || row.luvus !== undefined && row.luvus !== "default" && row.luvus !== "rovloom") {
|
|
76
|
-
throw new CliError2("configuration_required");
|
|
77
|
-
}
|
|
78
|
-
return Object.freeze(row.luvus === undefined ? {} : { luvus: row.luvus });
|
|
79
|
-
}
|
|
80
|
-
function setupTarget(machine, kind = "luvus", sessions = {}) {
|
|
81
|
-
return {
|
|
82
|
-
machine_id: machine,
|
|
83
|
-
provider_id: kind,
|
|
84
|
-
profile: "session",
|
|
85
|
-
session: kind === "luvus" ? sessions.luvus ?? "rovloom" : "default"
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function setupProviderKinds(input) {
|
|
89
|
-
if (!Array.isArray(input) || input.some((kind) => kind !== "luvus" && kind !== "herdr") || !["luvus", "herdr", "luvus,herdr"].includes(input.join(","))) {
|
|
90
|
-
throw new CliError2("configuration_required");
|
|
91
|
-
}
|
|
92
|
-
return Object.freeze([...input]);
|
|
93
|
-
}
|
|
94
|
-
function selectedSetupPolicies(plan, machine, kinds, sessions = {}) {
|
|
95
|
-
const grants = setupProviderKinds(kinds).map((kind) => ({
|
|
96
|
-
target: setupTarget(machine, kind, sessions),
|
|
97
|
-
methods: [...kind === "luvus" ? LUVUS_OPERATION_METHODS2 : HERDR_WORKFLOW_METHODS],
|
|
98
|
-
actions: ["dispatch", "read", "cancel"]
|
|
99
|
-
}));
|
|
100
|
-
return {
|
|
101
|
-
principal_id: plan.actor,
|
|
102
|
-
policies: {
|
|
103
|
-
caller: { revision: "setup_caller_" + (plan.lineage ?? plan.attempt_id), grants },
|
|
104
|
-
local: { revision: "setup_local_" + (plan.lineage ?? plan.attempt_id), grants: structuredClone(grants) }
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function setupReference(attempt, kind = "luvus") {
|
|
109
|
-
return kind + "_" + attempt;
|
|
110
|
-
}
|
|
111
|
-
function setupSessionsFor(config) {
|
|
112
|
-
return Object.freeze({ luvus: luvusMode(config) === "attach" ? "default" : "rovloom" });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// src/cli/pairing-http.ts
|
|
116
|
-
function setupReply(value, status = 200) {
|
|
117
|
-
return new Response(JSON.stringify(value), {
|
|
118
|
-
status,
|
|
119
|
-
headers: {
|
|
120
|
-
"content-type": "application/json",
|
|
121
|
-
"cache-control": "no-store",
|
|
122
|
-
"x-content-type-options": "nosniff"
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
async function requestJson(request, maximum, stopped) {
|
|
127
|
-
const signal = AbortSignal.any([request.signal, stopped, AbortSignal.timeout(5000)]);
|
|
128
|
-
const reader = request.body?.getReader();
|
|
129
|
-
if (!reader) {
|
|
130
|
-
throw new Error("setup_request_invalid");
|
|
131
|
-
}
|
|
132
|
-
const chunks = [];
|
|
133
|
-
let bytes = 0;
|
|
134
|
-
try {
|
|
135
|
-
while (true) {
|
|
136
|
-
let abort;
|
|
137
|
-
const cancelled = new Promise((_resolve, reject) => {
|
|
138
|
-
abort = () => reject(new Error("setup_request_closed"));
|
|
139
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
140
|
-
});
|
|
141
|
-
let result;
|
|
142
|
-
try {
|
|
143
|
-
if (signal.aborted) {
|
|
144
|
-
throw new Error("setup_request_closed");
|
|
145
|
-
}
|
|
146
|
-
result = await Promise.race([reader.read(), cancelled]);
|
|
147
|
-
} finally {
|
|
148
|
-
signal.removeEventListener("abort", abort);
|
|
149
|
-
}
|
|
150
|
-
if (result.done) {
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
bytes += result.value.length;
|
|
154
|
-
if (bytes > maximum || signal.aborted) {
|
|
155
|
-
throw new Error("setup_request_invalid");
|
|
156
|
-
}
|
|
157
|
-
chunks.push(result.value);
|
|
158
|
-
}
|
|
159
|
-
const parsed = parseNativeJson2(Buffer.concat(chunks, bytes), { object_bytes: maximum });
|
|
160
|
-
if (!parsed.ok) {
|
|
161
|
-
throw new Error("setup_request_invalid");
|
|
162
|
-
}
|
|
163
|
-
return object2(parsed.value);
|
|
164
|
-
} finally {
|
|
165
|
-
reader.cancel().catch(() => {});
|
|
166
|
-
reader.releaseLock();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function device(value) {
|
|
170
|
-
const input = object2(value);
|
|
171
|
-
fields2(input, ["machine_id", "recovery_generation", "credential"]);
|
|
172
|
-
id2(input.machine_id);
|
|
173
|
-
id2(input.recovery_generation);
|
|
174
|
-
if (typeof input.credential !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(input.credential)) {
|
|
175
|
-
throw new Error("setup_request_invalid");
|
|
176
|
-
}
|
|
177
|
-
return input;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
class GatewayPairingHttp {
|
|
181
|
-
state;
|
|
182
|
-
approval;
|
|
183
|
-
native;
|
|
184
|
-
repaired;
|
|
185
|
-
#exchanges = new Map;
|
|
186
|
-
#requests = new Set;
|
|
187
|
-
#stop = new AbortController;
|
|
188
|
-
#closed = false;
|
|
189
|
-
constructor(state, approval, native, repaired = () => {}) {
|
|
190
|
-
this.state = state;
|
|
191
|
-
this.approval = approval;
|
|
192
|
-
this.native = native;
|
|
193
|
-
this.repaired = repaired;
|
|
194
|
-
}
|
|
195
|
-
async authenticated(credential, run) {
|
|
196
|
-
const host = this.state.lifecycle.identityHost;
|
|
197
|
-
const active = host.authenticate(credential.machine_id, credential.recovery_generation, credential.credential);
|
|
198
|
-
try {
|
|
199
|
-
host.assert(active.identity, active.lease);
|
|
200
|
-
const value = await run();
|
|
201
|
-
host.assert(active.identity, active.lease);
|
|
202
|
-
return value;
|
|
203
|
-
} finally {
|
|
204
|
-
host.disconnect(active.identity);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
handle(request) {
|
|
208
|
-
const url = new URL(request.url);
|
|
209
|
-
if (!["/pairing/redeem", "/pairing/ack", "/pairing/status", "/pairing/native"].includes(url.pathname)) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (this.#closed || request.method !== "POST" || request.headers.has("origin") || url.search || url.hash || url.username || url.password || request.headers.get("content-type")?.split(";")[0]?.trim() !== "application/json") {
|
|
213
|
-
return Promise.resolve(setupReply({ error: "setup_request_refused" }, 403));
|
|
214
|
-
}
|
|
215
|
-
if (this.#requests.size >= 16) {
|
|
216
|
-
return Promise.resolve(setupReply({ error: "setup_busy" }, 503));
|
|
217
|
-
}
|
|
218
|
-
const task = this.respond(request, url.pathname);
|
|
219
|
-
this.#requests.add(task);
|
|
220
|
-
task.then(() => this.#requests.delete(task), () => this.#requests.delete(task));
|
|
221
|
-
return task;
|
|
222
|
-
}
|
|
223
|
-
async respond(request, path) {
|
|
224
|
-
try {
|
|
225
|
-
const input = await requestJson(request, path === "/pairing/native" ? 1048576 : 16384, this.#stop.signal);
|
|
226
|
-
if (this.#closed) {
|
|
227
|
-
throw new Error("setup_closed");
|
|
228
|
-
}
|
|
229
|
-
if (path === "/pairing/redeem") {
|
|
230
|
-
return await this.redeem(input);
|
|
231
|
-
}
|
|
232
|
-
const credential = device(input.authentication);
|
|
233
|
-
id2(input.attempt_id);
|
|
234
|
-
if (this.approval.read(input.attempt_id).phase === "ready") {
|
|
235
|
-
throw new Error("setup_closed");
|
|
236
|
-
}
|
|
237
|
-
return await this.authenticated(credential, async () => {
|
|
238
|
-
const plan = this.approval.read(input.attempt_id);
|
|
239
|
-
if (plan.machine_id !== credential.machine_id || plan.generation !== credential.recovery_generation) {
|
|
240
|
-
throw new Error("setup_target_mismatch");
|
|
241
|
-
}
|
|
242
|
-
if (path === "/pairing/ack") {
|
|
243
|
-
fields2(input, ["attempt_id", "authentication", "acknowledgement"]);
|
|
244
|
-
return await this.acknowledge(input.attempt_id, credential, input.acknowledgement);
|
|
245
|
-
}
|
|
246
|
-
if (path === "/pairing/status") {
|
|
247
|
-
fields2(input, ["attempt_id", "authentication"], ["providers", "sessions"]);
|
|
248
|
-
const providers = setupProviderKinds(input.providers ?? ["luvus"]);
|
|
249
|
-
const sessions = setupSessions(input.sessions);
|
|
250
|
-
if (plan.providers && boundedJson2(plan.providers) !== boundedJson2(providers)) {
|
|
251
|
-
throw new Error("setup_provider_selection_changed");
|
|
252
|
-
}
|
|
253
|
-
return setupReply({
|
|
254
|
-
attempt_id: plan.attempt_id,
|
|
255
|
-
machine_id: plan.machine_id,
|
|
256
|
-
recovery_generation: plan.generation,
|
|
257
|
-
phase: plan.phase,
|
|
258
|
-
...plan.lineage ? { lineage: plan.lineage } : {},
|
|
259
|
-
authorization: selectedSetupPolicies(plan, credential.machine_id, providers, sessions)
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
fields2(input, ["attempt_id", "authentication", "installation"]);
|
|
263
|
-
if (!["paired", "native_approved", "configured"].includes(plan.phase)) {
|
|
264
|
-
throw new Error("setup_not_paired");
|
|
265
|
-
}
|
|
266
|
-
return setupReply(await this.native({
|
|
267
|
-
plan,
|
|
268
|
-
credential,
|
|
269
|
-
input: input.installation,
|
|
270
|
-
signal: AbortSignal.any([request.signal, this.#stop.signal])
|
|
271
|
-
}));
|
|
272
|
-
});
|
|
273
|
-
} catch {
|
|
274
|
-
return setupReply({ error: "setup_not_completed" }, 409);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
async redeem(input) {
|
|
278
|
-
fields2(input, ["attempt_id", "machine_id", "enrollment"]);
|
|
279
|
-
id2(input.attempt_id);
|
|
280
|
-
id2(input.machine_id);
|
|
281
|
-
if (this.#exchanges.size >= 4 || this.#exchanges.has(input.attempt_id)) {
|
|
282
|
-
throw new Error("setup_exchange_unavailable");
|
|
283
|
-
}
|
|
284
|
-
const plan = this.approval.claim(input.attempt_id, input.machine_id, input.enrollment);
|
|
285
|
-
let deliver;
|
|
286
|
-
let refuse;
|
|
287
|
-
const response = new Promise((resolve, reject) => {
|
|
288
|
-
deliver = resolve;
|
|
289
|
-
refuse = reject;
|
|
290
|
-
});
|
|
291
|
-
let acknowledge;
|
|
292
|
-
let rejectAck;
|
|
293
|
-
const ack = new Promise((resolve, reject) => {
|
|
294
|
-
acknowledge = resolve;
|
|
295
|
-
rejectAck = reject;
|
|
296
|
-
});
|
|
297
|
-
ack.catch(() => {});
|
|
298
|
-
const custody = createPairingDelivery2({
|
|
299
|
-
backend: this.state.store,
|
|
300
|
-
identities: this.state.credentials,
|
|
301
|
-
authorizeChannel: (context) => {
|
|
302
|
-
if (this.#closed || context.operation !== "enroll" || context.machine_id !== plan.machine_id) {
|
|
303
|
-
throw new Error("setup_channel_refused");
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
deliver: async (credential, signal, delivery) => {
|
|
307
|
-
if (this.#closed || signal.aborted || credential.machine_id !== plan.machine_id || credential.recovery_generation !== plan.generation) {
|
|
308
|
-
throw new Error("setup_channel_refused");
|
|
309
|
-
}
|
|
310
|
-
exchange.context = delivery;
|
|
311
|
-
exchange.credential_sha256 = createHash("sha256").update(credential.credential).digest();
|
|
312
|
-
exchange.acknowledgement = acknowledge;
|
|
313
|
-
deliver(setupReply({
|
|
314
|
-
attempt_id: plan.attempt_id,
|
|
315
|
-
...delivery,
|
|
316
|
-
...credential,
|
|
317
|
-
state: "persist_and_acknowledge",
|
|
318
|
-
expires_at: plan.expires_at
|
|
319
|
-
}));
|
|
320
|
-
return await ack;
|
|
321
|
-
},
|
|
322
|
-
close: () => {
|
|
323
|
-
rejectAck(new Error("setup_delivery_closed"));
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
const task = custody.redeem({ registration_id: plan.machine_id, enrollment: input.enrollment }).then((receipt) => {
|
|
329
|
-
this.approval.finishDelivery(plan.attempt_id, true);
|
|
330
|
-
return receipt;
|
|
331
|
-
}, (error) => {
|
|
332
|
-
try {
|
|
333
|
-
this.approval.finishDelivery(plan.attempt_id, false);
|
|
334
|
-
} catch {}
|
|
335
|
-
refuse(new Error("setup_delivery_unknown"));
|
|
336
|
-
throw error;
|
|
337
|
-
}).finally(() => {
|
|
338
|
-
custody.close();
|
|
339
|
-
exchange.credential_sha256?.fill(0);
|
|
340
|
-
exchange.credential_sha256 = undefined;
|
|
341
|
-
exchange.acknowledgement = undefined;
|
|
342
|
-
});
|
|
343
|
-
const exchange = {
|
|
344
|
-
plan,
|
|
345
|
-
task,
|
|
346
|
-
close: () => custody.close()
|
|
347
|
-
};
|
|
348
|
-
this.#exchanges.set(plan.attempt_id, exchange);
|
|
349
|
-
task.catch(() => {});
|
|
350
|
-
return await response;
|
|
351
|
-
}
|
|
352
|
-
async acknowledge(attempt, credential, value) {
|
|
353
|
-
const ack = object2(value);
|
|
354
|
-
fields2(ack, ["delivery_id", "revision", "machine_id", "recovery_generation"]);
|
|
355
|
-
const exchange = this.#exchanges.get(attempt);
|
|
356
|
-
if (!exchange?.context || !exchange.acknowledgement || !exchange.credential_sha256 || ack.delivery_id !== exchange.context.delivery_id || ack.revision !== exchange.context.revision || ack.machine_id !== credential.machine_id || ack.recovery_generation !== credential.recovery_generation || !timingSafeEqual(createHash("sha256").update(credential.credential).digest(), exchange.credential_sha256)) {
|
|
357
|
-
throw new Error("setup_ack_refused");
|
|
358
|
-
}
|
|
359
|
-
exchange.acknowledgement(ack);
|
|
360
|
-
const receipt = await exchange.task;
|
|
361
|
-
if (this.approval.read(attempt).repair) {
|
|
362
|
-
this.repaired();
|
|
363
|
-
}
|
|
364
|
-
return setupReply({ ...receipt, attempt_id: attempt });
|
|
365
|
-
}
|
|
366
|
-
async close() {
|
|
367
|
-
this.#closed = true;
|
|
368
|
-
this.#stop.abort();
|
|
369
|
-
for (const exchange of this.#exchanges.values()) {
|
|
370
|
-
exchange.close();
|
|
371
|
-
}
|
|
372
|
-
await Promise.allSettled([...this.#exchanges.values()].map((value) => value.task));
|
|
373
|
-
await Promise.allSettled(this.#requests);
|
|
374
|
-
this.#exchanges.clear();
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/cli/setup-manifest.ts
|
|
379
|
-
function label(value) {
|
|
380
|
-
if (value === null) {
|
|
381
|
-
return "unavailable";
|
|
382
|
-
}
|
|
383
|
-
if (!value.length || value.length > 128 || [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) {
|
|
384
|
-
throw new CliError2("configuration_required");
|
|
385
|
-
}
|
|
386
|
-
return value;
|
|
387
|
-
}
|
|
388
|
-
async function reviewSetupManifest(input, plan, machine, directory, signal) {
|
|
389
|
-
const row = object2(input);
|
|
390
|
-
let entries;
|
|
391
|
-
if (Object.hasOwn(row, "installations")) {
|
|
392
|
-
fields2(row, ["installations"]);
|
|
393
|
-
if (!Array.isArray(row.installations)) {
|
|
394
|
-
throw new CliError2("configuration_required");
|
|
395
|
-
}
|
|
396
|
-
entries = row.installations;
|
|
397
|
-
} else {
|
|
398
|
-
fields2(row, ["reference", "value"]);
|
|
399
|
-
entries = [row];
|
|
400
|
-
}
|
|
401
|
-
const kinds = setupProviderKinds(entries.map((entry) => object2(object2(entry).value).kind));
|
|
402
|
-
if (plan.providers && boundedJson2(plan.providers) !== boundedJson2(kinds)) {
|
|
403
|
-
throw new CliError2("configuration_required");
|
|
404
|
-
}
|
|
405
|
-
const luvusIndex = kinds.indexOf("luvus");
|
|
406
|
-
const sessions = setupSessions(luvusIndex < 0 ? undefined : { luvus: object2(object2(object2(entries[luvusIndex]).value).target).session });
|
|
407
|
-
const authorization = selectedSetupPolicies(plan, machine, kinds, sessions);
|
|
408
|
-
const reviewed = [];
|
|
409
|
-
for (let index = 0;index < entries.length; index++) {
|
|
410
|
-
const kind = kinds[index];
|
|
411
|
-
const entry = object2(entries[index]);
|
|
412
|
-
fields2(entry, ["reference", "value"]);
|
|
413
|
-
const reference = setupReference(plan.attempt_id, kind);
|
|
414
|
-
const target = setupTarget(machine, kind, sessions);
|
|
415
|
-
if (entry.reference !== reference) {
|
|
416
|
-
throw new CliError2("configuration_required");
|
|
417
|
-
}
|
|
418
|
-
if (kind === "luvus") {
|
|
419
|
-
const remote = checkedRemoteLuvusConfig(reference, entry.value);
|
|
420
|
-
const expectedGrant = authorization.policies.caller.grants.find((grant) => boundedJson2(grant.target) === boundedJson2(target));
|
|
421
|
-
if (boundedJson2(remote.target) !== boundedJson2(target) || remote.binding.binding_revision !== 1 || !remote.binding.enabled || remote.peer_policy_revision !== authorization.policies.local.revision || boundedJson2(remote.raw_grant) !== boundedJson2({ principal_id: authorization.principal_id, methods: expectedGrant.methods })) {
|
|
422
|
-
throw new CliError2("configuration_required");
|
|
423
|
-
}
|
|
424
|
-
const verified = await createPinnedRemoteLuvus(remote);
|
|
425
|
-
verified.close();
|
|
426
|
-
reviewed.push({
|
|
427
|
-
kind,
|
|
428
|
-
reference,
|
|
429
|
-
target,
|
|
430
|
-
binding: remote.binding,
|
|
431
|
-
value: remote,
|
|
432
|
-
label: label(remote.contract.binary_version),
|
|
433
|
-
binary_sha256: remote.contract.binary_sha256
|
|
434
|
-
});
|
|
435
|
-
} else {
|
|
436
|
-
const remote = object2(entry.value);
|
|
437
|
-
fields2(remote, ["kind", "config", "schema_bundle"]);
|
|
438
|
-
const config = object2(remote.config);
|
|
439
|
-
fields2(config, ["version", "target", "binding_revision", "config_ref", "expected_source", "peer_policy_revision"]);
|
|
440
|
-
if (remote.kind !== "herdr" || typeof remote.schema_bundle !== "string" || Buffer.byteLength(remote.schema_bundle) > 524288) {
|
|
441
|
-
throw new CliError2("configuration_required");
|
|
442
|
-
}
|
|
443
|
-
const path = runtimeReference(directory, "herdr-schema_" + plan.attempt_id);
|
|
444
|
-
const parsed = parseHerdrProviderRefConfig2({ ...config, schema_path: path });
|
|
445
|
-
const bytes = Buffer.from(remote.schema_bundle, "utf8");
|
|
446
|
-
if (parsed.config_ref !== reference || parsed.binding_revision !== 1 || boundedJson2(parsed.target) !== boundedJson2(target) || parsed.peer_policy_revision !== authorization.policies.local.revision || hash2(bytes) !== parsed.expected_source.schema_sha256) {
|
|
447
|
-
throw new CliError2("configuration_required");
|
|
448
|
-
}
|
|
449
|
-
const compiled = await compileHerdrSchemas2(bytes, signal);
|
|
450
|
-
createHerdrCodec2(parsed.expected_source, compiled);
|
|
451
|
-
const binding = {
|
|
452
|
-
machine_id: machine,
|
|
453
|
-
provider_id: kind,
|
|
454
|
-
kind,
|
|
455
|
-
config_ref: reference,
|
|
456
|
-
binding_revision: 1,
|
|
457
|
-
enabled: true
|
|
458
|
-
};
|
|
459
|
-
reviewed.push({
|
|
460
|
-
kind,
|
|
461
|
-
reference,
|
|
462
|
-
target,
|
|
463
|
-
binding,
|
|
464
|
-
value: { kind, config: parsed },
|
|
465
|
-
label: label(parsed.expected_source.binary_version),
|
|
466
|
-
binary_sha256: parsed.expected_source.binary_sha256,
|
|
467
|
-
schema: { path, bytes }
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
return { kinds, reviewed, authorization, fingerprint: hash2(boundedJson2(input, 1048576)) };
|
|
472
|
-
}
|
|
473
|
-
async function publishSetupSchemas(entries, signal) {
|
|
474
|
-
for (const entry of entries) {
|
|
475
|
-
if (!entry.schema) {
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
const { path, bytes } = entry.schema;
|
|
479
|
-
let previous;
|
|
480
|
-
try {
|
|
481
|
-
previous = readRuntimeFile(path, 524288);
|
|
482
|
-
} catch (error) {
|
|
483
|
-
if (!(error instanceof CliError2 && error.code === "backend_unavailable")) {
|
|
484
|
-
throw error;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
if (previous === undefined) {
|
|
488
|
-
writeSetupFile(path, bytes);
|
|
489
|
-
} else if (!previous.equals(bytes)) {
|
|
490
|
-
throw new CliError2("configuration_required");
|
|
491
|
-
}
|
|
492
|
-
const value = entry.value;
|
|
493
|
-
const verified = await createHerdrGatewayResources2({ config: value.config, signal });
|
|
494
|
-
await verified.release();
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// src/cli/setup-gateway.ts
|
|
499
|
-
import { createHash as createHash3, randomBytes } from "crypto";
|
|
500
|
-
|
|
501
|
-
// src/cli/setup-approval.ts
|
|
502
|
-
import { createHash as createHash2, randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
503
|
-
var phases = new Set([
|
|
504
|
-
"registering",
|
|
505
|
-
"reserved",
|
|
506
|
-
"issued",
|
|
507
|
-
"redeeming",
|
|
508
|
-
"paired",
|
|
509
|
-
"native_approved",
|
|
510
|
-
"configured",
|
|
511
|
-
"ready",
|
|
512
|
-
"unknown"
|
|
513
|
-
]);
|
|
514
|
-
function plan(value) {
|
|
515
|
-
try {
|
|
516
|
-
const row = object2(value);
|
|
517
|
-
fields2(row, ["schema_version", "attempt_id", "actor", "uid", "generation", "name", "created_at", "expires_at", "phase"], ["machine_id", "enrollment_sha256", "native_fingerprint", "providers", "repair", "lineage"]);
|
|
518
|
-
for (const key of ["attempt_id", "actor", "generation", "name", ...row.machine_id ? ["machine_id"] : []]) {
|
|
519
|
-
id2(row[key]);
|
|
520
|
-
}
|
|
521
|
-
if (row.schema_version !== 1 || !Number.isSafeInteger(row.uid) || Number(row.uid) < 0 || !Number.isSafeInteger(row.created_at) || !Number.isSafeInteger(row.expires_at) || Number(row.expires_at) <= Number(row.created_at) || !phases.has(String(row.phase))) {
|
|
522
|
-
throw new CliError2("invalid_config");
|
|
523
|
-
}
|
|
524
|
-
if (row.enrollment_sha256 !== undefined) {
|
|
525
|
-
sha2(row.enrollment_sha256);
|
|
526
|
-
}
|
|
527
|
-
if (row.native_fingerprint !== undefined) {
|
|
528
|
-
sha2(row.native_fingerprint);
|
|
529
|
-
}
|
|
530
|
-
if (row.providers !== undefined) {
|
|
531
|
-
setupProviderKinds(row.providers);
|
|
532
|
-
}
|
|
533
|
-
if (row.repair !== undefined && (row.repair !== true || row.providers === undefined)) {
|
|
534
|
-
throw new CliError2("invalid_config");
|
|
535
|
-
}
|
|
536
|
-
if (row.lineage !== undefined) {
|
|
537
|
-
id2(row.lineage);
|
|
538
|
-
}
|
|
539
|
-
if (!["registering", "reserved"].includes(String(row.phase)) && (!row.machine_id || !row.enrollment_sha256)) {
|
|
540
|
-
throw new CliError2("invalid_config");
|
|
541
|
-
}
|
|
542
|
-
return Object.freeze({
|
|
543
|
-
...row,
|
|
544
|
-
...row.providers ? { providers: setupProviderKinds(row.providers) } : {}
|
|
545
|
-
});
|
|
546
|
-
} catch {
|
|
547
|
-
throw new CliError2("invalid_config");
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
class SetupApproval {
|
|
552
|
-
terminal;
|
|
553
|
-
directory;
|
|
554
|
-
#providers = new Map;
|
|
555
|
-
#resets = new Map;
|
|
556
|
-
#state;
|
|
557
|
-
constructor(terminal, directory) {
|
|
558
|
-
this.terminal = terminal;
|
|
559
|
-
this.directory = directory;
|
|
560
|
-
}
|
|
561
|
-
attach(state) {
|
|
562
|
-
this.terminal.assertOwner();
|
|
563
|
-
this.#state = state;
|
|
564
|
-
}
|
|
565
|
-
detach(state) {
|
|
566
|
-
if (this.#state === state) {
|
|
567
|
-
this.#state = undefined;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
state() {
|
|
571
|
-
this.terminal.assertOwner();
|
|
572
|
-
if (!this.#state) {
|
|
573
|
-
throw new CliError2("backend_unavailable", 3);
|
|
574
|
-
}
|
|
575
|
-
return this.#state;
|
|
576
|
-
}
|
|
577
|
-
path(attempt) {
|
|
578
|
-
id2(attempt);
|
|
579
|
-
return runtimeReference(this.directory, "setup_" + attempt);
|
|
580
|
-
}
|
|
581
|
-
read(attempt) {
|
|
582
|
-
const state = this.state();
|
|
583
|
-
const result = plan(readSetupJson(this.path(attempt)));
|
|
584
|
-
if (result.attempt_id !== attempt || result.actor !== this.terminal.actor || result.uid !== this.terminal.uid || result.generation !== state.store.generation()) {
|
|
585
|
-
throw new CliError2("configuration_required");
|
|
586
|
-
}
|
|
587
|
-
return result;
|
|
588
|
-
}
|
|
589
|
-
active() {
|
|
590
|
-
const raw = readSetupJson(runtimeReference(this.directory, "setup-active"));
|
|
591
|
-
if (raw === undefined) {
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
const value = object2(raw);
|
|
595
|
-
fields2(value, ["attempt_id"]);
|
|
596
|
-
id2(value.attempt_id);
|
|
597
|
-
return this.read(value.attempt_id);
|
|
598
|
-
}
|
|
599
|
-
save(value, replace) {
|
|
600
|
-
const checked = plan(value);
|
|
601
|
-
writeSetupJson(this.path(checked.attempt_id), checked, replace);
|
|
602
|
-
return checked;
|
|
603
|
-
}
|
|
604
|
-
async issue() {
|
|
605
|
-
const state = this.state();
|
|
606
|
-
const previous = this.active();
|
|
607
|
-
if (previous && previous.phase !== "ready") {
|
|
608
|
-
if (!["issued", "paired", "native_approved"].includes(previous.phase) || previous.phase === "issued" && Date.now() >= previous.expires_at) {
|
|
609
|
-
throw new CliError2("configuration_required");
|
|
610
|
-
}
|
|
611
|
-
if (!await this.terminal.confirm("Resume the existing device setup? / \u7EE7\u7EED\u539F\u8BBE\u5907\u8BBE\u7F6E\uFF1F")) {
|
|
612
|
-
throw new CliError2("configuration_required");
|
|
613
|
-
}
|
|
614
|
-
return { plan: previous };
|
|
615
|
-
}
|
|
616
|
-
if (!await this.terminal.confirm("Pair one Connector with this Gateway? / \u5141\u8BB8\u914D\u5BF9\u4E00\u4E2A Connector\uFF1F")) {
|
|
617
|
-
return { declined: true };
|
|
618
|
-
}
|
|
619
|
-
const now = Date.now();
|
|
620
|
-
const attempt = randomUUID();
|
|
621
|
-
const lineage = previous?.phase === "ready" ? previous.lineage ?? previous.attempt_id : undefined;
|
|
622
|
-
let current = this.save({
|
|
623
|
-
schema_version: 1,
|
|
624
|
-
attempt_id: attempt,
|
|
625
|
-
actor: this.terminal.actor,
|
|
626
|
-
uid: this.terminal.uid,
|
|
627
|
-
generation: state.store.generation(),
|
|
628
|
-
...lineage ? { lineage } : {},
|
|
629
|
-
name: "connector-" + attempt.slice(0, 8),
|
|
630
|
-
created_at: now,
|
|
631
|
-
expires_at: now + 300000,
|
|
632
|
-
phase: "registering"
|
|
633
|
-
}, false);
|
|
634
|
-
writeSetupJson(runtimeReference(this.directory, "setup-active"), { attempt_id: attempt }, previous !== undefined);
|
|
635
|
-
let registration = false;
|
|
636
|
-
let issuance = false;
|
|
637
|
-
const admin = new PairingAdmin2(state.store, {
|
|
638
|
-
authorize: (action, machine) => {
|
|
639
|
-
this.state();
|
|
640
|
-
if (this.#state !== state || Date.now() >= current.expires_at) {
|
|
641
|
-
throw new CliError2("configuration_required");
|
|
642
|
-
}
|
|
643
|
-
if (action === "register" && machine === undefined && !registration && current.phase === "registering") {
|
|
644
|
-
registration = true;
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
if (action === "enroll" && machine === current.machine_id && !issuance && current.phase === "reserved") {
|
|
648
|
-
issuance = true;
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
throw new CliError2("configuration_required");
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
const machine = admin.register(current.name);
|
|
655
|
-
current = this.save({ ...current, phase: "reserved", machine_id: machine.machine_id }, true);
|
|
656
|
-
const enrollment = admin.issue(machine.machine_id, Math.max(1, current.expires_at - Date.now()));
|
|
657
|
-
current = this.save({ ...current, phase: "issued", enrollment_sha256: createHash2("sha256").update(enrollment).digest("hex") }, true);
|
|
658
|
-
return { plan: current, code: machine.machine_id + "." + current.attempt_id + "." + enrollment };
|
|
659
|
-
}
|
|
660
|
-
async issueRepair() {
|
|
661
|
-
const state = this.state();
|
|
662
|
-
const previous = this.active();
|
|
663
|
-
const retry = previous?.repair === true && ["issued", "redeeming", "unknown"].includes(previous.phase);
|
|
664
|
-
if (!previous?.machine_id || !previous.providers || previous.phase !== "ready" && !retry) {
|
|
665
|
-
throw new CliError2("configuration_required");
|
|
666
|
-
}
|
|
667
|
-
const machine = previous.machine_id;
|
|
668
|
-
const device = state.store.machine(machine);
|
|
669
|
-
if (device.status === "revoked") {
|
|
670
|
-
throw new CliError2("configuration_required");
|
|
671
|
-
}
|
|
672
|
-
if (!await this.terminal.confirm("Re-pair the existing Connector device " + device.name + " (" + machine + ")? The credential it holds stops working now and in-flight calls become unknown. / \u91CD\u65B0\u914D\u5BF9\u5DF2\u6709\u8BBE\u5907 " + device.name + "\uFF08" + machine + "\uFF09\uFF1F\u8BE5\u8BBE\u5907\u5F53\u524D\u51ED\u636E\u7ACB\u5373\u5931\u6548\uFF0C\u8FDB\u884C\u4E2D\u7684\u8C03\u7528\u7ED3\u679C\u53D8\u4E3A\u672A\u77E5\u3002")) {
|
|
673
|
-
throw new CliError2("configuration_required");
|
|
674
|
-
}
|
|
675
|
-
for (let attempt = 0;; attempt++) {
|
|
676
|
-
const epoch = state.store.deviceEpoch(machine);
|
|
677
|
-
const generation = state.store.generation();
|
|
678
|
-
this.#resets.set(machine, {
|
|
679
|
-
confirmation: randomUUID(),
|
|
680
|
-
expires: Date.now() + 60000,
|
|
681
|
-
fence: boundedJson2({ recovery_generation: generation, connection_epoch: epoch })
|
|
682
|
-
});
|
|
683
|
-
try {
|
|
684
|
-
state.store.resetDeviceEnrollment(machine, generation, epoch);
|
|
685
|
-
state.channels.blockAndClose(machine);
|
|
686
|
-
break;
|
|
687
|
-
} catch (error) {
|
|
688
|
-
this.#resets.delete(machine);
|
|
689
|
-
if (attempt >= 2) {
|
|
690
|
-
throw error;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
const now = Date.now();
|
|
695
|
-
const attempt = randomUUID();
|
|
696
|
-
let current = this.save({
|
|
697
|
-
schema_version: 1,
|
|
698
|
-
attempt_id: attempt,
|
|
699
|
-
actor: this.terminal.actor,
|
|
700
|
-
uid: this.terminal.uid,
|
|
701
|
-
generation: state.store.generation(),
|
|
702
|
-
name: previous.name,
|
|
703
|
-
created_at: now,
|
|
704
|
-
expires_at: now + 300000,
|
|
705
|
-
phase: "reserved",
|
|
706
|
-
machine_id: machine,
|
|
707
|
-
providers: previous.providers,
|
|
708
|
-
repair: true,
|
|
709
|
-
...previous.lineage ? { lineage: previous.lineage } : {}
|
|
710
|
-
}, false);
|
|
711
|
-
let issuance = false;
|
|
712
|
-
const admin = new PairingAdmin2(state.store, {
|
|
713
|
-
authorize: (action, target) => {
|
|
714
|
-
this.state();
|
|
715
|
-
if (this.#state !== state || Date.now() >= current.expires_at || action !== "enroll" || target !== machine || issuance) {
|
|
716
|
-
throw new CliError2("configuration_required");
|
|
717
|
-
}
|
|
718
|
-
issuance = true;
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
const enrollment = admin.issue(machine, Math.max(1, current.expires_at - Date.now()));
|
|
722
|
-
current = this.save({ ...current, phase: "issued", enrollment_sha256: createHash2("sha256").update(enrollment).digest("hex") }, true);
|
|
723
|
-
writeSetupJson(runtimeReference(this.directory, "setup-active"), { attempt_id: attempt }, true);
|
|
724
|
-
return { plan: current, code: machine + "." + current.attempt_id + "." + enrollment };
|
|
725
|
-
}
|
|
726
|
-
claim(attempt, machine, enrollment) {
|
|
727
|
-
const current = this.read(attempt);
|
|
728
|
-
if (current.phase !== "issued" || current.machine_id !== machine || Date.now() >= current.expires_at || typeof enrollment !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(enrollment) || !timingSafeEqual2(createHash2("sha256").update(enrollment).digest(), Buffer.from(current.enrollment_sha256, "hex"))) {
|
|
729
|
-
throw new CliError2("configuration_required");
|
|
730
|
-
}
|
|
731
|
-
return this.save({ ...current, phase: "redeeming" }, true);
|
|
732
|
-
}
|
|
733
|
-
finishDelivery(attempt, delivered) {
|
|
734
|
-
const current = this.read(attempt);
|
|
735
|
-
if (current.phase !== "redeeming") {
|
|
736
|
-
throw new CliError2("configuration_required");
|
|
737
|
-
}
|
|
738
|
-
return this.save({ ...current, phase: delivered ? current.repair ? "configured" : "paired" : "unknown" }, true);
|
|
739
|
-
}
|
|
740
|
-
async approveNative(attempt, provider, fingerprint, description, signal = this.terminal.io.signal) {
|
|
741
|
-
const state = this.state();
|
|
742
|
-
const current = this.read(attempt);
|
|
743
|
-
const providers = setupProviderKinds(typeof provider === "string" ? [provider] : provider);
|
|
744
|
-
sha2(fingerprint);
|
|
745
|
-
if (!["paired", "native_approved"].includes(current.phase)) {
|
|
746
|
-
throw new CliError2("configuration_required");
|
|
747
|
-
}
|
|
748
|
-
if (current.phase === "native_approved" && current.native_fingerprint !== fingerprint) {
|
|
749
|
-
throw new CliError2("configuration_required");
|
|
750
|
-
}
|
|
751
|
-
if (current.providers && boundedJson2(current.providers) !== boundedJson2(providers)) {
|
|
752
|
-
throw new CliError2("configuration_required");
|
|
753
|
-
}
|
|
754
|
-
if (!await this.terminal.confirm(description, signal)) {
|
|
755
|
-
throw new CliError2("configuration_required");
|
|
756
|
-
}
|
|
757
|
-
const checked = this.read(attempt);
|
|
758
|
-
if (checked.phase !== current.phase || checked.native_fingerprint !== current.native_fingerprint || this.#state !== state) {
|
|
759
|
-
throw new CliError2("configuration_required");
|
|
760
|
-
}
|
|
761
|
-
for (const kind of providers) {
|
|
762
|
-
this.#providers.set(current.machine_id + "/" + kind, {
|
|
763
|
-
confirmation: randomUUID(),
|
|
764
|
-
expires: Date.now() + 300000,
|
|
765
|
-
generation: current.generation
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
return this.save({ ...current, phase: "native_approved", native_fingerprint: fingerprint, providers }, true);
|
|
769
|
-
}
|
|
770
|
-
authorizeManagement(request) {
|
|
771
|
-
const state = this.state();
|
|
772
|
-
if (request.action === "device_enrollment_reset") {
|
|
773
|
-
const reset = this.#resets.get(request.target);
|
|
774
|
-
if (!reset || reset.fence !== request.fence || Date.now() >= reset.expires) {
|
|
775
|
-
throw new CliError2("configuration_required");
|
|
776
|
-
}
|
|
777
|
-
this.#resets.delete(request.target);
|
|
778
|
-
return { actor: this.terminal.actor, confirmation_id: reset.confirmation };
|
|
779
|
-
}
|
|
780
|
-
const permit = this.#providers.get(request.target);
|
|
781
|
-
if (request.action !== "providers_manage" || request.fence !== "new" || !permit || permit.generation !== state.store.generation() || Date.now() >= permit.expires) {
|
|
782
|
-
throw new CliError2("configuration_required");
|
|
783
|
-
}
|
|
784
|
-
this.#providers.delete(request.target);
|
|
785
|
-
return { actor: this.terminal.actor, confirmation_id: permit.confirmation };
|
|
786
|
-
}
|
|
787
|
-
finishNative(attempt, fingerprint) {
|
|
788
|
-
const current = this.read(attempt);
|
|
789
|
-
if (current.phase !== "native_approved" || current.native_fingerprint !== fingerprint) {
|
|
790
|
-
throw new CliError2("configuration_required");
|
|
791
|
-
}
|
|
792
|
-
return this.save({ ...current, phase: "configured" }, true);
|
|
793
|
-
}
|
|
794
|
-
finishReady(attempt) {
|
|
795
|
-
const current = this.read(attempt);
|
|
796
|
-
if (current.phase !== "configured") {
|
|
797
|
-
throw new CliError2("configuration_required");
|
|
798
|
-
}
|
|
799
|
-
this.save({ ...current, phase: "ready" }, true);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// src/cli/setup-gateway.ts
|
|
804
|
-
function publish(path, value, maximum = 16384) {
|
|
805
|
-
const current = readSetupJson(path, maximum);
|
|
806
|
-
if (current === undefined) {
|
|
807
|
-
writeSetupJson(path, value, false, maximum);
|
|
808
|
-
} else if (boundedJson2(current, maximum) !== boundedJson2(value, maximum)) {
|
|
809
|
-
throw new CliError2("configuration_required");
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
function unionGrants(existing, added) {
|
|
813
|
-
const merged = [...existing];
|
|
814
|
-
for (const grant of added) {
|
|
815
|
-
const found = merged.find((entry) => boundedJson2(entry.target) === boundedJson2(grant.target));
|
|
816
|
-
if (!found) {
|
|
817
|
-
merged.push(grant);
|
|
818
|
-
} else if (boundedJson2(found) !== boundedJson2(grant)) {
|
|
819
|
-
throw new CliError2("configuration_required");
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
return merged;
|
|
823
|
-
}
|
|
824
|
-
function publishFirstCallers(path, resource, authorization, caller) {
|
|
825
|
-
const previous = readSetupJson(path, REGISTRY_MAX_BYTES);
|
|
826
|
-
const created = {
|
|
827
|
-
schema_version: 1,
|
|
828
|
-
kind: "mcp-caller-source",
|
|
829
|
-
resource,
|
|
830
|
-
local_policy: authorization.policies.local,
|
|
831
|
-
callers: [caller]
|
|
832
|
-
};
|
|
833
|
-
if (previous === undefined) {
|
|
834
|
-
parseRegistry(JSON.parse(boundedJson2(created, REGISTRY_MAX_BYTES)));
|
|
835
|
-
writeSetupFile(path, boundedJson2(created, REGISTRY_MAX_BYTES), false);
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
const registry = parseRegistry(previous, resource);
|
|
839
|
-
const kept = registry.callers.filter((entry) => isAdminPlaceholder(entry) || boundedJson2(entry, REGISTRY_MAX_BYTES) === boundedJson2(caller, REGISTRY_MAX_BYTES));
|
|
840
|
-
if (kept.length !== registry.callers.length) {
|
|
841
|
-
throw new CliError2("configuration_required");
|
|
842
|
-
}
|
|
843
|
-
const callers = [...kept];
|
|
844
|
-
if (!callers.some((entry) => boundedJson2(entry, REGISTRY_MAX_BYTES) === boundedJson2(caller, REGISTRY_MAX_BYTES))) {
|
|
845
|
-
callers.push(caller);
|
|
846
|
-
}
|
|
847
|
-
const merged = { ...created, callers };
|
|
848
|
-
parseRegistry(JSON.parse(boundedJson2(merged, REGISTRY_MAX_BYTES)), resource);
|
|
849
|
-
const stamp = setupFileStamp(path);
|
|
850
|
-
writeSetupFile(path, boundedJson2(merged, REGISTRY_MAX_BYTES), true, stamp);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
class GatewaySetup {
|
|
854
|
-
config;
|
|
855
|
-
terminal;
|
|
856
|
-
options;
|
|
857
|
-
approval;
|
|
858
|
-
#begun = false;
|
|
859
|
-
#active;
|
|
860
|
-
#timer;
|
|
861
|
-
#reload;
|
|
862
|
-
reconfigure = new Promise(() => {});
|
|
863
|
-
constructor(config, terminal, options = {}) {
|
|
864
|
-
this.config = config;
|
|
865
|
-
this.terminal = terminal;
|
|
866
|
-
this.options = options;
|
|
867
|
-
this.approval = new SetupApproval(terminal, config.state_dir);
|
|
868
|
-
}
|
|
869
|
-
attach(state) {
|
|
870
|
-
this.approval.attach(state);
|
|
871
|
-
this.reconfigure = new Promise((resolve) => {
|
|
872
|
-
this.#reload = resolve;
|
|
873
|
-
});
|
|
874
|
-
const http = new GatewayPairingHttp(state, this.approval, (request) => this.native(state, request), () => setImmediate(() => this.#reload?.()));
|
|
875
|
-
this.#active = http;
|
|
876
|
-
return http;
|
|
877
|
-
}
|
|
878
|
-
async listening(gateway, mcpUrl) {
|
|
879
|
-
if (this.options.repair && !this.#begun) {
|
|
880
|
-
this.#begun = true;
|
|
881
|
-
const active = this.approval.active();
|
|
882
|
-
if (active?.repair && active.phase === "configured") {
|
|
883
|
-
this.terminal.show(`Resuming the device re-pairing; waiting for the Connector to reconnect. / \u7EE7\u7EED\u539F\u91CD\u65B0\u914D\u5BF9\uFF0C\u7B49\u5F85 Connector \u91CD\u8FDE\u3002
|
|
884
|
-
`);
|
|
885
|
-
} else {
|
|
886
|
-
const issued = await this.approval.issueRepair();
|
|
887
|
-
this.terminal.show("Gateway MCP: " + mcpUrl + `
|
|
888
|
-
Connector endpoint: ` + gateway.transport.url.replace(/\/connector$/, "") + `
|
|
889
|
-
One-time re-pairing code for the existing device (5 minutes; keep private) / \u5DF2\u6709\u8BBE\u5907\u7684\u4E00\u6B21\u6027\u91CD\u65B0\u914D\u5BF9\u7801\uFF085\u5206\u949F\uFF0C\u8BF7\u4FDD\u5BC6\uFF09:
|
|
890
|
-
` + issued.code + `
|
|
891
|
-
Stop that Connector, then run rovloom connector --repair on its machine and enter the code. The old credential no longer works.
|
|
892
|
-
\u5728\u8BE5\u8BBE\u5907\u4E0A\u505C\u6B62 Connector\uFF0C\u8FD0\u884C rovloom connector --repair \u5E76\u8F93\u5165\u6B64\u7801\uFF1B\u65E7\u51ED\u636E\u5DF2\u5931\u6548\u3002
|
|
893
|
-
`);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
const current = this.approval.active();
|
|
897
|
-
if (!this.#begun && (current?.phase === "ready" ? this.options.addDevice === true : current?.phase !== "configured")) {
|
|
898
|
-
this.#begun = true;
|
|
899
|
-
this.terminal.show("Gateway MCP: " + mcpUrl + `
|
|
900
|
-
Connector endpoint: ` + gateway.transport.url.replace(/\/connector$/, "") + `
|
|
901
|
-
`);
|
|
902
|
-
const issued = await this.approval.issue();
|
|
903
|
-
if (issued.declined) {
|
|
904
|
-
const page = this.config.management_enabled ? "http://127.0.0.1:" + this.config.management_port + "/" : "";
|
|
905
|
-
this.terminal.show(`Gateway stays unpaired and running; no enrollment or credential is issued. / Gateway \u4FDD\u6301\u672A\u914D\u5BF9\u5E76\u7EE7\u7EED\u8FD0\u884C\uFF0C\u4E0D\u7B7E\u53D1\u51ED\u636E\u3002
|
|
906
|
-
` + (page ? "Finish first pairing from the management page: create the administrator with `rovloom admin create`, sign in at " + page + ", create a pairing application, then run `rovloom connector --claim` on the device. / \u5728\u7BA1\u7406\u9875\u5B8C\u6210\u9996\u6B21\u914D\u5BF9\uFF1A\u5148\u7528 `rovloom admin create` \u521B\u5EFA\u7BA1\u7406\u5458\uFF0C\u767B\u5F55 " + page + " \u65B0\u5EFA\u914D\u5BF9\u7533\u8BF7\uFF0C\u518D\u5728\u8BBE\u5907\u4E0A\u8FD0\u884C `rovloom connector --claim`\u3002\n" : `Management is disabled; enable it or rerun to pair from this terminal. / \u7BA1\u7406\u9875\u672A\u542F\u7528\uFF1B\u8BF7\u542F\u7528\u540E\u91CD\u8BD5\u6216\u5728\u672C\u7EC8\u7AEF\u5B8C\u6210\u914D\u5BF9\u3002
|
|
907
|
-
`));
|
|
908
|
-
this.terminal.finishQuestions();
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
if (issued.code) {
|
|
912
|
-
this.terminal.show(`One-time pairing code (5 minutes; keep private) / \u4E00\u6B21\u6027\u914D\u5BF9\u7801\uFF085\u5206\u949F\uFF0C\u8BF7\u4FDD\u5BC6\uFF09:
|
|
913
|
-
` + issued.code + `
|
|
914
|
-
Run rovloom connector in another local terminal and enter the Gateway address and code.
|
|
915
|
-
\u5728 Connector \u7684\u672C\u5730\u7EC8\u7AEF\u8F93\u5165 Gateway \u5730\u5740\u4E0E\u8BE5\u7801\uFF1B\u968F\u540E\u56DE\u5230\u6B64\u7EC8\u7AEF\u6279\u51C6\u5B9E\u6D4B\u5B89\u88C5\u3002
|
|
916
|
-
`);
|
|
917
|
-
} else {
|
|
918
|
-
this.terminal.show(`Resuming the existing setup; no new enrollment or credential is issued. / \u7EE7\u7EED\u539F\u8BBE\u7F6E\uFF0C\u4E0D\u91CD\u65B0\u7B7E\u53D1\u3002
|
|
919
|
-
`);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
const plan = this.approval.active();
|
|
923
|
-
if (plan?.phase === "ready") {
|
|
924
|
-
this.terminal.finishQuestions();
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
if (plan?.phase === "configured") {
|
|
928
|
-
this.terminal.finishQuestions();
|
|
929
|
-
let previous = "";
|
|
930
|
-
this.#timer = setInterval(() => {
|
|
931
|
-
try {
|
|
932
|
-
const device = gateway.transport.status().devices.find((entry) => entry.machine_id === plan.machine_id);
|
|
933
|
-
const current = device?.authenticated ? "connector_authenticated native_pending" : "gateway_waiting_for_connector";
|
|
934
|
-
if (current !== previous) {
|
|
935
|
-
previous = current;
|
|
936
|
-
this.terminal.show(current + `
|
|
937
|
-
`);
|
|
938
|
-
}
|
|
939
|
-
if (device?.authenticated && device.ready && (plan.providers ?? ["luvus"]).every((kind) => device.providers.some((entry) => entry.provider_id === kind && entry.ready))) {
|
|
940
|
-
this.approval.finishReady(plan.attempt_id);
|
|
941
|
-
this.terminal.show(`connector_ready / Connector \u5DF2\u901A\u8FC7\u771F\u5B9E\u8BA4\u8BC1\u4E14\u5168\u90E8\u6240\u9009\u539F\u751F provider \u5C31\u7EEA\u3002
|
|
942
|
-
`);
|
|
943
|
-
clearInterval(this.#timer);
|
|
944
|
-
this.#timer = undefined;
|
|
945
|
-
}
|
|
946
|
-
} catch {
|
|
947
|
-
this.terminal.io.stderr.write(`setup_observation_unavailable
|
|
948
|
-
`);
|
|
949
|
-
clearInterval(this.#timer);
|
|
950
|
-
this.#timer = undefined;
|
|
951
|
-
}
|
|
952
|
-
}, 250);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
async native(state, request) {
|
|
956
|
-
const { reviewed, kinds, authorization, fingerprint } = await reviewSetupManifest(request.input, request.plan, request.credential.machine_id, this.config.state_dir, request.signal);
|
|
957
|
-
if (request.plan.phase === "configured") {
|
|
958
|
-
if (request.plan.native_fingerprint !== fingerprint) {
|
|
959
|
-
throw new CliError2("configuration_required");
|
|
960
|
-
}
|
|
961
|
-
return { state: "configured", fingerprint };
|
|
962
|
-
}
|
|
963
|
-
if (request.signal.aborted) {
|
|
964
|
-
throw new CliError2("configuration_required");
|
|
965
|
-
}
|
|
966
|
-
const providers = readRuntimeConfig2(this.config).providers;
|
|
967
|
-
const additional = request.plan.lineage !== undefined;
|
|
968
|
-
if (providers.some((entry) => {
|
|
969
|
-
const expected = reviewed.find((value) => value.reference === entry.reference);
|
|
970
|
-
return expected ? boundedJson2(entry.value, 1048576) !== boundedJson2(expected.value, 1048576) : !additional;
|
|
971
|
-
})) {
|
|
972
|
-
throw new CliError2("configuration_required");
|
|
973
|
-
}
|
|
974
|
-
await this.approval.approveNative(request.plan.attempt_id, kinds, fingerprint, "Allow this paired device's measured native installations (" + reviewed.map((entry) => entry.kind + " " + entry.label + ", Target " + boundedJson2(entry.target) + ", binary SHA-256 " + entry.binary_sha256).join("; ") + `) for reviewed workspace/Agent operations and one local MCP caller? Terminal execution is controlled by the Connector machine owner's local configuration. / \u6279\u51C6\u8FD9\u4E9B\u5B9E\u6D4B\u539F\u751F\u6765\u6E90\u3001\u5404\u81EA\u7A97\u53E3/Agent \u64CD\u4F5C\u53CA\u4E00\u4E2A\u672C\u673A MCP caller\uFF08\u7EC8\u7AEF\u6267\u884C\u7531 Connector \u672C\u673A\u6240\u6709\u8005\u914D\u7F6E\u51B3\u5B9A\uFF09\uFF1F`, request.signal);
|
|
975
|
-
this.terminal.assertOwner();
|
|
976
|
-
if (request.signal.aborted) {
|
|
977
|
-
throw new CliError2("configuration_required");
|
|
978
|
-
}
|
|
979
|
-
await publishSetupSchemas(reviewed, request.signal);
|
|
980
|
-
const hostname = canonicalLoopbackHost(this.config.mcp_hostname);
|
|
981
|
-
const resource = new URL("http://" + hostname + ":" + this.config.mcp_port + "/mcp").href;
|
|
982
|
-
const clientPath = runtimeReference(this.config.state_dir, "mcp-client");
|
|
983
|
-
const original = readSetupJson(clientPath);
|
|
984
|
-
if (additional && original === undefined) {
|
|
985
|
-
throw new CliError2("configuration_required");
|
|
986
|
-
}
|
|
987
|
-
const client = original === undefined ? {
|
|
988
|
-
schema_version: 1,
|
|
989
|
-
kind: "mcp-client-credential",
|
|
990
|
-
attempt_id: request.plan.attempt_id,
|
|
991
|
-
resource,
|
|
992
|
-
principal_id: authorization.principal_id,
|
|
993
|
-
token: randomBytes(32).toString("base64url"),
|
|
994
|
-
issued_at: Date.now(),
|
|
995
|
-
expires_at: Date.now() + 30 * 86400000
|
|
996
|
-
} : object2(original);
|
|
997
|
-
fields2(client, [
|
|
998
|
-
"schema_version",
|
|
999
|
-
"kind",
|
|
1000
|
-
"attempt_id",
|
|
1001
|
-
"resource",
|
|
1002
|
-
"principal_id",
|
|
1003
|
-
"token",
|
|
1004
|
-
"issued_at",
|
|
1005
|
-
"expires_at"
|
|
1006
|
-
]);
|
|
1007
|
-
if (client.schema_version !== 1 || client.kind !== "mcp-client-credential" || client.attempt_id !== (request.plan.lineage ?? request.plan.attempt_id) || client.resource !== resource || client.principal_id !== authorization.principal_id || typeof client.token !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(client.token) || !Number.isSafeInteger(client.issued_at) || !Number.isSafeInteger(client.expires_at) || Number(client.expires_at) <= Date.now()) {
|
|
1008
|
-
throw new CliError2("configuration_required");
|
|
1009
|
-
}
|
|
1010
|
-
publish(clientPath, client);
|
|
1011
|
-
const callersPath = runtimeReference(this.config.state_dir, "mcp-callers");
|
|
1012
|
-
const providersPath = runtimeReference(this.config.state_dir, "providers");
|
|
1013
|
-
const reviewedEntries = reviewed.map((entry) => ({
|
|
1014
|
-
reference: entry.reference,
|
|
1015
|
-
value: entry.value
|
|
1016
|
-
}));
|
|
1017
|
-
if (!additional) {
|
|
1018
|
-
publishFirstCallers(callersPath, resource, authorization, {
|
|
1019
|
-
principal: { id: authorization.principal_id, policy: authorization.policies.caller },
|
|
1020
|
-
token_sha256: createHash3("sha256").update(client.token).digest("hex"),
|
|
1021
|
-
issued_at: client.issued_at,
|
|
1022
|
-
expires_at: client.expires_at
|
|
1023
|
-
});
|
|
1024
|
-
publish(providersPath, { schema_version: 1, role: "gateway", providers: reviewedEntries }, 1048576);
|
|
1025
|
-
} else {
|
|
1026
|
-
mergeSetupCallers(callersPath, resource, authorization);
|
|
1027
|
-
mergeSetupProviders(providersPath, reviewedEntries);
|
|
1028
|
-
}
|
|
1029
|
-
for (const entry of reviewed) {
|
|
1030
|
-
const binding = state.store.provider(entry.target.machine_id, entry.target.provider_id);
|
|
1031
|
-
if (!binding) {
|
|
1032
|
-
state.store.defineProvider(entry.binding);
|
|
1033
|
-
} else if (boundedJson2(binding) !== boundedJson2(entry.binding)) {
|
|
1034
|
-
throw new CliError2("configuration_required");
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
this.approval.finishNative(request.plan.attempt_id, fingerprint);
|
|
1038
|
-
let adminNote = "";
|
|
1039
|
-
if (this.config.management_enabled && !managementSourceExists2(this.config)) {
|
|
1040
|
-
const page = "http://127.0.0.1:" + this.config.management_port + "/";
|
|
1041
|
-
try {
|
|
1042
|
-
const admin = writeAdminFiles2(this.config, "admin", Date.now());
|
|
1043
|
-
adminNote = `
|
|
1044
|
-
Management page / \u7BA1\u7406\u9875: ` + page + `
|
|
1045
|
-
Management user / \u7BA1\u7406\u5458: admin` + `
|
|
1046
|
-
Management password (private) / \u7BA1\u7406\u5458\u5BC6\u7801: ` + admin.secret + `
|
|
1047
|
-
(Shown once; run rovloom admin create to re-issue. / \u53EA\u663E\u793A\u4E00\u6B21\uFF1B\u4E22\u5931\u540E\u8FD0\u884C rovloom admin create \u91CD\u65B0\u7B7E\u53D1\u3002)`;
|
|
1048
|
-
} catch {
|
|
1049
|
-
adminNote = `
|
|
1050
|
-
Management administrator was not created; run rovloom admin create. / \u672A\u80FD\u521B\u5EFA\u7BA1\u7406\u9875\u7BA1\u7406\u5458\uFF0C\u8BF7\u7A0D\u540E\u8FD0\u884C rovloom admin create\u3002`;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
this.terminal.show(`Native configuration saved; Gateway will reopen its real providers. / \u539F\u751F\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0CGateway \u5C06\u91CD\u5F00\u5B9E\u9645 provider\u3002
|
|
1054
|
-
MCP credential file / MCP \u51ED\u636E\u6587\u4EF6: ` + clientPath + `
|
|
1055
|
-
MCP URL: ` + (this.config.mcp_public_url ?? resource) + `
|
|
1056
|
-
Targets: ` + boundedJson2(reviewed.map((entry) => entry.target)) + `
|
|
1057
|
-
Binding revision: 1` + (additional ? `
|
|
1058
|
-
Existing MCP Bearer now also covers this device (not shown again; tokens made with rovloom token create --like need re-creating). / \u73B0\u6709 MCP Bearer \u5DF2\u8986\u76D6\u6B64\u8BBE\u5907\uFF08\u4E0D\u518D\u663E\u793A\uFF1B\u7528 token create --like \u514B\u9686\u7684\u4EE4\u724C\u9700\u8981\u91CD\u65B0\u514B\u9686\uFF09\u3002` : `
|
|
1059
|
-
Bearer (private): ` + client.token) + adminNote + `
|
|
1060
|
-
`);
|
|
1061
|
-
setImmediate(() => this.#reload?.());
|
|
1062
|
-
return { state: "configured", fingerprint };
|
|
1063
|
-
}
|
|
1064
|
-
async detach(state) {
|
|
1065
|
-
if (this.#timer) {
|
|
1066
|
-
clearInterval(this.#timer);
|
|
1067
|
-
this.#timer = undefined;
|
|
1068
|
-
}
|
|
1069
|
-
const http = this.#active;
|
|
1070
|
-
this.#active = undefined;
|
|
1071
|
-
await http?.close();
|
|
1072
|
-
this.approval.detach(state);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
function mergeSetupCallers(path, resource, authorization) {
|
|
1076
|
-
const stamp = setupFileStamp(path);
|
|
1077
|
-
const registry = structuredClone(parseRegistry(readRuntimeJson(path, REGISTRY_MAX_BYTES), resource));
|
|
1078
|
-
if (registry.local_policy.revision !== authorization.policies.local.revision) {
|
|
1079
|
-
throw new CliError2("configuration_required");
|
|
1080
|
-
}
|
|
1081
|
-
registry.local_policy.grants = unionGrants(registry.local_policy.grants, authorization.policies.local.grants);
|
|
1082
|
-
for (const entry of registry.callers) {
|
|
1083
|
-
if (entry.principal.id === authorization.principal_id) {
|
|
1084
|
-
if (entry.principal.policy.revision !== authorization.policies.caller.revision) {
|
|
1085
|
-
throw new CliError2("configuration_required");
|
|
1086
|
-
}
|
|
1087
|
-
entry.principal.policy.grants = unionGrants(entry.principal.policy.grants, authorization.policies.caller.grants);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
parseRegistry(JSON.parse(boundedJson2(registry, REGISTRY_MAX_BYTES)), resource);
|
|
1091
|
-
writeSetupFile(path, boundedJson2(registry, REGISTRY_MAX_BYTES), true, stamp);
|
|
1092
|
-
}
|
|
1093
|
-
function mergeSetupProviders(path, entries) {
|
|
1094
|
-
const stamp = setupFileStamp(path);
|
|
1095
|
-
const document = object2(readRuntimeJson(path, 1048576));
|
|
1096
|
-
fields2(document, ["schema_version", "role", "providers"]);
|
|
1097
|
-
if (document.schema_version !== 1 || document.role !== "gateway" || !Array.isArray(document.providers)) {
|
|
1098
|
-
throw new CliError2("configuration_required");
|
|
1099
|
-
}
|
|
1100
|
-
const merged = [...document.providers];
|
|
1101
|
-
for (const entry of entries) {
|
|
1102
|
-
const found = merged.find((value) => value.reference === entry.reference);
|
|
1103
|
-
if (!found) {
|
|
1104
|
-
merged.push(entry);
|
|
1105
|
-
} else if (boundedJson2(found.value, 1048576) !== boundedJson2(entry.value, 1048576)) {
|
|
1106
|
-
throw new CliError2("configuration_required");
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
if (merged.length > 32) {
|
|
1110
|
-
throw new CliError2("configuration_required");
|
|
1111
|
-
}
|
|
1112
|
-
writeSetupFile(path, boundedJson2({ ...document, providers: merged }, 1048576), true, stamp);
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
export { setupSessions, setupTarget, setupProviderKinds, selectedSetupPolicies, setupReference, setupSessionsFor, setupReply, requestJson, device, reviewSetupManifest, publishSetupSchemas, publish, publishFirstCallers, GatewaySetup, mergeSetupCallers, mergeSetupProviders };
|
|
1116
|
-
|
|
1117
|
-
//# debugId=3920190CFC3283A264756E2164756E21
|