dsh-account-authorization 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/README.zh-CN.md +43 -0
- package/SECURITY.md +39 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/cordis.patch.yml +5 -0
- package/lib/authorization-compat.js +52 -0
- package/lib/client.js +65 -0
- package/lib/client.js.map +1 -0
- package/lib/host.js +491 -0
- package/lib/index.js +548 -0
- package/lib/typert.host.js +369 -0
- package/lib/typert.remote-client.js +362 -0
- package/lib/types/authorization-compat.d.ts +18 -0
- package/lib/types/generated/host.d.ts +237 -0
- package/lib/types/generated/remote.d.ts +231 -0
- package/lib/types/host/index.d.ts +29 -0
- package/lib/types/index.d.ts +9 -0
- package/lib/types/types.d.ts +65 -0
- package/lib/types/types.js +1 -0
- package/package.json +146 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import AuthorizationService, { AuthorizationDeclinedError, AuthorizationError } from "@deepseek-ai/dsh-authorization";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
4
|
+
//#region ../lib/types/authorization-compat.js
|
|
5
|
+
/**
|
|
6
|
+
* DSH Desktop 2.0.3 compatibility service.
|
|
7
|
+
*
|
|
8
|
+
* Its bundled authorization service accepts flow metadata but drops it from
|
|
9
|
+
* list()/describe(). Preserve that opaque metadata while leaving flow,
|
|
10
|
+
* credential, and attempt ownership with the Harness authorization service.
|
|
11
|
+
*/
|
|
12
|
+
var TargetPreservingAuthorizationService = class extends AuthorizationService {
|
|
13
|
+
targets = /* @__PURE__ */ new Map();
|
|
14
|
+
registerFlow(flow) {
|
|
15
|
+
const disposeFlow = super.registerFlow(flow);
|
|
16
|
+
const target = "target" in flow ? flow.target : void 0;
|
|
17
|
+
if (target === void 0) {
|
|
18
|
+
this.targets.delete(flow.key);
|
|
19
|
+
return disposeFlow;
|
|
20
|
+
}
|
|
21
|
+
let disposeTarget;
|
|
22
|
+
try {
|
|
23
|
+
disposeTarget = this.ctx.effect(() => {
|
|
24
|
+
this.targets.set(flow.key, target);
|
|
25
|
+
return () => {
|
|
26
|
+
if (this.targets.get(flow.key) === target) this.targets.delete(flow.key);
|
|
27
|
+
};
|
|
28
|
+
}, "account-authorization: project flow target");
|
|
29
|
+
} catch (error) {
|
|
30
|
+
disposeFlow();
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
return () => {
|
|
34
|
+
try {
|
|
35
|
+
disposeTarget();
|
|
36
|
+
} finally {
|
|
37
|
+
disposeFlow();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
list() {
|
|
42
|
+
return super.list().map((entry) => this.projectTarget(entry));
|
|
43
|
+
}
|
|
44
|
+
describe(key) {
|
|
45
|
+
const entry = super.describe(key);
|
|
46
|
+
return entry === void 0 ? void 0 : this.projectTarget(entry);
|
|
47
|
+
}
|
|
48
|
+
projectTarget(entry) {
|
|
49
|
+
const target = this.targets.get(entry.key);
|
|
50
|
+
return target === void 0 ? entry : Object.assign({}, entry, { target });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region ../lib/types/host/index.js
|
|
55
|
+
/** Provider-neutral projection of Harness-owned authorization flows. */
|
|
56
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
57
|
+
var useValue = arguments.length > 2;
|
|
58
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
59
|
+
return useValue ? value : void 0;
|
|
60
|
+
};
|
|
61
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
62
|
+
function accept(f) {
|
|
63
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
64
|
+
return f;
|
|
65
|
+
}
|
|
66
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
67
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
68
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
69
|
+
var _, done = false;
|
|
70
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
71
|
+
var context = {};
|
|
72
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
73
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
74
|
+
context.addInitializer = function(f) {
|
|
75
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
76
|
+
extraInitializers.push(accept(f || null));
|
|
77
|
+
};
|
|
78
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
79
|
+
get: descriptor.get,
|
|
80
|
+
set: descriptor.set
|
|
81
|
+
} : descriptor[key], context);
|
|
82
|
+
if (kind === "accessor") {
|
|
83
|
+
if (result === void 0) continue;
|
|
84
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
85
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
86
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
87
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
88
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
89
|
+
else descriptor[key] = _;
|
|
90
|
+
}
|
|
91
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
92
|
+
done = true;
|
|
93
|
+
};
|
|
94
|
+
const MAX_RETAINED_ATTEMPTS = 32;
|
|
95
|
+
const MAX_RETAINED_COMMANDS = 64;
|
|
96
|
+
const MAX_RETAINED_REQUESTS = 128;
|
|
97
|
+
const MAX_LABEL_LENGTH = 256;
|
|
98
|
+
const MAX_CODE_LENGTH = 256;
|
|
99
|
+
const MAX_AUTHORIZATION_URL_LENGTH = 4096;
|
|
100
|
+
const MAX_CHOICES = 32;
|
|
101
|
+
function connectionId(key) {
|
|
102
|
+
return createHash("sha256").update(key).digest("base64url");
|
|
103
|
+
}
|
|
104
|
+
function bounded(value, maximum = MAX_LABEL_LENGTH) {
|
|
105
|
+
return value.length <= maximum ? value : value.slice(0, maximum);
|
|
106
|
+
}
|
|
107
|
+
function safeAuthorizationUrl(value) {
|
|
108
|
+
if (value === void 0 || value.length > MAX_AUTHORIZATION_URL_LENGTH) return void 0;
|
|
109
|
+
try {
|
|
110
|
+
const url = new URL(value);
|
|
111
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
112
|
+
return url.protocol === "https:" || url.protocol === "http:" && loopback ? url.href : void 0;
|
|
113
|
+
} catch {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function methodKind(id) {
|
|
118
|
+
const normalized = id.toLowerCase();
|
|
119
|
+
if (normalized.includes("device")) return "device";
|
|
120
|
+
if (normalized === "oauth" || normalized.includes("browser")) return "browser";
|
|
121
|
+
return "other";
|
|
122
|
+
}
|
|
123
|
+
function noticeView(notice) {
|
|
124
|
+
const url = safeAuthorizationUrl(notice.url);
|
|
125
|
+
if (notice.code !== void 0) return {
|
|
126
|
+
kind: "device",
|
|
127
|
+
...url === void 0 ? {} : { url },
|
|
128
|
+
code: bounded(notice.code, MAX_CODE_LENGTH)
|
|
129
|
+
};
|
|
130
|
+
if (url !== void 0) return {
|
|
131
|
+
kind: "browser",
|
|
132
|
+
url
|
|
133
|
+
};
|
|
134
|
+
return { kind: "waiting" };
|
|
135
|
+
}
|
|
136
|
+
function failure(error, unsupportedInput) {
|
|
137
|
+
if (unsupportedInput) return {
|
|
138
|
+
status: "failed",
|
|
139
|
+
code: "input-required"
|
|
140
|
+
};
|
|
141
|
+
if (error instanceof AuthorizationError) {
|
|
142
|
+
if (error.code === "NO_FLOW") return {
|
|
143
|
+
status: "failed",
|
|
144
|
+
code: "unavailable"
|
|
145
|
+
};
|
|
146
|
+
if (error.code === "UNKNOWN_METHOD") return {
|
|
147
|
+
status: "failed",
|
|
148
|
+
code: "unknown-method"
|
|
149
|
+
};
|
|
150
|
+
if (error.code === "ALREADY_IN_FLIGHT") return {
|
|
151
|
+
status: "failed",
|
|
152
|
+
code: "in-flight"
|
|
153
|
+
};
|
|
154
|
+
if (error.code === "NOT_COMMITTED") return {
|
|
155
|
+
status: "failed",
|
|
156
|
+
code: "not-committed"
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
status: "failed",
|
|
161
|
+
code: "flow-failed"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function assertRequestId(value) {
|
|
165
|
+
if (!/^[A-Za-z0-9_-]{8,128}$/.test(value)) throw new TypeError("requestId is invalid");
|
|
166
|
+
}
|
|
167
|
+
function isModelAuthorization(entry) {
|
|
168
|
+
if (!("target" in entry)) return false;
|
|
169
|
+
const target = entry.target;
|
|
170
|
+
return typeof target === "object" && target !== null && "kind" in target && target.kind === "llm-provider" && "providerId" in target && typeof target.providerId === "string" && target.providerId.length > 0;
|
|
171
|
+
}
|
|
172
|
+
/** Host service. Harness remains the only owner of flows, attempts, credentials, Providers, and models. */
|
|
173
|
+
let AccountAuthorizationService = (() => {
|
|
174
|
+
let _classSuper = TypertRemoteService;
|
|
175
|
+
let _instanceExtraInitializers = [];
|
|
176
|
+
let _connections_decorators;
|
|
177
|
+
let _start_decorators;
|
|
178
|
+
let _observe_decorators;
|
|
179
|
+
let _choose_decorators;
|
|
180
|
+
let _cancel_decorators;
|
|
181
|
+
let _disconnect_decorators;
|
|
182
|
+
return class AccountAuthorizationService extends _classSuper {
|
|
183
|
+
static {
|
|
184
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
185
|
+
_connections_decorators = [Remote("connections")];
|
|
186
|
+
_start_decorators = [Remote("start")];
|
|
187
|
+
_observe_decorators = [Remote("observe")];
|
|
188
|
+
_choose_decorators = [Remote("choose")];
|
|
189
|
+
_cancel_decorators = [Remote("cancel")];
|
|
190
|
+
_disconnect_decorators = [Remote("disconnect")];
|
|
191
|
+
__esDecorate(this, null, _connections_decorators, {
|
|
192
|
+
kind: "method",
|
|
193
|
+
name: "connections",
|
|
194
|
+
static: false,
|
|
195
|
+
private: false,
|
|
196
|
+
access: {
|
|
197
|
+
has: (obj) => "connections" in obj,
|
|
198
|
+
get: (obj) => obj.connections
|
|
199
|
+
},
|
|
200
|
+
metadata: _metadata
|
|
201
|
+
}, null, _instanceExtraInitializers);
|
|
202
|
+
__esDecorate(this, null, _start_decorators, {
|
|
203
|
+
kind: "method",
|
|
204
|
+
name: "start",
|
|
205
|
+
static: false,
|
|
206
|
+
private: false,
|
|
207
|
+
access: {
|
|
208
|
+
has: (obj) => "start" in obj,
|
|
209
|
+
get: (obj) => obj.start
|
|
210
|
+
},
|
|
211
|
+
metadata: _metadata
|
|
212
|
+
}, null, _instanceExtraInitializers);
|
|
213
|
+
__esDecorate(this, null, _observe_decorators, {
|
|
214
|
+
kind: "method",
|
|
215
|
+
name: "observe",
|
|
216
|
+
static: false,
|
|
217
|
+
private: false,
|
|
218
|
+
access: {
|
|
219
|
+
has: (obj) => "observe" in obj,
|
|
220
|
+
get: (obj) => obj.observe
|
|
221
|
+
},
|
|
222
|
+
metadata: _metadata
|
|
223
|
+
}, null, _instanceExtraInitializers);
|
|
224
|
+
__esDecorate(this, null, _choose_decorators, {
|
|
225
|
+
kind: "method",
|
|
226
|
+
name: "choose",
|
|
227
|
+
static: false,
|
|
228
|
+
private: false,
|
|
229
|
+
access: {
|
|
230
|
+
has: (obj) => "choose" in obj,
|
|
231
|
+
get: (obj) => obj.choose
|
|
232
|
+
},
|
|
233
|
+
metadata: _metadata
|
|
234
|
+
}, null, _instanceExtraInitializers);
|
|
235
|
+
__esDecorate(this, null, _cancel_decorators, {
|
|
236
|
+
kind: "method",
|
|
237
|
+
name: "cancel",
|
|
238
|
+
static: false,
|
|
239
|
+
private: false,
|
|
240
|
+
access: {
|
|
241
|
+
has: (obj) => "cancel" in obj,
|
|
242
|
+
get: (obj) => obj.cancel
|
|
243
|
+
},
|
|
244
|
+
metadata: _metadata
|
|
245
|
+
}, null, _instanceExtraInitializers);
|
|
246
|
+
__esDecorate(this, null, _disconnect_decorators, {
|
|
247
|
+
kind: "method",
|
|
248
|
+
name: "disconnect",
|
|
249
|
+
static: false,
|
|
250
|
+
private: false,
|
|
251
|
+
access: {
|
|
252
|
+
has: (obj) => "disconnect" in obj,
|
|
253
|
+
get: (obj) => obj.disconnect
|
|
254
|
+
},
|
|
255
|
+
metadata: _metadata
|
|
256
|
+
}, null, _instanceExtraInitializers);
|
|
257
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
258
|
+
enumerable: true,
|
|
259
|
+
configurable: true,
|
|
260
|
+
writable: true,
|
|
261
|
+
value: _metadata
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
static inject = ["authorization", "credentials"];
|
|
265
|
+
attempts = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Map());
|
|
266
|
+
startRequests = /* @__PURE__ */ new Map();
|
|
267
|
+
disconnectRequests = /* @__PURE__ */ new Map();
|
|
268
|
+
disconnectingConnections = /* @__PURE__ */ new Set();
|
|
269
|
+
constructor(ctx) {
|
|
270
|
+
super(ctx, "accountAuthorization");
|
|
271
|
+
ctx.effect(() => () => {
|
|
272
|
+
for (const attempt of this.attempts.values()) {
|
|
273
|
+
attempt.pending?.reject(new AuthorizationDeclinedError());
|
|
274
|
+
attempt.pending?.dispose();
|
|
275
|
+
attempt.controller.abort();
|
|
276
|
+
}
|
|
277
|
+
this.attempts.clear();
|
|
278
|
+
this.startRequests.clear();
|
|
279
|
+
this.disconnectRequests.clear();
|
|
280
|
+
this.disconnectingConnections.clear();
|
|
281
|
+
}, "account-authorization: dispose attempts");
|
|
282
|
+
}
|
|
283
|
+
flowByConnection(id) {
|
|
284
|
+
return this.ctx.authorization.list().find((entry) => isModelAuthorization(entry) && connectionId(entry.key) === id);
|
|
285
|
+
}
|
|
286
|
+
snapshot(attempt) {
|
|
287
|
+
return {
|
|
288
|
+
id: attempt.id,
|
|
289
|
+
connectionId: attempt.connectionId,
|
|
290
|
+
revision: attempt.revision,
|
|
291
|
+
...attempt.notice === void 0 ? {} : { notice: attempt.notice },
|
|
292
|
+
...attempt.prompt === void 0 ? {} : { prompt: attempt.prompt },
|
|
293
|
+
...attempt.terminal === void 0 ? {} : { terminal: attempt.terminal }
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
changed(attempt) {
|
|
297
|
+
attempt.revision += 1;
|
|
298
|
+
}
|
|
299
|
+
rememberCommand(attempt, requestId, operation) {
|
|
300
|
+
const existing = attempt.commands.get(requestId);
|
|
301
|
+
if (existing !== void 0) {
|
|
302
|
+
if (existing !== operation) throw new TypeError("requestId was already used for another operation");
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (attempt.commands.size >= MAX_RETAINED_COMMANDS) {
|
|
306
|
+
const first = attempt.commands.keys().next().value;
|
|
307
|
+
if (first !== void 0) attempt.commands.delete(first);
|
|
308
|
+
}
|
|
309
|
+
attempt.commands.set(requestId, operation);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
trimAttempts() {
|
|
313
|
+
if (this.attempts.size < MAX_RETAINED_ATTEMPTS) return;
|
|
314
|
+
const removable = [...this.attempts.values()].find((attempt) => attempt.terminal !== void 0);
|
|
315
|
+
if (removable !== void 0) {
|
|
316
|
+
this.attempts.delete(removable.id);
|
|
317
|
+
for (const [requestId, record] of this.startRequests) if (record.attemptId === removable.id) this.startRequests.delete(requestId);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
trimRequests(requests) {
|
|
321
|
+
if (requests.size < MAX_RETAINED_REQUESTS) return;
|
|
322
|
+
const first = requests.keys().next().value;
|
|
323
|
+
if (first !== void 0) requests.delete(first);
|
|
324
|
+
}
|
|
325
|
+
async connections() {
|
|
326
|
+
const rows = [];
|
|
327
|
+
for (const flow of this.ctx.authorization.list()) {
|
|
328
|
+
if (!isModelAuthorization(flow)) continue;
|
|
329
|
+
const methods = flow.methods.filter((method) => method.id !== "api-key" && method.id.length <= MAX_LABEL_LENGTH).map((method) => ({
|
|
330
|
+
id: method.id,
|
|
331
|
+
label: bounded(method.label),
|
|
332
|
+
kind: methodKind(method.id)
|
|
333
|
+
}));
|
|
334
|
+
if (methods.length === 0) continue;
|
|
335
|
+
const record = await this.ctx.credentials.describeRecord(flow.key);
|
|
336
|
+
rows.push({
|
|
337
|
+
id: connectionId(flow.key),
|
|
338
|
+
label: bounded(flow.label),
|
|
339
|
+
configured: record.configured,
|
|
340
|
+
inFlight: flow.inFlight,
|
|
341
|
+
methods
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return { connections: rows };
|
|
345
|
+
}
|
|
346
|
+
start(request) {
|
|
347
|
+
assertRequestId(request.requestId);
|
|
348
|
+
const repeated = this.startRequests.get(request.requestId);
|
|
349
|
+
if (repeated !== void 0) {
|
|
350
|
+
const operation = `${request.connectionId}:${request.method}`;
|
|
351
|
+
if (repeated.operation !== operation) throw new TypeError("requestId was already used for another authorization start");
|
|
352
|
+
const existing = this.attempts.get(repeated.attemptId);
|
|
353
|
+
if (existing === void 0) throw new Error("idempotent authorization attempt expired");
|
|
354
|
+
return this.snapshot(existing);
|
|
355
|
+
}
|
|
356
|
+
const flow = this.flowByConnection(request.connectionId);
|
|
357
|
+
if (flow === void 0) throw new Error("authorization connection is unavailable");
|
|
358
|
+
if (this.disconnectingConnections.has(request.connectionId)) throw new Error("authorization disconnect is in flight");
|
|
359
|
+
if (!flow.methods.some((method) => method.id === request.method && method.id !== "api-key")) throw new Error("authorization method is unavailable");
|
|
360
|
+
this.trimAttempts();
|
|
361
|
+
if (this.attempts.size >= MAX_RETAINED_ATTEMPTS) throw new Error("too many retained authorization attempts");
|
|
362
|
+
const attempt = {
|
|
363
|
+
id: randomUUID(),
|
|
364
|
+
connectionId: request.connectionId,
|
|
365
|
+
key: flow.key,
|
|
366
|
+
controller: new AbortController(),
|
|
367
|
+
revision: 0,
|
|
368
|
+
unsupportedInput: false,
|
|
369
|
+
commands: /* @__PURE__ */ new Map()
|
|
370
|
+
};
|
|
371
|
+
this.attempts.set(attempt.id, attempt);
|
|
372
|
+
this.trimRequests(this.startRequests);
|
|
373
|
+
this.startRequests.set(request.requestId, {
|
|
374
|
+
attemptId: attempt.id,
|
|
375
|
+
operation: `${request.connectionId}:${request.method}`
|
|
376
|
+
});
|
|
377
|
+
const prompt = (offered) => {
|
|
378
|
+
if (offered.kind === "text" && offered.signal !== void 0 && attempt.notice?.kind === "browser") {
|
|
379
|
+
const signal = offered.signal;
|
|
380
|
+
return new Promise((_resolve, reject) => {
|
|
381
|
+
const id = randomUUID();
|
|
382
|
+
const onAbort = () => {
|
|
383
|
+
pending.dispose();
|
|
384
|
+
reject(/* @__PURE__ */ new Error("authorization prompt was withdrawn"));
|
|
385
|
+
};
|
|
386
|
+
const pending = {
|
|
387
|
+
id,
|
|
388
|
+
kind: "browser-fallback",
|
|
389
|
+
reject,
|
|
390
|
+
dispose: () => {
|
|
391
|
+
signal.removeEventListener("abort", onAbort);
|
|
392
|
+
if (attempt.pending === pending) delete attempt.pending;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
attempt.pending = pending;
|
|
396
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
397
|
+
if (signal.aborted) onAbort();
|
|
398
|
+
else this.changed(attempt);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (offered.kind !== "select") {
|
|
402
|
+
attempt.unsupportedInput = true;
|
|
403
|
+
this.changed(attempt);
|
|
404
|
+
return Promise.reject(new AuthorizationDeclinedError());
|
|
405
|
+
}
|
|
406
|
+
return new Promise((resolve, reject) => {
|
|
407
|
+
const id = randomUUID();
|
|
408
|
+
const onAbort = () => {
|
|
409
|
+
pending.dispose();
|
|
410
|
+
reject(/* @__PURE__ */ new Error("authorization prompt was withdrawn"));
|
|
411
|
+
};
|
|
412
|
+
const pending = {
|
|
413
|
+
id,
|
|
414
|
+
kind: "choice",
|
|
415
|
+
offered,
|
|
416
|
+
resolve,
|
|
417
|
+
reject,
|
|
418
|
+
dispose: () => {
|
|
419
|
+
offered.signal?.removeEventListener("abort", onAbort);
|
|
420
|
+
if (attempt.pending === pending) delete attempt.pending;
|
|
421
|
+
if (attempt.prompt?.id === id) delete attempt.prompt;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
attempt.pending = pending;
|
|
425
|
+
attempt.prompt = {
|
|
426
|
+
id,
|
|
427
|
+
choices: offered.options.filter((option) => option.id.length <= MAX_LABEL_LENGTH).slice(0, MAX_CHOICES).map((option) => ({
|
|
428
|
+
id: option.id,
|
|
429
|
+
label: bounded(option.label),
|
|
430
|
+
kind: methodKind(option.id)
|
|
431
|
+
}))
|
|
432
|
+
};
|
|
433
|
+
offered.signal?.addEventListener("abort", onAbort, { once: true });
|
|
434
|
+
if (offered.signal?.aborted === true) onAbort();
|
|
435
|
+
else this.changed(attempt);
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
this.ctx.authorization.begin({
|
|
439
|
+
key: flow.key,
|
|
440
|
+
method: request.method,
|
|
441
|
+
signal: attempt.controller.signal,
|
|
442
|
+
interaction: {
|
|
443
|
+
notify: (notice) => {
|
|
444
|
+
attempt.notice = noticeView(notice);
|
|
445
|
+
this.changed(attempt);
|
|
446
|
+
},
|
|
447
|
+
prompt
|
|
448
|
+
}
|
|
449
|
+
}).then((outcome) => {
|
|
450
|
+
attempt.pending?.dispose();
|
|
451
|
+
attempt.terminal = attempt.unsupportedInput ? {
|
|
452
|
+
status: "failed",
|
|
453
|
+
code: "input-required"
|
|
454
|
+
} : { status: outcome.status };
|
|
455
|
+
this.changed(attempt);
|
|
456
|
+
}, (error) => {
|
|
457
|
+
attempt.pending?.dispose();
|
|
458
|
+
attempt.terminal = failure(error, attempt.unsupportedInput);
|
|
459
|
+
this.changed(attempt);
|
|
460
|
+
});
|
|
461
|
+
return this.snapshot(attempt);
|
|
462
|
+
}
|
|
463
|
+
observe(attemptId) {
|
|
464
|
+
const attempt = this.attempts.get(attemptId);
|
|
465
|
+
if (attempt === void 0) throw new Error("authorization attempt is unavailable");
|
|
466
|
+
return this.snapshot(attempt);
|
|
467
|
+
}
|
|
468
|
+
choose(request) {
|
|
469
|
+
assertRequestId(request.requestId);
|
|
470
|
+
const attempt = this.attempts.get(request.attemptId);
|
|
471
|
+
if (attempt === void 0) throw new Error("authorization attempt is unavailable");
|
|
472
|
+
const operation = `choose:${request.promptId}:${request.choice}`;
|
|
473
|
+
if (!this.rememberCommand(attempt, request.requestId, operation)) return this.snapshot(attempt);
|
|
474
|
+
const pending = attempt.pending;
|
|
475
|
+
if (pending === void 0 || pending.kind !== "choice" || pending.id !== request.promptId) throw new Error("authorization choice is no longer active");
|
|
476
|
+
if (!pending.offered.options.some((option) => option.id === request.choice)) throw new Error("authorization choice is invalid");
|
|
477
|
+
pending.dispose();
|
|
478
|
+
pending.resolve(request.choice);
|
|
479
|
+
this.changed(attempt);
|
|
480
|
+
return this.snapshot(attempt);
|
|
481
|
+
}
|
|
482
|
+
cancel(request) {
|
|
483
|
+
assertRequestId(request.requestId);
|
|
484
|
+
const attempt = this.attempts.get(request.attemptId);
|
|
485
|
+
if (attempt === void 0) throw new Error("authorization attempt is unavailable");
|
|
486
|
+
if (this.rememberCommand(attempt, request.requestId, "cancel")) {
|
|
487
|
+
attempt.pending?.reject(new AuthorizationDeclinedError());
|
|
488
|
+
attempt.pending?.dispose();
|
|
489
|
+
attempt.controller.abort();
|
|
490
|
+
this.ctx.authorization.cancel(attempt.key);
|
|
491
|
+
this.changed(attempt);
|
|
492
|
+
}
|
|
493
|
+
return this.snapshot(attempt);
|
|
494
|
+
}
|
|
495
|
+
async disconnect(request) {
|
|
496
|
+
assertRequestId(request.requestId);
|
|
497
|
+
const repeated = this.disconnectRequests.get(request.requestId);
|
|
498
|
+
if (repeated !== void 0) {
|
|
499
|
+
if (repeated.connectionId !== request.connectionId) throw new TypeError("requestId was already used for another disconnect");
|
|
500
|
+
return repeated.result;
|
|
501
|
+
}
|
|
502
|
+
const flow = this.flowByConnection(request.connectionId);
|
|
503
|
+
if (flow === void 0) throw new Error("authorization connection is unavailable");
|
|
504
|
+
const localAttemptActive = [...this.attempts.values()].some((attempt) => attempt.connectionId === request.connectionId && attempt.terminal === void 0);
|
|
505
|
+
if (flow.inFlight || localAttemptActive || this.disconnectingConnections.has(request.connectionId)) throw new Error("authorization is in flight");
|
|
506
|
+
if (this.disconnectRequests.size >= MAX_RETAINED_REQUESTS) {
|
|
507
|
+
const settled = [...this.disconnectRequests].find(([, record]) => record.settled);
|
|
508
|
+
if (settled === void 0) throw new Error("too many authorization disconnects are in flight");
|
|
509
|
+
this.disconnectRequests.delete(settled[0]);
|
|
510
|
+
}
|
|
511
|
+
let resolveResult;
|
|
512
|
+
let rejectResult;
|
|
513
|
+
const result = new Promise((resolve, reject) => {
|
|
514
|
+
resolveResult = resolve;
|
|
515
|
+
rejectResult = reject;
|
|
516
|
+
});
|
|
517
|
+
const record = {
|
|
518
|
+
connectionId: request.connectionId,
|
|
519
|
+
result,
|
|
520
|
+
settled: false
|
|
521
|
+
};
|
|
522
|
+
this.disconnectRequests.set(request.requestId, record);
|
|
523
|
+
this.disconnectingConnections.add(request.connectionId);
|
|
524
|
+
(async () => {
|
|
525
|
+
try {
|
|
526
|
+
this.ctx.authorization.cancel(flow.key);
|
|
527
|
+
await this.ctx.credentials.deleteRecord(flow.key);
|
|
528
|
+
resolveResult({ disconnected: true });
|
|
529
|
+
} catch (error) {
|
|
530
|
+
rejectResult(error);
|
|
531
|
+
} finally {
|
|
532
|
+
record.settled = true;
|
|
533
|
+
this.disconnectingConnections.delete(request.connectionId);
|
|
534
|
+
}
|
|
535
|
+
})();
|
|
536
|
+
return result;
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
})();
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region ../lib/types/index.js
|
|
542
|
+
/** Mount one authorization owner, then the model-account Host projection. */
|
|
543
|
+
async function apply(ctx) {
|
|
544
|
+
if (ctx.get("authorization", false) === void 0) await ctx.plugin(TargetPreservingAuthorizationService);
|
|
545
|
+
await ctx.plugin(AccountAuthorizationService);
|
|
546
|
+
}
|
|
547
|
+
//#endregion
|
|
548
|
+
export { AccountAuthorizationService, TargetPreservingAuthorizationService, apply, apply as default };
|