@arcblock/did-connect-js 4.0.1-beta.6 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2472 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ CloudflareKVStorage: () => kv_default,
34
+ VERSION: () => import_did_connect_core2.VERSION,
35
+ WalletAuthenticator: () => wallet_default,
36
+ WalletHandlers: () => wallet_default2,
37
+ attachExpress: () => attachExpress,
38
+ attachHono: () => attachHono,
39
+ createHonoRequest: () => createHonoRequest,
40
+ createHonoResponse: () => createHonoResponse,
41
+ isExpressApp: () => isExpressApp,
42
+ isHonoApp: () => isHonoApp,
43
+ parseWalletUA: () => parseWalletUA,
44
+ prepareBaseUrl: () => prepareBaseUrl
45
+ });
46
+ module.exports = __toCommonJS(index_exports);
47
+
48
+ // src/authenticator/wallet.ts
49
+ var import_node_querystring = __toESM(require("node:querystring"), 1);
50
+ var import_did = require("@arcblock/did");
51
+ var import_rsa = __toESM(require("@ocap/mcrypto/lib/crypter/rsa"), 1);
52
+ var import_util = require("@ocap/util");
53
+ var import_wallet2 = require("@ocap/wallet");
54
+ var import_debug2 = __toESM(require("debug"), 1);
55
+
56
+ // src/utils/helpers.ts
57
+ function pick(obj, keys) {
58
+ const result = {};
59
+ for (const k of keys) {
60
+ if (k in obj) result[k] = obj[k];
61
+ }
62
+ return result;
63
+ }
64
+ function omit(obj, keys) {
65
+ const result = {};
66
+ for (const k of Object.keys(obj)) {
67
+ if (!keys.includes(k)) result[k] = obj[k];
68
+ }
69
+ return result;
70
+ }
71
+ function isEqual(a, b) {
72
+ if (a === b) return true;
73
+ if (a == null || b == null) return false;
74
+ if (typeof a !== typeof b) return false;
75
+ if (Array.isArray(a)) {
76
+ if (!Array.isArray(b) || a.length !== b.length) return false;
77
+ return a.every((v3, i) => isEqual(v3, b[i]));
78
+ }
79
+ if (typeof a === "object") {
80
+ const ka = Object.keys(a);
81
+ const kb = Object.keys(b);
82
+ if (ka.length !== kb.length) return false;
83
+ return ka.every((k) => isEqual(a[k], b[k]));
84
+ }
85
+ return false;
86
+ }
87
+ function isPlainObject(value) {
88
+ if (value == null || typeof value !== "object") return false;
89
+ const proto = Object.getPrototypeOf(value);
90
+ return proto === Object.prototype || proto === null;
91
+ }
92
+ function random(min, max) {
93
+ return min + Math.floor(Math.random() * (max - min + 1));
94
+ }
95
+ function cloneDeep(value) {
96
+ if (value === null || typeof value !== "object") return value;
97
+ if (Array.isArray(value)) return value.map((item) => cloneDeep(item));
98
+ const result = {};
99
+ for (const key of Object.keys(value)) {
100
+ result[key] = cloneDeep(value[key]);
101
+ }
102
+ return result;
103
+ }
104
+ function shuffle(arr) {
105
+ const a = [...arr];
106
+ for (let i = a.length - 1; i > 0; i--) {
107
+ const j = Math.floor(Math.random() * (i + 1));
108
+ [a[i], a[j]] = [a[j], a[i]];
109
+ }
110
+ return a;
111
+ }
112
+
113
+ // src/authenticator/base.ts
114
+ var Jwt = __toESM(require("@arcblock/jwt"), 1);
115
+ var import_wallet = require("@ocap/wallet");
116
+ var import_debug = __toESM(require("debug"), 1);
117
+ var debug = (0, import_debug.default)("@arcblock/did-connect-js:authenticator:base");
118
+ var DEFAULT_CHAIN_INFO = { id: "none", host: "none", type: "arcblock" };
119
+ var BaseAuthenticator = class {
120
+ _validateWallet(wallet, canSign = true) {
121
+ if (!wallet) {
122
+ throw new Error("WalletAuthenticator cannot work without wallet");
123
+ }
124
+ if (typeof wallet === "function") {
125
+ return wallet;
126
+ }
127
+ if ((0, import_wallet.isValid)(wallet, canSign)) {
128
+ if (!wallet.pk && wallet.publicKey) {
129
+ wallet.pk = wallet.publicKey;
130
+ }
131
+ if (!wallet.sk && wallet.secretKey) {
132
+ wallet.sk = wallet.secretKey;
133
+ }
134
+ return wallet;
135
+ }
136
+ if (canSign && !wallet.sk) {
137
+ throw new Error("WalletAuthenticator cannot work without wallet.sk");
138
+ }
139
+ if (!wallet.pk) {
140
+ throw new Error("WalletAuthenticator cannot work without wallet.pk");
141
+ }
142
+ if (!wallet.address) {
143
+ throw new Error("WalletAuthenticator cannot work without wallet.address");
144
+ }
145
+ try {
146
+ const fullWallet = (0, import_wallet.fromJSON)(wallet);
147
+ if (!fullWallet.pk && fullWallet.publicKey) {
148
+ fullWallet.pk = fullWallet.publicKey;
149
+ }
150
+ if (!fullWallet.sk && fullWallet.secretKey) {
151
+ fullWallet.sk = fullWallet.secretKey;
152
+ }
153
+ return fullWallet;
154
+ } catch (_err) {
155
+ return wallet;
156
+ }
157
+ }
158
+ /**
159
+ * Verify a DID auth response sent from DID Wallet
160
+ *
161
+ * @method
162
+ * @param {object} data
163
+ * @param {string} [locale=en]
164
+ * @param {boolean} [enforceTimestamp=true]
165
+ * @returns Promise<boolean>
166
+ */
167
+ async _verify(data, fieldPk, fieldInfo, locale = "en", enforceTimestamp = true) {
168
+ debug("verify", data, locale);
169
+ const errors2 = {
170
+ pkMissing: {
171
+ en: `${fieldPk} is required to complete auth`,
172
+ zh: `${fieldPk} \u53C2\u6570\u7F3A\u5931`
173
+ },
174
+ tokenMissing: {
175
+ en: `${fieldInfo} is required to complete auth`,
176
+ zh: "JWT Token \u53C2\u6570\u7F3A\u5931"
177
+ },
178
+ pkFormat: {
179
+ en: `${fieldPk} should be either base58 or base16 format`,
180
+ zh: `${fieldPk} \u65E0\u6CD5\u89E3\u6790`
181
+ },
182
+ tokenInvalid: {
183
+ en: "Invalid JWT token",
184
+ zh: "\u7B7E\u540D\u65E0\u6548"
185
+ },
186
+ timeInvalid: {
187
+ en: "JWT token expired, make sure your device time in sync with network",
188
+ zh: "\u7B7E\u540D\u4E2D\u7684\u65F6\u95F4\u6233\u65E0\u6548\uFF0C\u8BF7\u786E\u4FDD\u8BBE\u5907\u548C\u7F51\u7EDC\u65F6\u95F4\u540C\u6B65"
189
+ }
190
+ };
191
+ const pk = data[fieldPk];
192
+ const info = data[fieldInfo];
193
+ if (!pk) {
194
+ throw new Error(errors2.pkMissing[locale]);
195
+ }
196
+ if (!info) {
197
+ throw new Error(errors2.tokenMissing[locale]);
198
+ }
199
+ if (!pk) {
200
+ throw new Error(errors2.pkFormat[locale]);
201
+ }
202
+ if (!await Jwt.verify(info, pk)) {
203
+ const isValidSig = await Jwt.verify(info, pk, { tolerance: 0, enforceTimestamp: false });
204
+ if (enforceTimestamp) {
205
+ const error = isValidSig ? errors2.timeInvalid[locale] : errors2.tokenInvalid[locale];
206
+ throw new Error(error);
207
+ }
208
+ }
209
+ return Jwt.decode(info);
210
+ }
211
+ };
212
+ var base_default = BaseAuthenticator;
213
+
214
+ // src/schema/index.ts
215
+ var import_validator2 = require("@arcblock/validator");
216
+ var v2 = __toESM(require("valibot"), 1);
217
+
218
+ // src/schema/claims.ts
219
+ var import_validator = require("@arcblock/validator");
220
+ var import_mcrypto = require("@ocap/mcrypto");
221
+ var v = __toESM(require("valibot"), 1);
222
+ var isHttpUrl = (s) => {
223
+ try {
224
+ const url2 = new URL(s);
225
+ return ["http:", "https:"].includes(url2.protocol);
226
+ } catch {
227
+ return false;
228
+ }
229
+ };
230
+ var trustedIssuerSchema = v.union([
231
+ v.object({
232
+ did: (0, import_validator.vDID)(),
233
+ endpoint: v.pipe(v.string(), v.check(isHttpUrl, "Must be a valid HTTP/HTTPS URL"))
234
+ }),
235
+ (0, import_validator.vDID)()
236
+ ]);
237
+ var optionalUrlSchema = v.optional(
238
+ v.pipe(
239
+ v.string(),
240
+ v.check((s) => {
241
+ if (s === "") return true;
242
+ try {
243
+ const url2 = new URL(s);
244
+ return ["http:", "https:"].includes(url2.protocol);
245
+ } catch {
246
+ return s.startsWith("/");
247
+ }
248
+ }, "Must be a valid URL")
249
+ ),
250
+ ""
251
+ );
252
+ var requirementSchema = v.object({
253
+ tokens: v.array(
254
+ v.object({
255
+ address: (0, import_validator.vDID)(),
256
+ value: (0, import_validator.vBNPositive)()
257
+ })
258
+ ),
259
+ assets: v.optional(
260
+ v.object({
261
+ address: v.optional(v.array((0, import_validator.vDID)())),
262
+ parent: v.optional(v.array((0, import_validator.vDID)())),
263
+ issuer: v.optional(v.array((0, import_validator.vDID)())),
264
+ amount: v.optional(v.pipe(v.number(), v.check((n) => n > 0, "Must be positive"), v.minValue(1)))
265
+ })
266
+ )
267
+ });
268
+ var targetTypeSchema = v.optional(
269
+ v.object({
270
+ key: v.optional(
271
+ v.picklist(Object.keys(import_mcrypto.types.KeyType).map((x) => x.toLowerCase())),
272
+ "ed25519"
273
+ ),
274
+ hash: v.optional(
275
+ v.picklist(Object.keys(import_mcrypto.types.HashType).map((x) => x.toLowerCase())),
276
+ "sha3"
277
+ ),
278
+ role: v.optional(
279
+ v.picklist(Object.keys(import_mcrypto.types.RoleType).map((x) => x.toLowerCase().split("_").pop())),
280
+ "account"
281
+ ),
282
+ encoding: v.optional(
283
+ v.picklist(Object.keys(import_mcrypto.types.EncodingType).map((x) => x.toLowerCase())),
284
+ "base58"
285
+ )
286
+ })
287
+ );
288
+ function withValidate(schema) {
289
+ return Object.assign(schema, {
290
+ validate: (data) => {
291
+ const { value, error } = (0, import_validator.vValidate)(schema, data);
292
+ if (error) error.toString = () => error.message;
293
+ return { value, error };
294
+ }
295
+ });
296
+ }
297
+ var profileItemValues = ["did", "fullName", "email", "phone", "signature", "avatar", "birthday", "url"];
298
+ var claims_default = (chainInfo2) => {
299
+ const createStandardFields = (type, description) => ({
300
+ type: v.optional(v.literal(type), type),
301
+ description: v.optional(v.pipe(v.string(), v.minLength(1)), description),
302
+ chainInfo: v.optional(chainInfo2),
303
+ mfaCode: v.optional(v.array(v.optional(v.pipe(v.number(), v.minValue(10), v.maxValue(99)))), []),
304
+ meta: v.optional(v.any(), {})
305
+ });
306
+ const authPrincipal = withValidate(
307
+ v.object({
308
+ ...createStandardFields("authPrincipal", "Please continue with your account"),
309
+ target: v.optional(v.union([v.literal(""), (0, import_validator.vDID)()]), ""),
310
+ supervised: v.optional(v.boolean(), false),
311
+ targetType: targetTypeSchema
312
+ })
313
+ );
314
+ const keyPair = withValidate(
315
+ v.object({
316
+ ...createStandardFields("keyPair", "Please create account to continue."),
317
+ moniker: v.pipe(v.string(), v.regex(/^[a-zA-Z0-9][-a-zA-Z0-9_]{2,128}$/)),
318
+ declare: v.optional(v.boolean(), true),
319
+ migrateFrom: v.optional(v.union([v.literal(""), (0, import_validator.vDID)()]), ""),
320
+ targetType: targetTypeSchema
321
+ })
322
+ );
323
+ const encryptionKey = withValidate(
324
+ v.object({
325
+ ...createStandardFields("encryptionKey", "Please provide encryptionKey to continue."),
326
+ salt: v.string(),
327
+ delegation: v.optional(v.union([v.literal(""), v.string()]), "")
328
+ })
329
+ );
330
+ const profile = withValidate(
331
+ v.pipe(
332
+ v.looseObject({
333
+ ...createStandardFields("profile", "Please provide your profile to continue.")
334
+ }),
335
+ // Rename fields → items (ignoreUndefined: true, override: true)
336
+ v.transform((obj) => {
337
+ const { fields, ...rest } = obj;
338
+ if (fields !== void 0) {
339
+ return { ...rest, items: fields };
340
+ }
341
+ return rest;
342
+ }),
343
+ v.rawTransform(({ dataset, addIssue, NEVER }) => {
344
+ const obj = dataset.value;
345
+ const items = obj.items ?? ["fullName"];
346
+ if (!Array.isArray(items) || items.length < 1) {
347
+ addIssue({ message: '"items" must contain at least 1 items' });
348
+ return NEVER;
349
+ }
350
+ const validSet = new Set(profileItemValues);
351
+ for (let i = 0; i < items.length; i++) {
352
+ if (!validSet.has(items[i])) {
353
+ addIssue({
354
+ message: `"items[${i}]" must be one of [${[...profileItemValues].join(", ")}]`
355
+ });
356
+ return NEVER;
357
+ }
358
+ }
359
+ return { ...obj, items };
360
+ })
361
+ )
362
+ );
363
+ const signature = withValidate(
364
+ v.object({
365
+ ...createStandardFields("signature", "Sign this transaction or message to continue."),
366
+ typeUrl: v.picklist([
367
+ "fg:x:delegation",
368
+ "fg:t:transaction",
369
+ "mime:text/plain",
370
+ "mime:text/html",
371
+ "eth:transaction",
372
+ "eth:standard-data",
373
+ "eth:personal-data",
374
+ "eth:typed-data",
375
+ "eth:legacy-data"
376
+ ]),
377
+ display: v.optional(v.union([v.literal(""), v.string()]), ""),
378
+ method: v.optional(
379
+ v.picklist(["none", ...Object.keys(import_mcrypto.types.HashType).map((x) => x.toLowerCase())]),
380
+ "sha3"
381
+ ),
382
+ digest: v.optional(v.union([v.literal(""), v.string()]), ""),
383
+ origin: v.optional(v.union([v.literal(""), v.string()]), ""),
384
+ nonce: v.optional(v.union([v.literal(""), v.string()]), ""),
385
+ requirement: v.optional(requirementSchema)
386
+ })
387
+ );
388
+ const prepareTx = withValidate(
389
+ v.object({
390
+ ...createStandardFields("prepareTx", "Prepare and sign this transaction to continue."),
391
+ display: v.optional(v.union([v.literal(""), v.string()]), ""),
392
+ partialTx: v.string(),
393
+ nonce: v.optional(v.union([v.literal(""), v.string()]), ""),
394
+ requirement: requirementSchema
395
+ })
396
+ );
397
+ const agreement = withValidate(
398
+ v.object({
399
+ ...createStandardFields("agreement", "Confirm your agreement to continue."),
400
+ uri: v.union([
401
+ v.literal(""),
402
+ v.pipe(v.string(), v.check(isHttpUrl, "Must be a valid HTTP/HTTPS URL"))
403
+ ]),
404
+ method: v.optional(
405
+ v.picklist(Object.keys(import_mcrypto.types.HashType).map((x) => x.toLowerCase())),
406
+ "sha2"
407
+ ),
408
+ digest: v.string()
409
+ })
410
+ );
411
+ const vcFilterSchema = v.object({
412
+ type: v.optional(v.pipe(v.array(v.pipe(v.string(), v.minLength(1))), v.minLength(1))),
413
+ target: v.optional((0, import_validator.vDID)()),
414
+ trustedIssuers: v.optional(v.pipe(v.array(trustedIssuerSchema), v.minLength(1))),
415
+ tag: v.optional(v.pipe(v.string(), v.check((s) => s === "" || s.length >= 1, "Must be non-empty or empty string")), ""),
416
+ ownerDid: v.optional(v.array((0, import_validator.vDID)()), []),
417
+ claimUrl: optionalUrlSchema,
418
+ acquireUrl: optionalUrlSchema
419
+ });
420
+ const verifiableCredential = withValidate(
421
+ v.object({
422
+ ...createStandardFields("verifiableCredential", "Please present a verifiable credential to continue."),
423
+ optional: v.optional(v.boolean(), false),
424
+ claimUrl: optionalUrlSchema,
425
+ acquireUrl: optionalUrlSchema,
426
+ // v1
427
+ item: v.optional(v.pipe(v.array(v.pipe(v.string(), v.minLength(1))), v.minLength(1))),
428
+ target: v.optional((0, import_validator.vDID)()),
429
+ trustedIssuers: v.optional(v.pipe(v.array(trustedIssuerSchema), v.minLength(1))),
430
+ tag: v.optional(v.pipe(v.string(), v.check((s) => s === "" || s.length >= 1, "Must be non-empty or empty string")), ""),
431
+ ownerDid: v.optional(v.array((0, import_validator.vDID)()), []),
432
+ // v2
433
+ filters: v.optional(v.array(vcFilterSchema))
434
+ })
435
+ );
436
+ const assetFilterSchema = v.object({
437
+ address: v.optional((0, import_validator.vDID)()),
438
+ trustedIssuers: v.optional(v.pipe(v.array(trustedIssuerSchema), v.minLength(1))),
439
+ trustedParents: v.optional(v.pipe(v.array((0, import_validator.vDID)()), v.minLength(1))),
440
+ tag: v.optional(v.pipe(v.string(), v.check((s) => s === "" || s.length >= 1, "Must be non-empty or empty string")), ""),
441
+ ownerDid: v.optional(v.array((0, import_validator.vDID)()), []),
442
+ consumed: v.optional(v.boolean()),
443
+ acquireUrl: optionalUrlSchema
444
+ });
445
+ const asset = withValidate(
446
+ v.object({
447
+ ...createStandardFields("asset", "Please present an on chain asset to continue."),
448
+ optional: v.optional(v.boolean(), false),
449
+ // v1
450
+ address: v.optional((0, import_validator.vDID)()),
451
+ trustedIssuers: v.optional(v.pipe(v.array(trustedIssuerSchema), v.minLength(1))),
452
+ trustedParents: v.optional(v.pipe(v.array((0, import_validator.vDID)()), v.minLength(1))),
453
+ tag: v.optional(v.pipe(v.string(), v.check((s) => s === "" || s.length >= 1, "Must be non-empty or empty string")), ""),
454
+ ownerDid: v.optional(v.array((0, import_validator.vDID)()), []),
455
+ consumed: v.optional(v.boolean()),
456
+ acquireUrl: optionalUrlSchema,
457
+ // v2
458
+ filters: v.optional(v.array(assetFilterSchema))
459
+ })
460
+ );
461
+ const assetOrVCFilterSchema = v.object({
462
+ type: v.optional(v.pipe(v.array(v.pipe(v.string(), v.minLength(1))), v.minLength(1))),
463
+ address: v.optional((0, import_validator.vDID)()),
464
+ trustedIssuers: v.optional(v.pipe(v.array(trustedIssuerSchema), v.minLength(1))),
465
+ trustedParents: v.optional(v.pipe(v.array((0, import_validator.vDID)()), v.minLength(1))),
466
+ tag: v.optional(v.pipe(v.string(), v.check((s) => s === "" || s.length >= 1, "Must be non-empty or empty string")), ""),
467
+ ownerDid: v.optional(v.array((0, import_validator.vDID)()), []),
468
+ consumed: v.optional(v.boolean()),
469
+ claimUrl: optionalUrlSchema,
470
+ acquireUrl: optionalUrlSchema
471
+ });
472
+ const assetOrVC = withValidate(
473
+ v.object({
474
+ ...createStandardFields("assetOrVC", "Please present NFT to continue."),
475
+ filters: v.pipe(v.array(assetOrVCFilterSchema), v.minLength(1)),
476
+ optional: v.optional(v.boolean(), false)
477
+ })
478
+ );
479
+ return {
480
+ authPrincipal,
481
+ profile,
482
+ signature,
483
+ prepareTx,
484
+ agreement,
485
+ verifiableCredential,
486
+ asset,
487
+ assetOrVC,
488
+ keyPair,
489
+ encryptionKey
490
+ };
491
+ };
492
+
493
+ // src/schema/index.ts
494
+ var isHttpUrl2 = (s) => {
495
+ try {
496
+ const url2 = new URL(s);
497
+ return ["http:", "https:"].includes(url2.protocol);
498
+ } catch {
499
+ return false;
500
+ }
501
+ };
502
+ var chainInfoSchema = v2.pipe(
503
+ v2.object({
504
+ type: v2.optional(v2.picklist(["arcblock", "ethereum", "solona"]), "arcblock"),
505
+ id: v2.optional(v2.any()),
506
+ host: v2.optional(v2.any())
507
+ }),
508
+ v2.rawTransform(({ dataset, addIssue, NEVER }) => {
509
+ const obj = dataset.value;
510
+ let typeSchema;
511
+ switch (obj.type) {
512
+ case "arcblock":
513
+ typeSchema = v2.object({
514
+ type: v2.literal("arcblock"),
515
+ id: v2.optional(v2.string(), "none"),
516
+ host: v2.optional(
517
+ v2.pipe(
518
+ v2.string(),
519
+ v2.check(
520
+ (s) => s === "none" || isHttpUrl2(s),
521
+ "must be a valid uri with a scheme matching the http|https pattern"
522
+ )
523
+ ),
524
+ "none"
525
+ )
526
+ });
527
+ break;
528
+ case "ethereum":
529
+ typeSchema = v2.object({
530
+ type: v2.literal("ethereum"),
531
+ id: v2.pipe(v2.string(), v2.regex(/^[0-9]+$/, "fails to match the numbers pattern")),
532
+ host: v2.optional(v2.string())
533
+ });
534
+ break;
535
+ case "solona":
536
+ typeSchema = v2.object({
537
+ type: v2.literal("solona"),
538
+ id: v2.pipe(v2.string(), v2.regex(/^[0-9]+$/, "fails to match the numbers pattern")),
539
+ host: v2.optional(v2.string())
540
+ });
541
+ break;
542
+ }
543
+ const result = v2.safeParse(typeSchema, obj);
544
+ if (!result.success) {
545
+ for (const issue of result.issues) {
546
+ addIssue({ message: issue.message, path: issue.path });
547
+ }
548
+ return NEVER;
549
+ }
550
+ const output = {};
551
+ for (const [k, val] of Object.entries(result.output)) {
552
+ if (val !== void 0) output[k] = val;
553
+ }
554
+ return output;
555
+ })
556
+ );
557
+ var appInfoSchema = v2.looseObject({
558
+ name: v2.string(),
559
+ description: v2.string(),
560
+ icon: v2.pipe(v2.string(), v2.check(isHttpUrl2, "Must be a valid HTTP/HTTPS URL")),
561
+ link: v2.optional(v2.pipe(v2.string(), v2.check(isHttpUrl2, "Must be a valid HTTP/HTTPS URL"))),
562
+ path: v2.optional(v2.pipe(v2.string(), v2.check(isHttpUrl2, "Must be a valid HTTP/HTTPS URL")), "https://abtwallet.io/i/"),
563
+ publisher: v2.optional((0, import_validator2.vDID)()),
564
+ updateSubEndpoint: v2.optional(v2.boolean()),
565
+ subscriptionEndpoint: v2.optional(v2.string()),
566
+ nodeDid: v2.optional((0, import_validator2.vDID)()),
567
+ agentDid: v2.optional((0, import_validator2.vDID)())
568
+ });
569
+ function withValidate2(schema) {
570
+ return Object.assign(schema, {
571
+ validate: (data) => {
572
+ const { value, error } = (0, import_validator2.vValidate)(schema, data);
573
+ if (error) error.toString = () => error.message;
574
+ return { value, error };
575
+ },
576
+ validateAsync: (data) => {
577
+ const { value, error } = (0, import_validator2.vValidate)(schema, data);
578
+ return error ? Promise.reject(new Error(error.message)) : Promise.resolve(value);
579
+ }
580
+ });
581
+ }
582
+ var chainInfo = withValidate2(chainInfoSchema);
583
+ var appInfo = withValidate2(appInfoSchema);
584
+ var claims = claims_default(chainInfoSchema);
585
+
586
+ // src/authenticator/wallet.ts
587
+ var debug2 = (0, import_debug2.default)("@arcblock/did-connect-js:authenticator:wallet");
588
+ var DEFAULT_TIMEOUT = 8e3;
589
+ var MFA_CODE_COUNT = 3;
590
+ var WalletAuthenticator = class _WalletAuthenticator extends base_default {
591
+ wallet;
592
+ appInfo;
593
+ memberAppInfo;
594
+ chainInfo;
595
+ delegator;
596
+ delegation;
597
+ baseUrl;
598
+ tokenKey;
599
+ timeout;
600
+ txEncoder;
601
+ static formatDisplay(display) {
602
+ if (!display) {
603
+ return "";
604
+ }
605
+ if (display?.type && display.content) {
606
+ return JSON.stringify(pick(display, ["type", "content"]));
607
+ }
608
+ try {
609
+ const parsed = JSON.parse(display);
610
+ if (parsed?.type && parsed.content) {
611
+ return display;
612
+ }
613
+ return "";
614
+ } catch (_err) {
615
+ return "";
616
+ }
617
+ }
618
+ /**
619
+ * Creates an instance of DID Authenticator.
620
+ *
621
+ * @class
622
+ * @param {object} config
623
+ * @param {WalletObject|Function} config.wallet - wallet instance {@see @ocap/wallet} or a function that returns wallet instance
624
+ * @param {WalletObject|Function} [config.delegator] - the party that authorizes `wallet` to perform actions on behalf of `wallet`
625
+ * @param {string|Function} [config.delegation] - the jwt token that proves delegation relationship
626
+ * @param {ApplicationInfo|Function} config.appInfo - application basic info or a function that returns application info
627
+ * @param {ChainInfo|Function} config.chainInfo - application chain info or a function that returns chain info
628
+ * @param {Number} [config.timeout=8000] - timeout in milliseconds when generating claim
629
+ * @param {object} [config.baseUrl] - url to assemble wallet request uri, can be inferred from request object
630
+ * @param {string} [config.tokenKey='_t_'] - query param key for `token`
631
+ */
632
+ constructor({
633
+ wallet,
634
+ appInfo: appInfo2,
635
+ memberAppInfo,
636
+ delegator,
637
+ delegation,
638
+ timeout = DEFAULT_TIMEOUT,
639
+ chainInfo: chainInfo2 = DEFAULT_CHAIN_INFO,
640
+ baseUrl = "",
641
+ tokenKey = "_t_",
642
+ txEncoder
643
+ }) {
644
+ super();
645
+ this.wallet = this._validateWallet(wallet, false);
646
+ this.appInfo = this._validateAppInfo(appInfo2);
647
+ this.memberAppInfo = this._validateAppInfo(memberAppInfo, true);
648
+ this.chainInfo = chainInfo2;
649
+ this.delegator = delegator;
650
+ this.delegation = delegation;
651
+ this.baseUrl = baseUrl;
652
+ this.tokenKey = tokenKey;
653
+ this.timeout = timeout;
654
+ this.txEncoder = txEncoder;
655
+ if (!this.appInfo.link) {
656
+ this.appInfo.link = this.baseUrl;
657
+ }
658
+ }
659
+ /**
660
+ * Generate a deep link url that can be displayed as QRCode for DID Wallet to consume
661
+ *
662
+ * @method
663
+ * @param {object} params
664
+ * @param {string} params.baseUrl - baseUrl inferred from request object
665
+ * @param {string} params.pathname - wallet callback pathname
666
+ * @param {string} params.token - action token
667
+ * @param {object} params.query - params that should be persisted in wallet callback url
668
+ * @returns {string}
669
+ */
670
+ uri({
671
+ baseUrl,
672
+ pathname = "",
673
+ token = "",
674
+ query = {}
675
+ } = {}) {
676
+ const params = { ...query, [this.tokenKey]: token };
677
+ const payload = {
678
+ action: "requestAuth",
679
+ url: encodeURIComponent(`${this.baseUrl || baseUrl}${pathname}?${import_node_querystring.default.stringify(params)}`)
680
+ };
681
+ const uri = `https://abtwallet.io/i/?${import_node_querystring.default.stringify(payload)}`;
682
+ debug2("uri", { token, pathname, uri, params, payload });
683
+ return uri;
684
+ }
685
+ /**
686
+ * Compute public url to return to wallet
687
+ *
688
+ * @method
689
+ * @param {string} pathname
690
+ * @param {object} params
691
+ * @returns {string}
692
+ */
693
+ getPublicUrl(pathname, params = {}, baseUrl = "") {
694
+ return `${this.baseUrl || baseUrl}${pathname}?${import_node_querystring.default.stringify(params)}`;
695
+ }
696
+ /**
697
+ * Sign a plain response, usually on auth success or error
698
+ *
699
+ * @method
700
+ * @param {object} params
701
+ * @param {object} params.response - response
702
+ * @param {string} params.errorMessage - error message, default to empty
703
+ * @param {string} params.successMessage - success message, default to empty
704
+ * @param {string} params.nextWorkflow - https://github.com/ArcBlock/ABT-DID-Protocol#concatenate-multiple-workflow
705
+ * @param {string} params.nextUrl - tell wallet do open this url in webview
706
+ * @param {object} params.cookies - key-value pairs to be set as cookie before open nextUrl
707
+ * @param {object} params.storages - key-value pairs to be set as localStorage before open nextUrl
708
+ * @param {string} baseUrl
709
+ * @param {object} request
710
+ * @returns {Promise<object>} { appPk, agentPk, authInfo }
711
+ */
712
+ async signResponse({
713
+ response = {},
714
+ errorMessage = "",
715
+ successMessage = "",
716
+ nextWorkflow = "",
717
+ nextUrl = "",
718
+ cookies = {},
719
+ storages = {}
720
+ }, baseUrl, request, extraParams = {}) {
721
+ const context = request.context || {};
722
+ const infoParams = { baseUrl, request, ...context, extraParams };
723
+ const [wallet, delegator, delegation] = await Promise.all([
724
+ this.getWalletInfo(infoParams),
725
+ this.getDelegator(infoParams),
726
+ this.getDelegation(infoParams)
727
+ ]);
728
+ const [appInfo2, memberAppInfo] = await Promise.all([
729
+ this.getAppInfo({ ...infoParams, wallet, delegator }, "appInfo"),
730
+ this.getAppInfo({ ...infoParams, wallet, delegator }, "memberAppInfo")
731
+ ]);
732
+ const didwallet = request.context.wallet;
733
+ const payload = {
734
+ appInfo: appInfo2,
735
+ memberAppInfo,
736
+ status: errorMessage ? "error" : "ok",
737
+ errorMessage: errorMessage || "",
738
+ successMessage: successMessage || "",
739
+ nextWorkflow: nextWorkflow || "",
740
+ nextUrl: nextUrl || "",
741
+ cookies: cookies || {},
742
+ storages: storages || "",
743
+ response
744
+ };
745
+ if (delegator) {
746
+ payload.iss = (0, import_util.toDid)(delegator.address);
747
+ payload.agentDid = (0, import_util.toDid)(wallet.address);
748
+ payload.verifiableClaims = [{ type: "certificate", content: delegation }];
749
+ }
750
+ const result = {
751
+ appPk: (0, import_util.toBase58)(wallet.pk),
752
+ authInfo: await wallet.signJWT(payload, true, didwallet ? didwallet.jwt : void 0)
753
+ };
754
+ if (delegator) {
755
+ result.appPk = (0, import_util.toBase58)(delegator.pk);
756
+ result.agentPk = (0, import_util.toBase58)(wallet.pk);
757
+ }
758
+ return result;
759
+ }
760
+ /**
761
+ * Sign a auth response that returned to wallet: tell the wallet the appInfo/chainInfo
762
+ *
763
+ * @method
764
+ * @param {object} params
765
+ * @param {object} params.claims - info required by application to complete the auth
766
+ * @param {string} params.pathname - pathname to assemble callback url
767
+ * @param {string} params.baseUrl - baseUrl
768
+ * @param {object} params.challenge - random challenge to be included in the body
769
+ * @param {object} params.extraParams - extra query params and locale
770
+ * @param {object} params.request
771
+ * @param {object} params.context
772
+ * @param {string} params.context.token - action token
773
+ * @param {number} params.context.currentStep - current step
774
+ * @param {string} [params.context.sharedKey] - shared key between app and wallet
775
+ * @param {string} [params.context.encryptionKey] - encryption key from wallet
776
+ * @param {Function} [params.context.mfaCode] - function used to generate mfa code
777
+ * @param {string} params.context.userDid - decoded from req.query, base58
778
+ * @param {string} params.context.userPk - decoded from req.query, base58
779
+ * @param {string} params.context.didwallet - DID Wallet os and version
780
+ * @returns {Promise<object>} { appPk, agentPk, sharedKey, authInfo }
781
+ */
782
+ async sign({
783
+ context,
784
+ request,
785
+ claims: claims2,
786
+ pathname = "",
787
+ baseUrl = "",
788
+ challenge = "",
789
+ extraParams = {}
790
+ }) {
791
+ const claimsInfo = await this.tryWithTimeout(
792
+ () => this.genRequestedClaims({
793
+ claims: claims2,
794
+ context: { baseUrl, request, ...context },
795
+ extraParams
796
+ })
797
+ );
798
+ if (claimsInfo.filter((x) => x.mfaCode && x.mfaCode.length > 0).length > 1) {
799
+ throw new Error("Multiple MFA is not supported when sending more than 1 claim");
800
+ }
801
+ const tmp = claimsInfo.find((x) => isEqual(this._isValidChainInfo(x.chainInfo), DEFAULT_CHAIN_INFO) === false);
802
+ const infoParams = { baseUrl, request, ...context, extraParams };
803
+ const [wallet, delegator, delegation, chainInfo2] = await Promise.all([
804
+ this.getWalletInfo(infoParams),
805
+ this.getDelegator(infoParams),
806
+ this.getDelegation(infoParams),
807
+ this.getChainInfo(infoParams, tmp?.chainInfo)
808
+ ]);
809
+ const [appInfo2, memberAppInfo] = await Promise.all([
810
+ this.getAppInfo({ ...infoParams, wallet, delegator }, "appInfo"),
811
+ this.getAppInfo({ ...infoParams, wallet, delegator }, "memberAppInfo")
812
+ ]);
813
+ const payload = {
814
+ action: "responseAuth",
815
+ challenge,
816
+ appInfo: appInfo2,
817
+ memberAppInfo,
818
+ chainInfo: chainInfo2,
819
+ requestedClaims: claimsInfo.map((x) => {
820
+ delete x.chainInfo;
821
+ return x;
822
+ }),
823
+ url: `${this.baseUrl || baseUrl}${pathname}?${import_node_querystring.default.stringify({ [this.tokenKey]: context.token })}`
824
+ };
825
+ if (delegator) {
826
+ payload.iss = (0, import_util.toDid)(delegator.address);
827
+ payload.agentDid = (0, import_util.toDid)(wallet.address);
828
+ payload.verifiableClaims = [{ type: "certificate", content: delegation }];
829
+ }
830
+ const version = context.didwallet ? context.didwallet.jwt : void 0;
831
+ const result = {
832
+ appPk: (0, import_util.toBase58)(wallet.pk),
833
+ authInfo: await wallet.signJWT(payload, true, version),
834
+ sensitive: claimsInfo.every((x) => ["keyPair", "encryptionKey"].includes(x.type))
835
+ };
836
+ if (result.sensitive && context.sharedKey && context.encryptionKey) {
837
+ try {
838
+ const pk = (0, import_util.fromBase58)(context.encryptionKey).toString("utf8");
839
+ result.sharedKey = import_rsa.default.encrypt(context.sharedKey, pk, "base58");
840
+ } catch (err) {
841
+ console.error("Failed to encrypt shared key", err);
842
+ }
843
+ }
844
+ if (delegator) {
845
+ result.appPk = (0, import_util.toBase58)(delegator.pk);
846
+ result.agentPk = (0, import_util.toBase58)(wallet.pk);
847
+ }
848
+ return result;
849
+ }
850
+ /**
851
+ * Determine chainInfo on the fly
852
+ *
853
+ * @param {object} params - contains the context of this request
854
+ * @param {object|undefined} [info=undefined] - chain info object or function
855
+ * @returns {Promise<ChainInfo>}
856
+ * @memberof WalletAuthenticator
857
+ */
858
+ async getChainInfo(params, info) {
859
+ if (info && this._isValidChainInfo(info)) {
860
+ return info;
861
+ }
862
+ if (typeof this.chainInfo === "function") {
863
+ const result = await this.tryWithTimeout(() => this.chainInfo(params));
864
+ if (this._isValidChainInfo(result)) {
865
+ return result;
866
+ }
867
+ }
868
+ if (this.chainInfo && this._isValidChainInfo(this.chainInfo)) {
869
+ return this.chainInfo;
870
+ }
871
+ return DEFAULT_CHAIN_INFO;
872
+ }
873
+ /**
874
+ * Determine appInfo/memberAppInfo on the fly
875
+ *
876
+ * @param {object} params - contains the context of this request
877
+ * @param {string} key - appInfo | memberAppInfo
878
+ * @returns {Promise<ApplicationInfo>}
879
+ * @memberof WalletAuthenticator
880
+ */
881
+ async getAppInfo(params, key = "appInfo") {
882
+ if (typeof this[key] === "function") {
883
+ const info = await this.tryWithTimeout(() => this[key](params));
884
+ if (info) {
885
+ if (!info.link) {
886
+ info.link = params.baseUrl;
887
+ }
888
+ if (!info.publisher) {
889
+ info.publisher = (0, import_util.toDid)(params.delegator ? params.delegator.address : params.wallet.address);
890
+ }
891
+ }
892
+ return this._validateAppInfo(info, key === "memberAppInfo");
893
+ }
894
+ if (this[key] && !this[key].publisher) {
895
+ this[key].publisher = (0, import_util.toDid)(params.delegator ? params.delegator.address : params.wallet.address);
896
+ }
897
+ return this[key];
898
+ }
899
+ async getWalletInfo(params) {
900
+ if (typeof this.wallet === "function") {
901
+ const result = await this.tryWithTimeout(() => this.wallet(params));
902
+ return this._validateWallet(result, false);
903
+ }
904
+ return this.wallet;
905
+ }
906
+ async getDelegator(params) {
907
+ if (typeof this.delegator === "function") {
908
+ const result = await this.tryWithTimeout(() => this.delegator(params));
909
+ return result ? this._validateWallet(result, false) : null;
910
+ }
911
+ return this.delegator;
912
+ }
913
+ async getDelegation(params) {
914
+ if (typeof this.delegation === "function") {
915
+ const result = await this.tryWithTimeout(() => this.delegation(params));
916
+ return result;
917
+ }
918
+ return this.delegation;
919
+ }
920
+ /**
921
+ * Verify a DID auth response sent from DID Wallet
922
+ *
923
+ * @method
924
+ * @param {object} data
925
+ * @param {string} [locale=en]
926
+ * @param {boolean} [enforceTimestamp=true]
927
+ * @returns Promise<boolean>
928
+ */
929
+ async verify(data, locale = "en", enforceTimestamp = true) {
930
+ const {
931
+ iss,
932
+ iat,
933
+ challenge = "",
934
+ action = "responseAuth",
935
+ requestedClaims
936
+ } = await this._verify(data, "userPk", "userInfo", locale, enforceTimestamp);
937
+ debug2("verify.context", { userPk: data.userPk, userDid: (0, import_did.toAddress)(iss), action, challenge });
938
+ debug2("verify.claims", requestedClaims);
939
+ return {
940
+ token: data.token,
941
+ userDid: (0, import_did.toAddress)(iss),
942
+ userPk: data.userPk,
943
+ claims: requestedClaims,
944
+ action,
945
+ challenge,
946
+ timestamp: iat
947
+ };
948
+ }
949
+ // ---------------------------------------
950
+ // Request claim related methods
951
+ // ---------------------------------------
952
+ genRequestedClaims({
953
+ claims: claims2,
954
+ context,
955
+ extraParams
956
+ }) {
957
+ return Promise.all(
958
+ Object.keys(claims2).map(async (x) => {
959
+ let name = x;
960
+ let claim = claims2[x];
961
+ if (Array.isArray(claims2[x])) {
962
+ [name, claim] = claims2[x];
963
+ }
964
+ if (!claims[name]) {
965
+ throw new Error(`Unsupported claim type ${name}`);
966
+ }
967
+ const fn = typeof this[name] === "function" ? name : "getClaimInfo";
968
+ const result = await this[fn]({ claim, context, extraParams });
969
+ if (result.mfa && typeof context.mfaCode === "function") {
970
+ result.mfaCode = [await context.mfaCode()];
971
+ while (result.mfaCode.length < MFA_CODE_COUNT) {
972
+ const noise = random(10, 99);
973
+ if (result.mfaCode.includes(noise) === false) {
974
+ result.mfaCode.push(noise);
975
+ }
976
+ }
977
+ result.mfaCode = shuffle(result.mfaCode);
978
+ }
979
+ const { value, error } = claims[name].validate(result);
980
+ if (error) {
981
+ throw new Error(`Invalid ${name} claim: ${error.message}`);
982
+ }
983
+ return value;
984
+ })
985
+ );
986
+ }
987
+ async getClaimInfo({
988
+ claim,
989
+ context,
990
+ extraParams
991
+ }) {
992
+ const { userDid, userPk, didwallet } = context;
993
+ const result = typeof claim === "function" ? await claim({
994
+ userDid: userDid ? (0, import_did.toAddress)(userDid) : "",
995
+ userPk: userPk || "",
996
+ didwallet,
997
+ extraParams,
998
+ context
999
+ }) : claim;
1000
+ const infoParams = { ...context, ...extraParams };
1001
+ const chainInfo2 = await this.getChainInfo(infoParams, result.chainInfo);
1002
+ result.chainInfo = chainInfo2;
1003
+ return result;
1004
+ }
1005
+ // Request wallet to sign something: transaction/text/html/image
1006
+ async signature({
1007
+ claim,
1008
+ context,
1009
+ extraParams
1010
+ }) {
1011
+ const {
1012
+ data,
1013
+ type = "mime:text/plain",
1014
+ digest = "",
1015
+ method = "sha3",
1016
+ // set this to `none` to instruct wallet not to hash before signing
1017
+ wallet,
1018
+ sender,
1019
+ display,
1020
+ description: desc,
1021
+ chainInfo: chainInfo2,
1022
+ meta = {},
1023
+ mfa = false,
1024
+ nonce = "",
1025
+ requirement = { tokens: [], assets: {} }
1026
+ } = await this.getClaimInfo({
1027
+ claim,
1028
+ context,
1029
+ extraParams
1030
+ });
1031
+ debug2("claim.signature", { data, digest, type, sender, context, nonce, requirement });
1032
+ if (!data && !digest) {
1033
+ throw new Error("Signature claim requires either data or digest to be provided");
1034
+ }
1035
+ const description = desc || "Sign this transaction to continue.";
1036
+ if (type.endsWith("Tx")) {
1037
+ if (!chainInfo2.host) {
1038
+ throw new Error("Invalid chainInfo when trying to encoding transaction");
1039
+ }
1040
+ if (!this.txEncoder) {
1041
+ throw new Error(
1042
+ `Transaction encoding for "${type}" requires a txEncoder. Pass txEncoder to WalletAuthenticator or install @ocap/client.`
1043
+ );
1044
+ }
1045
+ if (!data.pk) {
1046
+ data.pk = context.userPk;
1047
+ }
1048
+ try {
1049
+ const txBuffer = await this.txEncoder({
1050
+ type,
1051
+ data,
1052
+ wallet: wallet || (0, import_wallet2.fromAddress)(sender || context.userDid),
1053
+ chainHost: chainInfo2.host
1054
+ });
1055
+ return {
1056
+ type: "signature",
1057
+ description,
1058
+ typeUrl: "fg:t:transaction",
1059
+ origin: (0, import_util.toBase58)(txBuffer),
1060
+ method,
1061
+ display: _WalletAuthenticator.formatDisplay(display),
1062
+ digest: "",
1063
+ chainInfo: chainInfo2,
1064
+ meta,
1065
+ mfa,
1066
+ nonce,
1067
+ requirement
1068
+ };
1069
+ } catch (err) {
1070
+ throw new Error(`Failed to encode transaction: ${err.message}`);
1071
+ }
1072
+ }
1073
+ if (type === "fg:t:transaction") {
1074
+ return {
1075
+ type: "signature",
1076
+ description,
1077
+ typeUrl: "fg:t:transaction",
1078
+ origin: (0, import_util.toBase58)(data),
1079
+ display: _WalletAuthenticator.formatDisplay(display),
1080
+ method,
1081
+ digest: "",
1082
+ chainInfo: chainInfo2,
1083
+ meta,
1084
+ mfa,
1085
+ nonce,
1086
+ requirement
1087
+ };
1088
+ }
1089
+ return {
1090
+ type: "signature",
1091
+ description: desc || "Sign this message to continue.",
1092
+ origin: data ? (0, import_util.toBase58)(data) : "",
1093
+ typeUrl: type,
1094
+ display: _WalletAuthenticator.formatDisplay(display),
1095
+ method,
1096
+ digest,
1097
+ chainInfo: chainInfo2,
1098
+ meta,
1099
+ mfa,
1100
+ nonce,
1101
+ requirement
1102
+ };
1103
+ }
1104
+ // Request wallet to complete and sign a partial tx to broadcasting
1105
+ // Usually used in payment scenarios
1106
+ // The wallet can leverage multiple input capabilities of the chain
1107
+ async prepareTx({
1108
+ claim,
1109
+ context,
1110
+ extraParams
1111
+ }) {
1112
+ const {
1113
+ partialTx,
1114
+ requirement = { tokens: [], assets: {} },
1115
+ type,
1116
+ display,
1117
+ wallet,
1118
+ sender,
1119
+ description: desc,
1120
+ chainInfo: chainInfo2,
1121
+ meta = {},
1122
+ mfa = false,
1123
+ nonce = ""
1124
+ } = await this.getClaimInfo({
1125
+ claim,
1126
+ context,
1127
+ extraParams
1128
+ });
1129
+ debug2("claim.prepareTx", { partialTx, requirement, type, sender, context });
1130
+ if (!partialTx || !requirement) {
1131
+ throw new Error("prepareTx claim requires both partialTx and requirement to be provided");
1132
+ }
1133
+ const description = desc || "Prepare and sign this transaction to continue.";
1134
+ if (type?.endsWith("Tx")) {
1135
+ if (!chainInfo2.host) {
1136
+ throw new Error("Invalid chainInfo when trying to encoding partial transaction");
1137
+ }
1138
+ if (!this.txEncoder) {
1139
+ throw new Error(
1140
+ `Transaction encoding for "${type}" requires a txEncoder. Pass txEncoder to WalletAuthenticator or install @ocap/client.`
1141
+ );
1142
+ }
1143
+ if (!partialTx.pk) {
1144
+ partialTx.pk = context.userPk;
1145
+ }
1146
+ try {
1147
+ const txBuffer = await this.txEncoder({
1148
+ type,
1149
+ data: partialTx,
1150
+ wallet: wallet || (0, import_wallet2.fromAddress)(sender || context.userDid),
1151
+ chainHost: chainInfo2.host
1152
+ });
1153
+ return {
1154
+ type: "prepareTx",
1155
+ description,
1156
+ partialTx: (0, import_util.toBase58)(txBuffer),
1157
+ display: _WalletAuthenticator.formatDisplay(display),
1158
+ requirement,
1159
+ chainInfo: chainInfo2,
1160
+ meta,
1161
+ mfa,
1162
+ nonce
1163
+ };
1164
+ } catch (err) {
1165
+ throw new Error(`Failed to encode partial transaction: ${err.message}`);
1166
+ }
1167
+ }
1168
+ return {
1169
+ type: "prepareTx",
1170
+ description,
1171
+ partialTx: (0, import_util.toBase58)(partialTx),
1172
+ requirement,
1173
+ display: _WalletAuthenticator.formatDisplay(display),
1174
+ chainInfo: chainInfo2,
1175
+ meta,
1176
+ mfa,
1177
+ nonce
1178
+ };
1179
+ }
1180
+ _validateAppInfo(info, allowEmpty = false) {
1181
+ if (typeof info === "function") {
1182
+ return info;
1183
+ }
1184
+ if (!info) {
1185
+ if (allowEmpty === false) {
1186
+ throw new Error("Wallet authenticator can not work with invalid appInfo: empty");
1187
+ }
1188
+ return null;
1189
+ }
1190
+ const { value, error } = appInfo.validate(info);
1191
+ if (error) {
1192
+ throw new Error(`Wallet authenticator can not work with invalid appInfo: ${error.message}`);
1193
+ }
1194
+ return value;
1195
+ }
1196
+ _isValidChainInfo(x) {
1197
+ const { error } = chainInfo.validate(x);
1198
+ return !error;
1199
+ }
1200
+ tryWithTimeout(asyncFn, label = "") {
1201
+ if (typeof asyncFn !== "function") {
1202
+ throw new Error("asyncFn must be a valid function when calling tryWithTimeout");
1203
+ }
1204
+ const timeout = Number(this.timeout) || DEFAULT_TIMEOUT;
1205
+ const inferredLabel = label || asyncFn.name || asyncFn.toString();
1206
+ const invocationStack = new Error(`Timeout at: ${inferredLabel}`).stack;
1207
+ return new Promise(async (resolve, reject) => {
1208
+ const timer = setTimeout(() => {
1209
+ const error = new Error(`Async operation (${inferredLabel}) did not complete within ${timeout} ms`);
1210
+ error.stack = invocationStack;
1211
+ error.name = "TIMEOUT";
1212
+ reject(error);
1213
+ }, timeout);
1214
+ try {
1215
+ const result = await asyncFn();
1216
+ resolve(result);
1217
+ } catch (err) {
1218
+ reject(err);
1219
+ } finally {
1220
+ clearTimeout(timer);
1221
+ }
1222
+ });
1223
+ }
1224
+ };
1225
+ var wallet_default = WalletAuthenticator;
1226
+
1227
+ // src/handlers/wallet.ts
1228
+ var import_debug4 = __toESM(require("debug"), 1);
1229
+
1230
+ // src/adapters/detect.ts
1231
+ function isExpressApp(app) {
1232
+ return typeof app?.set === "function" && typeof app?.engine === "function";
1233
+ }
1234
+ function isHonoApp(app) {
1235
+ return typeof app?.fetch === "function" && !isExpressApp(app);
1236
+ }
1237
+
1238
+ // src/adapters/express.ts
1239
+ function attachExpress({ app, prefix, action, handlers }) {
1240
+ const pathname = `${prefix}/${action}/auth`;
1241
+ const {
1242
+ generateSession,
1243
+ checkSession,
1244
+ expireSession,
1245
+ onAuthRequest,
1246
+ onAuthResponse,
1247
+ ensureContext,
1248
+ ensureSignedJson
1249
+ } = handlers;
1250
+ app.get(`${prefix}/${action}/token`, generateSession);
1251
+ app.post(`${prefix}/${action}/token`, generateSession);
1252
+ app.get(`${prefix}/${action}/status`, ensureContext, checkSession);
1253
+ app.get(`${prefix}/${action}/timeout`, ensureContext, expireSession);
1254
+ app.get(pathname, ensureContext, ensureSignedJson, onAuthRequest);
1255
+ app.post(pathname, ensureContext, ensureSignedJson, onAuthResponse);
1256
+ app.get(`${pathname}/submit`, ensureContext, ensureSignedJson, onAuthResponse);
1257
+ return { generateSession, expireSession, checkSession, onAuthRequest, onAuthResponse };
1258
+ }
1259
+
1260
+ // src/adapters/hono.ts
1261
+ function parseCookieHeader(header) {
1262
+ const cookies = {};
1263
+ if (!header) return cookies;
1264
+ for (const pair of header.split(";")) {
1265
+ const idx = pair.indexOf("=");
1266
+ if (idx > 0) {
1267
+ const key = pair.slice(0, idx).trim();
1268
+ const val = pair.slice(idx + 1).trim();
1269
+ cookies[key] = decodeURIComponent(val);
1270
+ }
1271
+ }
1272
+ return cookies;
1273
+ }
1274
+ function negotiateLanguage(acceptHeader, ...langs) {
1275
+ if (!acceptHeader || langs.length === 0) return langs[0] || false;
1276
+ const lower = acceptHeader.toLowerCase();
1277
+ for (const lang of langs) {
1278
+ const prefix = lang.toLowerCase().split("-")[0];
1279
+ if (lower.includes(lang.toLowerCase()) || lower.includes(prefix)) {
1280
+ return lang;
1281
+ }
1282
+ }
1283
+ return langs[0] || false;
1284
+ }
1285
+ function createHonoRequest(c, bodyCache = {}) {
1286
+ const url2 = new URL(c.req.url);
1287
+ const rawHeaders = {};
1288
+ if (c.req.raw?.headers) {
1289
+ c.req.raw.headers.forEach((value, key) => {
1290
+ rawHeaders[key.toLowerCase()] = value;
1291
+ });
1292
+ }
1293
+ return {
1294
+ body: bodyCache,
1295
+ query: Object.fromEntries(url2.searchParams.entries()),
1296
+ params: typeof c.req.param === "function" ? c.req.param() : {},
1297
+ headers: rawHeaders,
1298
+ cookies: parseCookieHeader(rawHeaders.cookie || ""),
1299
+ protocol: url2.protocol.replace(":", ""),
1300
+ originalUrl: url2.pathname + url2.search,
1301
+ get(name) {
1302
+ return rawHeaders[name.toLowerCase()];
1303
+ },
1304
+ acceptsLanguages(...langs) {
1305
+ return negotiateLanguage(rawHeaders["accept-language"] || "", ...langs);
1306
+ },
1307
+ raw: c
1308
+ };
1309
+ }
1310
+ function createHonoResponse() {
1311
+ let result = null;
1312
+ const response = {
1313
+ jsonp(data) {
1314
+ result = { statusCode: 200, body: data };
1315
+ },
1316
+ json(data) {
1317
+ result = { statusCode: 200, body: data };
1318
+ },
1319
+ status(code) {
1320
+ return {
1321
+ json(data) {
1322
+ result = { statusCode: code, body: data };
1323
+ }
1324
+ };
1325
+ },
1326
+ _getResult() {
1327
+ return result;
1328
+ }
1329
+ };
1330
+ return response;
1331
+ }
1332
+ function wrapHandler(middlewares, handler) {
1333
+ return async (c) => {
1334
+ let body = {};
1335
+ try {
1336
+ const text = await c.req.text();
1337
+ if (text) {
1338
+ const contentType = c.req.header("content-type") || "";
1339
+ if (contentType.includes("json")) {
1340
+ body = JSON.parse(text);
1341
+ } else if (contentType.includes("urlencoded")) {
1342
+ body = Object.fromEntries(new URLSearchParams(text).entries());
1343
+ }
1344
+ }
1345
+ } catch {
1346
+ }
1347
+ const req = createHonoRequest(c, body);
1348
+ const res = createHonoResponse();
1349
+ for (const mw of middlewares) {
1350
+ let nextCalled = false;
1351
+ await mw(req, res, () => {
1352
+ nextCalled = true;
1353
+ });
1354
+ if (!nextCalled) {
1355
+ const r2 = res._getResult();
1356
+ if (r2) return c.json(r2.body, r2.statusCode);
1357
+ return c.json({ error: "Unknown error" }, 500);
1358
+ }
1359
+ }
1360
+ await handler(req, res);
1361
+ const r = res._getResult();
1362
+ return c.json(r?.body ?? {}, r?.statusCode ?? 200);
1363
+ };
1364
+ }
1365
+ function attachHono({ app, prefix, action, handlers }) {
1366
+ const pathname = `${prefix}/${action}/auth`;
1367
+ const {
1368
+ generateSession,
1369
+ checkSession,
1370
+ expireSession,
1371
+ onAuthRequest,
1372
+ onAuthResponse,
1373
+ ensureContext,
1374
+ ensureSignedJson
1375
+ } = handlers;
1376
+ app.use(`${prefix}/${action}/*`, async (c, next) => {
1377
+ c.header("Access-Control-Allow-Origin", "*");
1378
+ c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1379
+ c.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
1380
+ if (c.req.method === "OPTIONS") {
1381
+ return c.text("", 204);
1382
+ }
1383
+ await next();
1384
+ });
1385
+ app.get(`${prefix}/${action}/token`, wrapHandler([], generateSession));
1386
+ app.post(`${prefix}/${action}/token`, wrapHandler([], generateSession));
1387
+ app.get(`${prefix}/${action}/status`, wrapHandler([ensureContext], checkSession));
1388
+ app.get(`${prefix}/${action}/timeout`, wrapHandler([ensureContext], expireSession));
1389
+ app.get(pathname, wrapHandler([ensureContext, ensureSignedJson], onAuthRequest));
1390
+ app.post(pathname, wrapHandler([ensureContext, ensureSignedJson], onAuthResponse));
1391
+ app.get(`${pathname}/submit`, wrapHandler([ensureContext, ensureSignedJson], onAuthResponse));
1392
+ return { generateSession, expireSession, checkSession, onAuthRequest, onAuthResponse };
1393
+ }
1394
+
1395
+ // src/handlers/base.ts
1396
+ var import_node_events = require("node:events");
1397
+
1398
+ // src/protocol.ts
1399
+ var import_jwt = require("@arcblock/jwt");
1400
+ var import_aes = __toESM(require("@ocap/mcrypto/lib/crypter/aes"), 1);
1401
+ var import_util2 = require("@ocap/util");
1402
+ var import_did_connect_core = require("@arcblock/did-connect-core");
1403
+ var VERSION = "1.0.0";
1404
+ var decrypt = (data, config = {}, dataKey = "userInfo") => {
1405
+ try {
1406
+ (0, import_jwt.decode)(data[dataKey]);
1407
+ return data;
1408
+ } catch {
1409
+ }
1410
+ if (config.sharedKey && data.version === VERSION) {
1411
+ data[dataKey] = import_aes.default.decrypt((0, import_util2.fromBase58)(data[dataKey]), config.sharedKey, "buffer").toString("utf8");
1412
+ }
1413
+ return data;
1414
+ };
1415
+ var encrypt = (data, config = {}, dataKey = "authInfo") => {
1416
+ const { clientVersion, sharedKey } = config || {};
1417
+ if (data.sensitive && sharedKey && clientVersion === VERSION) {
1418
+ data.version = VERSION;
1419
+ data[dataKey] = import_aes.default.encrypt(data[dataKey], sharedKey, "base58");
1420
+ }
1421
+ delete data.sensitive;
1422
+ return data;
1423
+ };
1424
+ var PROTECTED_KEYS = ["challenge", "nonce", "sharedKey", "encryptionKey"];
1425
+ var SESSION_STATUS = {
1426
+ CREATED: "created",
1427
+ SUCCEED: "succeed",
1428
+ ERROR: "error",
1429
+ BUSY: "busy",
1430
+ SCANNED: "scanned",
1431
+ FORBIDDEN: "forbidden"
1432
+ };
1433
+
1434
+ // src/handlers/base.ts
1435
+ var BaseHandler = class extends import_node_events.EventEmitter {
1436
+ authenticator;
1437
+ tokenStorage;
1438
+ pathTransformer;
1439
+ onConnect;
1440
+ /**
1441
+ * Creates an instance of DID Auth Handlers.
1442
+ *
1443
+ * @class
1444
+ * @param {object} config
1445
+ * @param {function} config.pathTransformer - function to transform path when generate action;
1446
+ * @param {object} config.tokenStorage - function to generate action token
1447
+ * @param {object} config.authenticator - Authenticator instance that can to jwt sign/verify
1448
+ * @param {function} [config.onConnect=noop] - function called when wallet selected did
1449
+ */
1450
+ constructor({
1451
+ pathTransformer,
1452
+ tokenStorage,
1453
+ authenticator,
1454
+ onConnect
1455
+ }) {
1456
+ super();
1457
+ this.authenticator = authenticator;
1458
+ this.tokenStorage = tokenStorage;
1459
+ this.tokenStorage.on("create", (data) => this.emit("created", data));
1460
+ this.tokenStorage.on("destroy", (token) => this.emit("deleted", { token }));
1461
+ this.tokenStorage.on("update", async (data) => {
1462
+ if (!data) {
1463
+ return;
1464
+ }
1465
+ const payload = await this.tokenStorage.read(data.token);
1466
+ this.emit("updated", omit(payload, PROTECTED_KEYS));
1467
+ });
1468
+ if (typeof pathTransformer === "function") {
1469
+ this.pathTransformer = pathTransformer;
1470
+ } else {
1471
+ this.pathTransformer = (v3) => v3;
1472
+ }
1473
+ if (typeof onConnect === "function") {
1474
+ this.onConnect = onConnect;
1475
+ } else {
1476
+ this.onConnect = () => {
1477
+ };
1478
+ }
1479
+ }
1480
+ };
1481
+ var base_default2 = BaseHandler;
1482
+
1483
+ // src/handlers/util.ts
1484
+ var import_did2 = require("@arcblock/did");
1485
+ var Mcrypto = __toESM(require("@ocap/mcrypto"), 1);
1486
+ var import_util3 = require("@ocap/util");
1487
+ var import_fast_json_stable_stringify = __toESM(require("fast-json-stable-stringify"), 1);
1488
+ var import_url = __toESM(require("url"), 1);
1489
+
1490
+ // src/utils/sealed-box.ts
1491
+ var import_salsa = require("@noble/ciphers/salsa.js");
1492
+ var import_ed25519 = require("@noble/curves/ed25519.js");
1493
+ var import_blake2 = require("@noble/hashes/blake2.js");
1494
+ var import_utils = require("@noble/hashes/utils.js");
1495
+ var SIGMA = new Uint32Array([1634760805, 857760878, 2036477234, 1797285236]);
1496
+ function cryptoBoxBeforenm(sharedSecret) {
1497
+ const k = new Uint32Array(sharedSecret.buffer, sharedSecret.byteOffset, 8);
1498
+ const i = new Uint32Array(4);
1499
+ const out = new Uint32Array(8);
1500
+ (0, import_salsa.hsalsa)(SIGMA, k, i, out);
1501
+ return new Uint8Array(out.buffer);
1502
+ }
1503
+ function seal(message, recipientPk) {
1504
+ const ephSk = import_ed25519.x25519.utils.randomSecretKey();
1505
+ const ephPk = import_ed25519.x25519.getPublicKey(ephSk);
1506
+ const nonce = (0, import_blake2.blake2b)((0, import_utils.concatBytes)(ephPk, recipientPk), { dkLen: 24 });
1507
+ const key = cryptoBoxBeforenm(import_ed25519.x25519.getSharedSecret(ephSk, recipientPk));
1508
+ const encrypted = (0, import_salsa.xsalsa20poly1305)(key, nonce).encrypt(message);
1509
+ return (0, import_utils.concatBytes)(ephPk, encrypted);
1510
+ }
1511
+
1512
+ // src/handlers/util.ts
1513
+ var import_debug3 = __toESM(require("debug"), 1);
1514
+ var ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/;
1515
+ var WINDOWS_PATH_REGEX = /^[a-zA-Z]:\\/;
1516
+ var isUrl = (input) => {
1517
+ if (typeof input !== "string") {
1518
+ return false;
1519
+ }
1520
+ if (WINDOWS_PATH_REGEX.test(input)) {
1521
+ return false;
1522
+ }
1523
+ return ABSOLUTE_URL_REGEX.test(input);
1524
+ };
1525
+ var _debug = (0, import_debug3.default)("@arcblock/did-connect-js:handlers:util");
1526
+ var sha3 = Mcrypto.Hasher.SHA3.hash256;
1527
+ var SUPPORTED_LOCALES = ["en", "zh"];
1528
+ function normalizeLocale(value) {
1529
+ const raw = Array.isArray(value) ? value[0] : value;
1530
+ if (typeof raw !== "string") return void 0;
1531
+ const base = raw.trim().toLowerCase().replace("_", "-").split("-")[0];
1532
+ return SUPPORTED_LOCALES.includes(base) ? base : void 0;
1533
+ }
1534
+ var getLocale = (req) => {
1535
+ const queryLocale = normalizeLocale(req.query.locale);
1536
+ if (queryLocale) return queryLocale;
1537
+ const cookieLocale = normalizeLocale(req.cookies?.arc_lang);
1538
+ if (cookieLocale) return cookieLocale;
1539
+ const acceptedLocale = normalizeLocale(req.acceptsLanguages("en-US", "zh-CN"));
1540
+ return acceptedLocale || "en";
1541
+ };
1542
+ var getSessionId = () => Date.now().toString();
1543
+ var noop = () => ({});
1544
+ var noTouch = (x) => x;
1545
+ var errors = {
1546
+ tokenMissing: {
1547
+ en: "Session Id is required to check status",
1548
+ zh: "\u7F3A\u5C11\u4F1A\u8BDD ID \u53C2\u6570"
1549
+ },
1550
+ didMismatch: {
1551
+ en: "Login user and wallet user mismatch, please relogin and try again",
1552
+ zh: "\u767B\u5F55\u7528\u6237\u548C\u626B\u7801\u7528\u6237\u4E0D\u5339\u914D\uFF0C\u4E3A\u4FDD\u969C\u5B89\u5168\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u5E94\u7528"
1553
+ },
1554
+ mfaMismatch: {
1555
+ en: "Dynamic verification code mismatch, please try again later",
1556
+ zh: "\u52A8\u6001\u9A8C\u8BC1\u7801\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u8BD5"
1557
+ },
1558
+ challengeMismatch: {
1559
+ en: "Challenge mismatch",
1560
+ zh: "\u968F\u673A\u6821\u9A8C\u7801\u4E0D\u5339\u914D"
1561
+ },
1562
+ token404: {
1563
+ en: "Session not found or expired",
1564
+ zh: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F"
1565
+ },
1566
+ didMissing: {
1567
+ en: "userDid is required to start auth",
1568
+ zh: "userDid \u53C2\u6570\u7F3A\u5931\uFF0C\u8BF7\u52FF\u5C1D\u8BD5\u8FDE\u63A5\u591A\u4E2A\u4E0D\u540C\u7684\u94B1\u5305"
1569
+ },
1570
+ pkMissing: {
1571
+ en: "userPk is required to start auth",
1572
+ zh: "userPk \u53C2\u6570\u7F3A\u5931\uFF0C\u8BF7\u52FF\u5C1D\u8BD5\u8FDE\u63A5\u591A\u4E2A\u4E0D\u540C\u7684\u94B1\u5305"
1573
+ },
1574
+ authClaim: {
1575
+ en: "authPrincipal claim is not configured correctly",
1576
+ zh: "authPrincipal \u58F0\u660E\u914D\u7F6E\u4E0D\u6B63\u786E"
1577
+ },
1578
+ userDeclined: {
1579
+ en: "You have declined the authentication request",
1580
+ zh: "\u6388\u6743\u8BF7\u6C42\u88AB\u62D2\u7EDD"
1581
+ },
1582
+ userBusy: {
1583
+ en: "Busy processing another DID Connect request",
1584
+ zh: "\u6B63\u5728\u5904\u7406\u5176\u4ED6\u8BF7\u6C42"
1585
+ }
1586
+ };
1587
+ var preparePathname = (path, req) => {
1588
+ const delimiter = path.replace(/\/retrieve$/, "").replace(/\/auth$/, "");
1589
+ const fullPath = import_url.default.parse(req.originalUrl).pathname;
1590
+ const [prefix] = fullPath.split(delimiter);
1591
+ const cleanPath = [prefix, path].join("/").replace(/\/+/g, "/");
1592
+ return cleanPath;
1593
+ };
1594
+ var getBaseUrl = (req) => {
1595
+ if (req.headers["x-path-prefix"]) {
1596
+ return `/${req.headers["x-path-prefix"]}/`.replace(/\/+/g, "/");
1597
+ }
1598
+ return "/";
1599
+ };
1600
+ var prepareBaseUrl = (req, params) => {
1601
+ const pathPrefix = getBaseUrl(req).replace(/\/$/, "");
1602
+ const [hostname = "", port = 80] = (req.get("x-forwarded-host") || req.get("x-real-hostname") || req.get("host") || "").split(":");
1603
+ const finalPort = (params?.["x-real-port"] ?? null) || req.get("X-Real-Port") || port || "";
1604
+ return import_url.default.format({
1605
+ protocol: params?.["x-real-protocol"] || req.get("X-Real-protocol") || req.get("X-Forwarded-Proto") || req.protocol,
1606
+ hostname,
1607
+ port: Number(finalPort) === 80 ? "" : finalPort,
1608
+ pathname: pathPrefix
1609
+ });
1610
+ };
1611
+ var base64UrlUnescape = (str) => (str + "===".slice((str.length + 3) % 4)).replace(/-/g, "+").replace(/_/g, "/");
1612
+ var decodeEncKey = (str) => new Uint8Array(Buffer.from(base64UrlUnescape(str), "base64"));
1613
+ var getStepChallenge = () => (0, import_util3.stripHexPrefix)(Mcrypto.getRandomBytes(16)).toUpperCase();
1614
+ var parseWalletUA = (userAgent) => {
1615
+ const ua = (userAgent || "").toString().toLowerCase();
1616
+ let os = "";
1617
+ let version = "";
1618
+ if (ua.indexOf("android") > -1) {
1619
+ os = "android";
1620
+ } else if (ua.indexOf("iphone") > -1) {
1621
+ os = "ios";
1622
+ } else if (ua.indexOf("ipad") > -1) {
1623
+ os = "ios";
1624
+ } else if (ua.indexOf("ipod") > -1) {
1625
+ os = "ios";
1626
+ } else if (ua.indexOf("arcwallet") === 0) {
1627
+ os = "web";
1628
+ } else if (ua.indexOf("abtwallet") === 0) {
1629
+ os = "web";
1630
+ }
1631
+ const match = ua.split(/\s+/).find((x) => x.startsWith("arcwallet/") || x.startsWith("abtwallet/"));
1632
+ if (match) {
1633
+ const tmp = match.split("/");
1634
+ const coerced = tmp.length > 1 ? (tmp[1].match(/(\d+\.\d+\.\d+)/) || [])[1] : void 0;
1635
+ if (coerced) {
1636
+ version = coerced;
1637
+ }
1638
+ }
1639
+ return { os, version, jwt: "1.1.0" };
1640
+ };
1641
+ var isDeepLink = (str) => str.startsWith("https://abtwallet.io/i/") || str.startsWith("https://didwallet.io/i/");
1642
+ var isConnectedOnly = (params, sessionUserDid = "") => {
1643
+ if (typeof params.forceConnected === "string" && (0, import_did2.isValid)(params.forceConnected)) {
1644
+ return params.forceConnected;
1645
+ }
1646
+ if (!sessionUserDid) {
1647
+ return false;
1648
+ }
1649
+ if ((0, import_did2.isValid)(sessionUserDid) === false) {
1650
+ return false;
1651
+ }
1652
+ if (params.connectedDid !== sessionUserDid) {
1653
+ return false;
1654
+ }
1655
+ if (typeof params.forceConnected === "undefined") {
1656
+ return true;
1657
+ }
1658
+ if (typeof params.forceConnected === "string") {
1659
+ try {
1660
+ return !!JSON.parse(params.forceConnected);
1661
+ } catch {
1662
+ return false;
1663
+ }
1664
+ }
1665
+ return !!params.forceConnected;
1666
+ };
1667
+ function createHandlers({
1668
+ action,
1669
+ pathname,
1670
+ claims: claims2,
1671
+ onStart,
1672
+ onConnect,
1673
+ onAuth,
1674
+ onDecline,
1675
+ onComplete,
1676
+ onExpire,
1677
+ onError,
1678
+ pathTransformer,
1679
+ tokenStorage,
1680
+ authenticator,
1681
+ authPrincipal,
1682
+ persistentDynamicClaims = false,
1683
+ getSignParams = noop,
1684
+ getPathName = noTouch,
1685
+ options
1686
+ }) {
1687
+ const { tokenKey, encKey, versionKey, cleanupDelay } = options;
1688
+ const defaultSteps = (Array.isArray(claims2) ? claims2 : [claims2]).filter(Boolean);
1689
+ if (defaultSteps.length > 0) {
1690
+ const keys = Object.keys(defaultSteps[0]);
1691
+ const firstClaim = defaultSteps[0][keys[0]];
1692
+ if (Array.isArray(firstClaim)) {
1693
+ if (firstClaim[0] === "authPrincipal") {
1694
+ authPrincipal = false;
1695
+ }
1696
+ } else if (keys[0] === "authPrincipal") {
1697
+ authPrincipal = false;
1698
+ }
1699
+ }
1700
+ if (authPrincipal) {
1701
+ let target = "";
1702
+ let description = "Please continue with your account";
1703
+ let chainInfo2;
1704
+ let targetType;
1705
+ if (typeof authPrincipal === "string") {
1706
+ if ((0, import_did2.isValid)(authPrincipal)) {
1707
+ target = authPrincipal;
1708
+ } else {
1709
+ description = authPrincipal;
1710
+ }
1711
+ }
1712
+ if (typeof authPrincipal === "object") {
1713
+ target = authPrincipal.target ?? target;
1714
+ description = authPrincipal.description ?? description;
1715
+ targetType = authPrincipal.targetType ?? targetType;
1716
+ if (authPrincipal.chainInfo && authenticator._isValidChainInfo(authPrincipal.chainInfo)) {
1717
+ chainInfo2 = authPrincipal.chainInfo;
1718
+ }
1719
+ }
1720
+ const supervised = defaultSteps.length === 0;
1721
+ defaultSteps.unshift({
1722
+ authPrincipal: {
1723
+ skippable: true,
1724
+ description,
1725
+ target,
1726
+ chainInfo: chainInfo2,
1727
+ targetType,
1728
+ supervised
1729
+ }
1730
+ });
1731
+ }
1732
+ const canSkipConnect = defaultSteps[0]?.authPrincipal?.skippable;
1733
+ const createExtraParams = (locale, params, extra = {}) => {
1734
+ const finalParams = { ...params, ...extra || {} };
1735
+ return {
1736
+ locale,
1737
+ action,
1738
+ ...Object.keys(finalParams).filter((x) => !["userDid", "userInfo", "userSession", "appSession", "userPk", "token"].includes(x)).reduce((obj, x) => {
1739
+ obj[x] = finalParams[x];
1740
+ return obj;
1741
+ }, {})
1742
+ };
1743
+ };
1744
+ const createSessionUpdater = (token, params) => async (key, value, secure = false) => {
1745
+ const getUpdate = (k, v3) => {
1746
+ if (secure && params[encKey]) {
1747
+ const encrypted = seal(Buffer.from((0, import_fast_json_stable_stringify.default)(v3)), decodeEncKey(params[encKey]));
1748
+ return { [k]: Buffer.from(encrypted).toString("base64") };
1749
+ }
1750
+ return { [k]: v3 };
1751
+ };
1752
+ if (typeof key === "object") {
1753
+ secure = value;
1754
+ const keys = Object.keys(key);
1755
+ const updates = Object.assign({}, ...keys.map((k) => getUpdate(k, key[k])));
1756
+ return tokenStorage.update(token, updates);
1757
+ }
1758
+ return tokenStorage.update(token, omit(getUpdate(key, value), PROTECTED_KEYS));
1759
+ };
1760
+ const createMfaCodeGenerator = (token) => async () => {
1761
+ const mfaCode = random(10, 99);
1762
+ await tokenStorage.update(token, { mfaCode });
1763
+ return mfaCode;
1764
+ };
1765
+ const onProcessError = ({
1766
+ req,
1767
+ res,
1768
+ stage,
1769
+ err
1770
+ }) => {
1771
+ const { token, store } = req.context || {};
1772
+ if (token) {
1773
+ tokenStorage.update(token, { status: SESSION_STATUS.ERROR, error: err.message, mfaCode: 0 });
1774
+ }
1775
+ res.jsonp({ error: err.message });
1776
+ onError({ token, extraParams: store?.extraParams ?? {}, stage, err });
1777
+ };
1778
+ const _preparePathname = (str, req) => {
1779
+ const auto = preparePathname(str, req);
1780
+ const custom = pathTransformer(auto);
1781
+ return custom;
1782
+ };
1783
+ const generateSession = async (req, res) => {
1784
+ try {
1785
+ const params = {
1786
+ "x-real-port": req.get("x-real-port"),
1787
+ "x-real-protocol": req.get("x-real-protocol"),
1788
+ deviceDid: req.get("x-device-did"),
1789
+ connectedDid: req.cookies?.connected_did ?? "",
1790
+ connectedPk: req.cookies?.connected_pk ?? "",
1791
+ ...req.body,
1792
+ ...req.query,
1793
+ ...req.params
1794
+ };
1795
+ params.forceConnected = isConnectedOnly(params, req.get("x-user-did"));
1796
+ const token = sha3(getSessionId()).replace(/^0x/, "").slice(0, 8);
1797
+ await tokenStorage.create(token, SESSION_STATUS.CREATED);
1798
+ let sourceToken = params.sourceToken || "";
1799
+ const sourceTokenState = sourceToken ? await tokenStorage.read(sourceToken) : null;
1800
+ if (sourceTokenState) {
1801
+ if ([SESSION_STATUS.SUCCEED].includes(sourceTokenState.status)) {
1802
+ sourceToken = "";
1803
+ } else {
1804
+ await tokenStorage.update(sourceToken, { destToken: token });
1805
+ }
1806
+ }
1807
+ const finalPath = _preparePathname(getPathName(pathname, req), req);
1808
+ const baseUrl = prepareBaseUrl(req, params);
1809
+ const uri = await authenticator.uri({ token, pathname: finalPath, baseUrl, query: {} });
1810
+ const challenge = getStepChallenge();
1811
+ const didwallet = parseWalletUA(req.query["user-agent"] || req.headers["user-agent"]);
1812
+ const extraParams = createExtraParams(getLocale(req), params);
1813
+ const hookParams = {
1814
+ req,
1815
+ request: req,
1816
+ challenge,
1817
+ baseUrl,
1818
+ deepLink: uri,
1819
+ extraParams,
1820
+ updateSession: createSessionUpdater(token, extraParams),
1821
+ didwallet
1822
+ };
1823
+ const [wallet, delegator] = await Promise.all([
1824
+ authenticator.getWalletInfo({ baseUrl, request: req, extraParams }),
1825
+ authenticator.getDelegator({ baseUrl, request: req, extraParams })
1826
+ ]);
1827
+ const [appInfo2, memberAppInfo] = await Promise.all([
1828
+ authenticator.getAppInfo({ baseUrl, request: req, wallet, delegator, extraParams }, "appInfo"),
1829
+ authenticator.getAppInfo({ baseUrl, request: req, wallet, delegator, extraParams }, "memberAppInfo")
1830
+ ]);
1831
+ await tokenStorage.update(token, {
1832
+ currentStep: 0,
1833
+ mfaSupported: !didwallet.os,
1834
+ // If we are mobile (both webview and mobile browsers , os is truthy), mfa should be disabled
1835
+ challenge,
1836
+ sharedKey: getStepChallenge(),
1837
+ // used for wallet to encrypt userInfo
1838
+ extraParams: params,
1839
+ appInfo: appInfo2,
1840
+ memberAppInfo,
1841
+ sourceToken
1842
+ });
1843
+ const extra = await onStart(hookParams);
1844
+ res.jsonp({ token, status: SESSION_STATUS.CREATED, url: uri, appInfo: appInfo2, memberAppInfo, extra: extra || {} });
1845
+ } catch (err) {
1846
+ onProcessError({ req, res, stage: "generate-token", err });
1847
+ }
1848
+ };
1849
+ const checkSession = async (req, res) => {
1850
+ try {
1851
+ const { locale, token, store, params } = req.context;
1852
+ if (!token) {
1853
+ res.status(400).json({ error: errors.tokenMissing[locale] });
1854
+ return;
1855
+ }
1856
+ if (!store) {
1857
+ res.status(400).json({ error: errors.token404[locale] });
1858
+ return;
1859
+ }
1860
+ if (store.status === SESSION_STATUS.FORBIDDEN) {
1861
+ res.status(403).json({ error: errors.didMismatch[locale] });
1862
+ return;
1863
+ }
1864
+ if (store.status === SESSION_STATUS.SUCCEED) {
1865
+ setTimeout(() => {
1866
+ tokenStorage.delete(token).catch(console.error);
1867
+ }, cleanupDelay);
1868
+ const extraParams = createExtraParams(locale, params, store?.extraParams ?? {});
1869
+ await onComplete({
1870
+ req,
1871
+ request: req,
1872
+ userDid: store.did,
1873
+ userPk: store.pk,
1874
+ extraParams,
1875
+ updateSession: createSessionUpdater(token, extraParams)
1876
+ });
1877
+ }
1878
+ res.status(200).json(
1879
+ Object.keys(store).filter((x) => PROTECTED_KEYS.includes(x) === false).reduce((acc, key) => {
1880
+ acc[key] = store[key];
1881
+ return acc;
1882
+ }, {})
1883
+ );
1884
+ } catch (err) {
1885
+ onProcessError({ req, res, stage: "check-token-status", err });
1886
+ }
1887
+ };
1888
+ const expireSession = async (req, res) => {
1889
+ try {
1890
+ const { locale, token, store } = req.context;
1891
+ if (!token) {
1892
+ res.status(400).json({ error: errors.tokenMissing[locale] });
1893
+ return;
1894
+ }
1895
+ if (!store) {
1896
+ res.status(400).json({ error: errors.token404[locale] });
1897
+ return;
1898
+ }
1899
+ onExpire({ token, extraParams: store?.extraParams ?? {}, status: "expired" });
1900
+ if (store.status !== SESSION_STATUS.SCANNED) {
1901
+ await tokenStorage.delete(token);
1902
+ }
1903
+ res.status(200).json({ token });
1904
+ } catch (err) {
1905
+ onProcessError({ req, res, stage: "mark-token-timeout", err });
1906
+ }
1907
+ };
1908
+ const checkUser = async ({ context, userDid, userPk }) => {
1909
+ const { locale, token, store } = context;
1910
+ const isConnected = store.currentStep > 0;
1911
+ if (isConnected) {
1912
+ if (!userDid) {
1913
+ return errors.didMissing[locale];
1914
+ }
1915
+ if (!userPk) {
1916
+ return errors.pkMissing[locale];
1917
+ }
1918
+ if (userDid !== store.did) {
1919
+ await tokenStorage.update(token, { status: SESSION_STATUS.FORBIDDEN });
1920
+ return errors.didMismatch[locale];
1921
+ }
1922
+ }
1923
+ return false;
1924
+ };
1925
+ const onAuthRequest = async (req, res) => {
1926
+ const { locale, token, store, params, didwallet } = req.context;
1927
+ const extraParams = createExtraParams(locale, params, store?.extraParams ?? {});
1928
+ const userDid = params.userDid || store.did || extraParams.connectedDid;
1929
+ const userPk = params.userPk || store.pk || extraParams.connectedPk;
1930
+ const error = await checkUser({ context: req.context, userDid, userPk });
1931
+ if (error) {
1932
+ return res.jsonp({ error });
1933
+ }
1934
+ if (params[versionKey] && store.clientVersion !== params[versionKey]) {
1935
+ store.clientVersion = params[versionKey];
1936
+ store.encryptionKey = params[encKey];
1937
+ await tokenStorage.update(token, { clientVersion: params[versionKey], encryptionKey: params[encKey] });
1938
+ }
1939
+ try {
1940
+ const steps = [...cloneDeep(defaultSteps)];
1941
+ const shouldSkipConnect = canSkipConnect && !!extraParams.connectedDid;
1942
+ if (shouldSkipConnect) {
1943
+ steps[0].authPrincipal.supervised = false;
1944
+ }
1945
+ if (extraParams.forceConnected) {
1946
+ let target = extraParams.connectedDid;
1947
+ if (typeof extraParams.forceConnected === "string" && (0, import_did2.isValid)(extraParams.forceConnected)) {
1948
+ target = extraParams.forceConnected;
1949
+ }
1950
+ if ((0, import_did2.isValid)(target)) {
1951
+ steps[0].authPrincipal.target = target;
1952
+ }
1953
+ }
1954
+ if (store.status !== SESSION_STATUS.SCANNED) {
1955
+ await tokenStorage.update(token, { status: SESSION_STATUS.SCANNED, connectedWallet: didwallet });
1956
+ }
1957
+ if (store.dynamic || shouldSkipConnect) {
1958
+ const newClaims = await onConnect({
1959
+ req,
1960
+ request: req,
1961
+ userDid,
1962
+ userPk,
1963
+ didwallet,
1964
+ challenge: store.challenge,
1965
+ pathname: _preparePathname(getPathName(pathname, req), req),
1966
+ baseUrl: prepareBaseUrl(req, extraParams),
1967
+ extraParams,
1968
+ updateSession: createSessionUpdater(token, extraParams)
1969
+ });
1970
+ if (newClaims) {
1971
+ if (Array.isArray(newClaims)) {
1972
+ steps.push(...newClaims);
1973
+ } else {
1974
+ steps.push(newClaims);
1975
+ }
1976
+ }
1977
+ }
1978
+ const signParams = await getSignParams(req);
1979
+ const signedClaim = await authenticator.sign(
1980
+ Object.assign(signParams, {
1981
+ context: {
1982
+ token,
1983
+ userDid,
1984
+ userPk,
1985
+ didwallet,
1986
+ ...pick(store, ["currentStep", "sharedKey", "encryptionKey"]),
1987
+ mfaCode: store.mfaSupported ? createMfaCodeGenerator(token) : void 0
1988
+ },
1989
+ claims: steps[store.currentStep],
1990
+ pathname: _preparePathname(getPathName(pathname, req), req),
1991
+ baseUrl: prepareBaseUrl(req, extraParams),
1992
+ extraParams,
1993
+ challenge: store.challenge,
1994
+ appInfo: store.appInfo,
1995
+ memberAppInfo: store.memberAppInfo,
1996
+ request: req
1997
+ })
1998
+ );
1999
+ res.jsonp(encrypt(signedClaim, store));
2000
+ } catch (err) {
2001
+ onProcessError({ req, res, stage: "send-auth-claim", err });
2002
+ }
2003
+ };
2004
+ const onAuthResponse = async (req, res) => {
2005
+ const { locale, token, store, params, didwallet } = req.context;
2006
+ try {
2007
+ const {
2008
+ userDid,
2009
+ userPk,
2010
+ action: userAction,
2011
+ challenge: userChallenge,
2012
+ claims: claimResponse,
2013
+ timestamp
2014
+ } = await authenticator.verify(decrypt(params, store), locale);
2015
+ if (!store.did || !store.pk) {
2016
+ await tokenStorage.update(token, { did: userDid, pk: userPk });
2017
+ }
2018
+ const extraParams = createExtraParams(locale, params, store?.extraParams ?? {});
2019
+ const cbParams = {
2020
+ step: store.currentStep,
2021
+ req,
2022
+ request: req,
2023
+ userDid,
2024
+ userPk,
2025
+ challenge: store.challenge,
2026
+ didwallet,
2027
+ claims: claimResponse,
2028
+ baseUrl: prepareBaseUrl(req, extraParams),
2029
+ extraParams,
2030
+ updateSession: createSessionUpdater(token, extraParams),
2031
+ timestamp
2032
+ };
2033
+ const steps = [...defaultSteps];
2034
+ const shouldSkipConnect = canSkipConnect && !!extraParams.connectedDid;
2035
+ if (store.dynamic || shouldSkipConnect) {
2036
+ const newClaims = await onConnect(cbParams);
2037
+ if (newClaims) {
2038
+ if (Array.isArray(newClaims)) {
2039
+ steps.push(...newClaims);
2040
+ } else {
2041
+ steps.push(newClaims);
2042
+ }
2043
+ }
2044
+ } else if (persistentDynamicClaims && Array.isArray(store.dynamicClaims)) {
2045
+ steps.push(...store.dynamicClaims);
2046
+ }
2047
+ if (userAction === "declineAuth") {
2048
+ await tokenStorage.update(token, {
2049
+ status: SESSION_STATUS.ERROR,
2050
+ error: errors.userDeclined[locale],
2051
+ mfaCode: 0,
2052
+ currentStep: steps.length - 1
2053
+ });
2054
+ const result = await onDecline(cbParams);
2055
+ return res.jsonp({ ...result || {} });
2056
+ }
2057
+ if (userAction === "busy") {
2058
+ await tokenStorage.update(token, {
2059
+ status: SESSION_STATUS.BUSY,
2060
+ error: errors.userBusy[locale],
2061
+ mfaCode: 0,
2062
+ currentStep: steps.length - 1
2063
+ });
2064
+ return res.jsonp({});
2065
+ }
2066
+ if (store.mfaCode && !claimResponse.some((x) => isEqual(x.mfaCode, [store.mfaCode]))) {
2067
+ return onProcessError({ req, res, stage: "verify-mfa-code", err: new Error(errors.mfaMismatch[locale]) });
2068
+ }
2069
+ if (!userChallenge) {
2070
+ return res.jsonp({ error: errors.challengeMismatch[locale] });
2071
+ }
2072
+ if (userChallenge !== store.challenge) {
2073
+ return res.jsonp({ error: errors.challengeMismatch[locale] });
2074
+ }
2075
+ const error = await checkUser({ context: req.context, userDid, userPk });
2076
+ if (error) {
2077
+ return res.jsonp({ error });
2078
+ }
2079
+ const isConnected = store.currentStep > 0;
2080
+ if (isConnected === false) {
2081
+ const newClaims = await onConnect(cbParams);
2082
+ if (newClaims) {
2083
+ await tokenStorage.update(token, {
2084
+ dynamic: !persistentDynamicClaims,
2085
+ dynamicClaims: persistentDynamicClaims ? newClaims : void 0
2086
+ });
2087
+ if (Array.isArray(newClaims)) {
2088
+ steps.push(...newClaims);
2089
+ } else {
2090
+ steps.push(newClaims);
2091
+ }
2092
+ }
2093
+ }
2094
+ const onLastStep = async (result) => {
2095
+ let nextWorkflow = isUrl(extraParams.nw) ? extraParams.nw : "";
2096
+ if (nextWorkflow && result?.nextWorkflowData) {
2097
+ if (isPlainObject(result.nextWorkflowData) === false) {
2098
+ const err = new Error(`expect nextWorkflowData should be a plain object, got: ${result.nextWorkflowData}`);
2099
+ return onProcessError({ req, res, stage: "validate-next-workflow-data", err });
2100
+ }
2101
+ const tmp = new URL(nextWorkflow);
2102
+ const merged = Object.assign(extraParams.previousWorkflowData || {}, result.nextWorkflowData);
2103
+ const previousWorkflowData = (0, import_util3.toBase64)(JSON.stringify(merged));
2104
+ if (previousWorkflowData.length > 8192) {
2105
+ const err = new Error("base64 encoded nextWorkflowData should be less than 8192 characters");
2106
+ return onProcessError({ req, res, stage: "append-next-workflow", err });
2107
+ }
2108
+ if (isDeepLink(nextWorkflow)) {
2109
+ const actualUrl = decodeURIComponent(tmp.searchParams.get("url"));
2110
+ const obj = new URL(actualUrl);
2111
+ obj.searchParams.set("previousWorkflowData", previousWorkflowData);
2112
+ tmp.searchParams.set("url", obj.href);
2113
+ } else {
2114
+ tmp.searchParams.set("previousWorkflowData", previousWorkflowData);
2115
+ }
2116
+ nextWorkflow = tmp.href;
2117
+ }
2118
+ const updates = {};
2119
+ let actualNw = nextWorkflow || result?.nextWorkflow || "";
2120
+ if (actualNw) {
2121
+ if (isDeepLink(actualNw)) {
2122
+ actualNw = new URL(actualNw).searchParams.get("url");
2123
+ }
2124
+ if (actualNw) {
2125
+ updates.nextWorkflow = decodeURIComponent(actualNw);
2126
+ }
2127
+ }
2128
+ if (result?.nextToken && result.nextWorkflow) {
2129
+ try {
2130
+ await tokenStorage.update(result.nextToken, { prevToken: token });
2131
+ } catch (err) {
2132
+ console.error("DIDAuth: failed to to update nextToken", err);
2133
+ updates.status = SESSION_STATUS.SUCCEED;
2134
+ }
2135
+ } else {
2136
+ if (store.prevToken) {
2137
+ try {
2138
+ await tokenStorage.update(store.prevToken, { status: SESSION_STATUS.SUCCEED });
2139
+ } catch (err) {
2140
+ console.error("DIDAuth: failed to to update prevToken", err);
2141
+ }
2142
+ }
2143
+ updates.status = SESSION_STATUS.SUCCEED;
2144
+ }
2145
+ await tokenStorage.update(token, updates);
2146
+ return res.jsonp({ ...Object.assign({ nextWorkflow }, result || {}) });
2147
+ };
2148
+ if (steps.length === 1) {
2149
+ const result = await onAuth(cbParams);
2150
+ return onLastStep(result);
2151
+ }
2152
+ if (isConnected && store.currentStep < steps.length) {
2153
+ const result = await onAuth(cbParams);
2154
+ const isLastStep = store.currentStep === steps.length - 1;
2155
+ if (isLastStep) {
2156
+ return onLastStep(result);
2157
+ }
2158
+ }
2159
+ const nextStep = store.currentStep + 1;
2160
+ const nextChallenge = getStepChallenge();
2161
+ await tokenStorage.update(token, { currentStep: nextStep, challenge: nextChallenge, mfaCode: 0 });
2162
+ const signParams = await getSignParams(req);
2163
+ try {
2164
+ const nextSignedClaim = await authenticator.sign(
2165
+ Object.assign(signParams, {
2166
+ context: {
2167
+ token,
2168
+ userDid,
2169
+ userPk,
2170
+ didwallet,
2171
+ ...pick(store, ["currentStep", "sharedKey", "encryptionKey"]),
2172
+ mfaCode: store.mfaSupported ? createMfaCodeGenerator(token) : void 0
2173
+ },
2174
+ claims: steps[nextStep],
2175
+ pathname: _preparePathname(getPathName(pathname, req), req),
2176
+ baseUrl: prepareBaseUrl(req, extraParams),
2177
+ extraParams,
2178
+ challenge: nextChallenge,
2179
+ appInfo: store.appInfo,
2180
+ memberAppInfo: store.memberAppInfo,
2181
+ request: req
2182
+ })
2183
+ );
2184
+ return res.jsonp(encrypt(nextSignedClaim, store));
2185
+ } catch (err) {
2186
+ return onProcessError({ req, res, stage: "next-auth-claim", err });
2187
+ }
2188
+ } catch (err) {
2189
+ onProcessError({ req, res, stage: "verify-auth-claim", err });
2190
+ }
2191
+ };
2192
+ const ensureContext = async (req, _res, next) => {
2193
+ const didwallet = parseWalletUA(req.query["user-agent"] || req.headers["user-agent"]);
2194
+ const params = { ...req.body, ...req.query, ...req.params };
2195
+ const token = params[tokenKey];
2196
+ const locale = getLocale(req);
2197
+ let store = null;
2198
+ if (token) {
2199
+ store = await tokenStorage.read(token);
2200
+ if (params.previousWorkflowData) {
2201
+ try {
2202
+ store.extraParams.previousWorkflowData = JSON.parse((0, import_util3.fromBase64)(params.previousWorkflowData).toString());
2203
+ await tokenStorage.update(token, { extraParams: store.extraParams });
2204
+ } catch (e) {
2205
+ console.warn("Could not parse previousWorkflowData", params.previousWorkflowData, e);
2206
+ }
2207
+ }
2208
+ if (store?.destToken && typeof params.notrace === "undefined") {
2209
+ const result = await tokenStorage.read(store.destToken);
2210
+ if (result) {
2211
+ store = result;
2212
+ }
2213
+ }
2214
+ }
2215
+ req.context = { locale, token, didwallet, params, store };
2216
+ return next();
2217
+ };
2218
+ const ensureSignedJson = (req, res, next) => {
2219
+ if (req.ensureSignedJson === void 0) {
2220
+ req.ensureSignedJson = true;
2221
+ const originJsonp = res.jsonp;
2222
+ res.jsonp = async (payload) => {
2223
+ if (payload.appPk && payload.authInfo) {
2224
+ return originJsonp.call(res, payload);
2225
+ }
2226
+ const data = payload.response ? { response: payload.response } : { response: payload };
2227
+ const fields = ["error", "errorMessage", "successMessage", "nextWorkflow", "nextUrl", "cookies", "storages"];
2228
+ fields.forEach((x) => {
2229
+ if (payload[x]) {
2230
+ data[x] = payload[x];
2231
+ }
2232
+ });
2233
+ data.errorMessage = data.error || data.errorMessage || "";
2234
+ if (typeof data.response === "object") {
2235
+ data.response = omit(data.response, fields);
2236
+ }
2237
+ const params = { ...req.body, ...req.query, ...req.params };
2238
+ const token2 = params[tokenKey];
2239
+ const store2 = token2 ? await tokenStorage.read(token2) : null;
2240
+ const extraParams = store2?.extraParams ?? {};
2241
+ const signedData = await authenticator.signResponse(data, prepareBaseUrl(req, extraParams), req, extraParams);
2242
+ originJsonp.call(res, encrypt(signedData, store2));
2243
+ };
2244
+ }
2245
+ const { token, store, locale } = req.context;
2246
+ if (!token || !store) {
2247
+ return res.jsonp({ error: errors.token404[locale] });
2248
+ }
2249
+ next();
2250
+ };
2251
+ return {
2252
+ generateSession,
2253
+ expireSession,
2254
+ checkSession,
2255
+ onAuthRequest,
2256
+ onAuthResponse,
2257
+ ensureContext,
2258
+ ensureSignedJson,
2259
+ createExtraParams
2260
+ };
2261
+ }
2262
+
2263
+ // src/handlers/wallet.ts
2264
+ var debug3 = (0, import_debug4.default)("@arcblock/did-connect-js:handlers:wallet");
2265
+ var noop2 = () => {
2266
+ };
2267
+ var WalletHandlers = class extends base_default2 {
2268
+ options;
2269
+ /**
2270
+ * Creates an instance of DID Auth Handlers.
2271
+ *
2272
+ * @class
2273
+ * @param {object} config
2274
+ * @param {object} config.tokenStorage - function to generate action token
2275
+ * @param {object} config.authenticator - Authenticator instance that can to jwt sign/verify
2276
+ * @param {function} [config.pathTransformer=null] - how should we update pathname
2277
+ * @param {function} [config.onConnect=noop] - function called before each auth request send back to app, used to check for permission, throw error to halt the auth process
2278
+ * @param {object} [config.options={}] - custom options to define all handlers attached
2279
+ * @param {string} [config.options.prefix='/api/did'] - url prefix for this group endpoints
2280
+ * @param {number} [config.options.cleanupDelay=60000] - how long to wait before cleanup finished session
2281
+ * @param {string} [config.options.tokenKey='_t_'] - query param key for `token`
2282
+ * @param {string} [config.options.encKey='_ek_'] - query param key for encryption key
2283
+ * @param {string} [config.options.versionKey='_v_'] - query param key for protocol `version`
2284
+ */
2285
+ constructor({
2286
+ pathTransformer,
2287
+ tokenStorage,
2288
+ authenticator,
2289
+ onConnect = noop2,
2290
+ options = {}
2291
+ }) {
2292
+ super({ pathTransformer, tokenStorage, authenticator, onConnect });
2293
+ this.options = {
2294
+ prefix: "/api/did",
2295
+ cleanupDelay: 6e4,
2296
+ tokenKey: "_t_",
2297
+ encKey: "_ek_",
2298
+ versionKey: "_v_",
2299
+ ...options
2300
+ };
2301
+ }
2302
+ /**
2303
+ * Attach routes and handlers for authenticator
2304
+ * Now express app have route handlers attached to the following url
2305
+ * - `GET /api/did/{action}/token` create new token
2306
+ * - `GET /api/did/{action}/status` check for token status
2307
+ * - `GET /api/did/{action}/timeout` expire a token
2308
+ * - `GET /api/did/{action}/auth` create auth response
2309
+ * - `POST /api/did/{action}/auth` process payment request
2310
+ *
2311
+ * @method
2312
+ * @param {object} config
2313
+ * @param {object} config.app - express instance to attach routes to
2314
+ * @param {object} [config.claims] - claims for this request
2315
+ * @param {string} config.action - action of this group of routes
2316
+ * @param {function} [config.onStart=noop] - callback when a new action start
2317
+ * @param {function} [config.onConnect=noop] - callback when a new action start
2318
+ * @param {function} config.onAuth - callback when user completed auth in DID Wallet, and data posted back
2319
+ * @param {function} [config.onDecline=noop] - callback when user has declined in wallet
2320
+ * @param {function} [config.onComplete=noop] - callback when the whole auth process is done, action token is removed
2321
+ * @param {function} [config.onExpire=noop] - callback when the action token expired
2322
+ * @param {function} [config.onError=console.error] - callback when there are some errors
2323
+ * @param {boolean|string|did} [config.authPrincipal=true] - whether should we do auth principal claim first
2324
+ * @param {boolean} [config.persistentDynamicClaims=false] - whether should we persist dynamic claims
2325
+ * @return void
2326
+ */
2327
+ attach({
2328
+ app,
2329
+ action,
2330
+ claims: claims2 = void 0,
2331
+ onStart = noop2,
2332
+ onConnect,
2333
+ onAuth,
2334
+ onDecline = noop2,
2335
+ onComplete = noop2,
2336
+ onExpire = noop2,
2337
+ onError = console.error,
2338
+ authPrincipal = true,
2339
+ persistentDynamicClaims = false
2340
+ }) {
2341
+ if (typeof onAuth !== "function") {
2342
+ throw new Error("onAuth callback is required to attach did auth handlers");
2343
+ }
2344
+ if (typeof onDecline !== "function") {
2345
+ throw new Error("onDecline callback is required to attach did auth handlers");
2346
+ }
2347
+ if (typeof onComplete !== "function") {
2348
+ throw new Error("onComplete callback is required to attach did auth handlers");
2349
+ }
2350
+ const { prefix } = this.options;
2351
+ const pathname = `${prefix}/${action}/auth`;
2352
+ debug3("attach routes", { action, prefix, pathname });
2353
+ const onConnectWrapped = async (...args) => {
2354
+ if (typeof this.onConnect === "function") {
2355
+ await this.onConnect(...args);
2356
+ }
2357
+ if (typeof onConnect === "function") {
2358
+ return onConnect(...args);
2359
+ }
2360
+ };
2361
+ const {
2362
+ generateSession,
2363
+ expireSession,
2364
+ checkSession,
2365
+ onAuthRequest,
2366
+ onAuthResponse,
2367
+ ensureContext,
2368
+ ensureSignedJson
2369
+ } = createHandlers({
2370
+ action,
2371
+ pathname,
2372
+ claims: claims2,
2373
+ onStart,
2374
+ onConnect: onConnectWrapped,
2375
+ // must be deterministic when returning dynamic claims
2376
+ onAuth,
2377
+ onDecline,
2378
+ onComplete,
2379
+ onExpire,
2380
+ onError,
2381
+ authPrincipal,
2382
+ persistentDynamicClaims,
2383
+ options: this.options,
2384
+ pathTransformer: this.pathTransformer,
2385
+ tokenStorage: this.tokenStorage,
2386
+ authenticator: this.authenticator
2387
+ });
2388
+ const handlerFns = {
2389
+ generateSession,
2390
+ checkSession,
2391
+ expireSession,
2392
+ onAuthRequest,
2393
+ onAuthResponse,
2394
+ ensureContext,
2395
+ ensureSignedJson
2396
+ };
2397
+ if (isHonoApp(app)) {
2398
+ return attachHono({ app, prefix, action, handlers: handlerFns });
2399
+ }
2400
+ return attachExpress({ app, prefix, action, handlers: handlerFns });
2401
+ }
2402
+ };
2403
+ var wallet_default2 = WalletHandlers;
2404
+
2405
+ // src/storage/kv.ts
2406
+ var import_events = require("events");
2407
+ var CloudflareKVStorage = class extends import_events.EventEmitter {
2408
+ kv;
2409
+ ttl;
2410
+ prefix;
2411
+ constructor(kv, options = {}) {
2412
+ super();
2413
+ this.kv = kv;
2414
+ this.ttl = options.ttl ?? 300;
2415
+ this.prefix = options.prefix ?? "";
2416
+ }
2417
+ key(token) {
2418
+ return `${this.prefix}${token}`;
2419
+ }
2420
+ async create(token, status = "created") {
2421
+ const record = { token, status };
2422
+ await this.kv.put(this.key(token), JSON.stringify(record), { expirationTtl: this.ttl });
2423
+ this.emit("create", record);
2424
+ return record;
2425
+ }
2426
+ async read(token) {
2427
+ const raw = await this.kv.get(this.key(token));
2428
+ if (!raw) return null;
2429
+ return JSON.parse(raw);
2430
+ }
2431
+ async update(token, updates) {
2432
+ const existing = await this.read(token);
2433
+ if (!existing) return null;
2434
+ delete updates.token;
2435
+ const merged = { ...existing, ...updates };
2436
+ await this.kv.put(this.key(token), JSON.stringify(merged), { expirationTtl: this.ttl });
2437
+ this.emit("update", merged);
2438
+ return merged;
2439
+ }
2440
+ async delete(token) {
2441
+ const existing = await this.read(token);
2442
+ if (existing) {
2443
+ this.emit("destroy", existing);
2444
+ }
2445
+ await this.kv.delete(this.key(token));
2446
+ }
2447
+ async exist(token, did) {
2448
+ const record = await this.read(token);
2449
+ if (!record) return false;
2450
+ if (did) return record.did === did;
2451
+ return true;
2452
+ }
2453
+ };
2454
+ var kv_default = CloudflareKVStorage;
2455
+
2456
+ // src/index.ts
2457
+ var import_did_connect_core2 = require("@arcblock/did-connect-core");
2458
+ // Annotate the CommonJS export names for ESM import in node:
2459
+ 0 && (module.exports = {
2460
+ CloudflareKVStorage,
2461
+ VERSION,
2462
+ WalletAuthenticator,
2463
+ WalletHandlers,
2464
+ attachExpress,
2465
+ attachHono,
2466
+ createHonoRequest,
2467
+ createHonoResponse,
2468
+ isExpressApp,
2469
+ isHonoApp,
2470
+ parseWalletUA,
2471
+ prepareBaseUrl
2472
+ });