@calimero-network/mero-js 25.3.0 → 25.3.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/README.md +3 -3
- package/dist/auth/index.d.ts +11 -2
- package/dist/auth/index.js +20 -3
- package/dist/cloud/enrol-redirect.d.ts +10 -24
- package/dist/cloud/enrol-redirect.js +16 -8
- package/dist/cloud/link-state.d.ts +5 -1
- package/dist/cloud/link-state.js +28 -21
- package/dist/events/sse.js +20 -8
- package/dist/http-client/http-types.d.ts +1 -1
- package/dist/http-client/web-client.d.ts +7 -0
- package/dist/http-client/web-client.js +41 -25
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +410 -364
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +410 -364
- package/dist/index.mjs.map +4 -4
- package/dist/mero-js.d.ts +2 -2
- package/dist/mero-js.js +2 -2
- package/dist/request/proof-signer.d.ts +1 -1
- package/dist/request/request-sig.d.ts +9 -9
- package/dist/request/request-sig.js +7 -8
- package/dist/request-proof/authorizer.d.ts +3 -10
- package/dist/request-proof/authorizer.js +5 -10
- package/dist/request-proof/request-proof.d.ts +3 -6
- package/dist/request-proof/request-proof.js +4 -20
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -331,6 +331,244 @@ function createTimeoutSignal(timeoutMs) {
|
|
|
331
331
|
return controller.signal;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
// src/crypto/internal.ts
|
|
335
|
+
var PKCS8_ED25519_PREFIX = new Uint8Array([
|
|
336
|
+
48,
|
|
337
|
+
46,
|
|
338
|
+
2,
|
|
339
|
+
1,
|
|
340
|
+
0,
|
|
341
|
+
48,
|
|
342
|
+
5,
|
|
343
|
+
6,
|
|
344
|
+
3,
|
|
345
|
+
43,
|
|
346
|
+
101,
|
|
347
|
+
112,
|
|
348
|
+
4,
|
|
349
|
+
34,
|
|
350
|
+
4,
|
|
351
|
+
32
|
|
352
|
+
]);
|
|
353
|
+
function u32le(value) {
|
|
354
|
+
const out = new Uint8Array(4);
|
|
355
|
+
new DataView(out.buffer).setUint32(0, value, true);
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
function hex(bytes) {
|
|
359
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
360
|
+
}
|
|
361
|
+
function fromHex(value, label2, expectedBytes) {
|
|
362
|
+
const clean = value.trim();
|
|
363
|
+
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
const bytes = new Uint8Array(expectedBytes);
|
|
369
|
+
for (let i = 0; i < expectedBytes; i += 1) {
|
|
370
|
+
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
371
|
+
}
|
|
372
|
+
return bytes;
|
|
373
|
+
}
|
|
374
|
+
function fromHexUnsized(value, label2) {
|
|
375
|
+
const clean = value.trim();
|
|
376
|
+
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
377
|
+
throw new Error(`${label2} must be an even number of hex characters`);
|
|
378
|
+
}
|
|
379
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
380
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
381
|
+
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
382
|
+
}
|
|
383
|
+
return bytes;
|
|
384
|
+
}
|
|
385
|
+
function u64le(value) {
|
|
386
|
+
const out = new Uint8Array(8);
|
|
387
|
+
new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
function concat(...parts) {
|
|
391
|
+
const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
|
|
392
|
+
let offset = 0;
|
|
393
|
+
for (const part of parts) {
|
|
394
|
+
out.set(part, offset);
|
|
395
|
+
offset += part.length;
|
|
396
|
+
}
|
|
397
|
+
return out;
|
|
398
|
+
}
|
|
399
|
+
async function domainHash(domain, parts) {
|
|
400
|
+
const chunks = [u64le(domain.length), domain];
|
|
401
|
+
for (const part of parts) {
|
|
402
|
+
chunks.push(u64le(part.length), part);
|
|
403
|
+
}
|
|
404
|
+
return new Uint8Array(
|
|
405
|
+
await crypto.subtle.digest("SHA-256", concat(...chunks))
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
async function importSigningKey(secretHex) {
|
|
409
|
+
const seed = fromHex(secretHex, "deviceSecret", 32);
|
|
410
|
+
const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
|
|
411
|
+
try {
|
|
412
|
+
return await crypto.subtle.importKey(
|
|
413
|
+
"pkcs8",
|
|
414
|
+
pkcs8,
|
|
415
|
+
{ name: "Ed25519" },
|
|
416
|
+
false,
|
|
417
|
+
["sign"]
|
|
418
|
+
);
|
|
419
|
+
} catch (cause) {
|
|
420
|
+
const detail = cause instanceof Error ? `: ${cause.message}` : "";
|
|
421
|
+
throw new Error(
|
|
422
|
+
"this runtime has no WebCrypto Ed25519, so a warrant cannot be signed here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; otherwise mint the warrant with `merod account warrant`" + detail
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async function verifySignature(publicKey, signature, payload) {
|
|
427
|
+
const key = await crypto.subtle.importKey(
|
|
428
|
+
"raw",
|
|
429
|
+
fromHex(publicKey, "publicKey", 32),
|
|
430
|
+
{ name: "Ed25519" },
|
|
431
|
+
false,
|
|
432
|
+
["verify"]
|
|
433
|
+
);
|
|
434
|
+
return crypto.subtle.verify({ name: "Ed25519" }, key, signature, payload);
|
|
435
|
+
}
|
|
436
|
+
async function derivePublicKey(secretHex) {
|
|
437
|
+
const seed = fromHex(secretHex, "deviceSecret", 32);
|
|
438
|
+
const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
|
|
439
|
+
const key = await crypto.subtle.importKey(
|
|
440
|
+
"pkcs8",
|
|
441
|
+
pkcs8,
|
|
442
|
+
{ name: "Ed25519" },
|
|
443
|
+
true,
|
|
444
|
+
["sign"]
|
|
445
|
+
);
|
|
446
|
+
const jwk = await crypto.subtle.exportKey("jwk", key);
|
|
447
|
+
const raw = jwk.x;
|
|
448
|
+
return Uint8Array.from(
|
|
449
|
+
atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
|
|
450
|
+
(c) => c.charCodeAt(0)
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/request/request-sig.ts
|
|
455
|
+
var SIGN_DOMAIN = new TextEncoder().encode("calimero.auth.request.v2");
|
|
456
|
+
var BODY_DOMAIN = new TextEncoder().encode("calimero.auth.request.body.v1");
|
|
457
|
+
function borshString(value) {
|
|
458
|
+
const bytes = new TextEncoder().encode(value);
|
|
459
|
+
return concat(u32le(bytes.length), bytes);
|
|
460
|
+
}
|
|
461
|
+
async function requestBodyHash(body = new Uint8Array(0)) {
|
|
462
|
+
const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
|
|
463
|
+
return domainHash(BODY_DOMAIN, [bytes]);
|
|
464
|
+
}
|
|
465
|
+
async function signRequest(input) {
|
|
466
|
+
const key = await importSigningKey(input.signerSecret);
|
|
467
|
+
const bodyHash = await requestBodyHash(input.body);
|
|
468
|
+
const issuedAt = u64le(input.issuedAt);
|
|
469
|
+
const expiresAt = u64le(input.expiresAt);
|
|
470
|
+
const encoder = new TextEncoder();
|
|
471
|
+
const preimage = await domainHash(SIGN_DOMAIN, [
|
|
472
|
+
encoder.encode(input.method),
|
|
473
|
+
encoder.encode(input.path),
|
|
474
|
+
bodyHash,
|
|
475
|
+
issuedAt,
|
|
476
|
+
expiresAt
|
|
477
|
+
]);
|
|
478
|
+
const signature = new Uint8Array(
|
|
479
|
+
await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
|
|
480
|
+
);
|
|
481
|
+
return hex(
|
|
482
|
+
concat(
|
|
483
|
+
borshString(input.method),
|
|
484
|
+
borshString(input.path),
|
|
485
|
+
bodyHash,
|
|
486
|
+
issuedAt,
|
|
487
|
+
expiresAt,
|
|
488
|
+
signature
|
|
489
|
+
)
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/request-proof/request-proof.ts
|
|
494
|
+
var utf8 = (text) => new TextEncoder().encode(text);
|
|
495
|
+
var REQUEST_PROOF_HEADER = "X-Calimero-Proof";
|
|
496
|
+
var DEFAULT_VALID_FOR = 300;
|
|
497
|
+
async function signRequestProof(input) {
|
|
498
|
+
const { method, path, credential } = input;
|
|
499
|
+
if (!method) throw new Error("signRequestProof needs the method the request will use");
|
|
500
|
+
if (!path.startsWith("/")) {
|
|
501
|
+
throw new Error(`signRequestProof needs a path, got ${JSON.stringify(path)}`);
|
|
502
|
+
}
|
|
503
|
+
const bodyHash = await requestBodyHash(input.body);
|
|
504
|
+
const issuedAt = Math.floor(input.now ?? Date.now() / 1e3);
|
|
505
|
+
const expiresAt = issuedAt + (input.validForSeconds ?? DEFAULT_VALID_FOR);
|
|
506
|
+
const payload = await domainHash(SIGN_DOMAIN, [
|
|
507
|
+
utf8(method),
|
|
508
|
+
utf8(path),
|
|
509
|
+
bodyHash,
|
|
510
|
+
u64le(issuedAt),
|
|
511
|
+
u64le(expiresAt)
|
|
512
|
+
]);
|
|
513
|
+
const signature = await signPayload(payload, input);
|
|
514
|
+
if (signature.length !== 64) {
|
|
515
|
+
throw new Error(`the signer returned ${signature.length} bytes, not a 64-byte signature`);
|
|
516
|
+
}
|
|
517
|
+
const requestSig = concat(
|
|
518
|
+
u32le(utf8(method).length),
|
|
519
|
+
utf8(method),
|
|
520
|
+
u32le(utf8(path).length),
|
|
521
|
+
utf8(path),
|
|
522
|
+
bodyHash,
|
|
523
|
+
u64le(issuedAt),
|
|
524
|
+
u64le(expiresAt),
|
|
525
|
+
signature
|
|
526
|
+
);
|
|
527
|
+
const accountProof = hexToBytes(credential, "credential");
|
|
528
|
+
return hex(concat(accountProof, new Uint8Array([0]), requestSig));
|
|
529
|
+
}
|
|
530
|
+
async function signPayload(payload, input) {
|
|
531
|
+
if (input.signer) return input.signer.sign(payload);
|
|
532
|
+
if (!input.deviceSecret) {
|
|
533
|
+
throw new Error("signRequestProof needs either `deviceSecret` or `signer`");
|
|
534
|
+
}
|
|
535
|
+
const key = await importSigningKey(input.deviceSecret);
|
|
536
|
+
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, key, payload));
|
|
537
|
+
}
|
|
538
|
+
function hexToBytes(value, label2) {
|
|
539
|
+
const clean = value.trim();
|
|
540
|
+
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
541
|
+
throw new Error(`${label2} is not an even-length hex string`);
|
|
542
|
+
}
|
|
543
|
+
return fromHex(clean, label2, clean.length / 2);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/request-proof/authorizer.ts
|
|
547
|
+
function createProofAuthorizer(credential) {
|
|
548
|
+
if (!credential.credential) {
|
|
549
|
+
throw new Error("a proof authorizer needs the `credential` from signDeviceCert");
|
|
550
|
+
}
|
|
551
|
+
if (!credential.deviceSecret && !credential.signer) {
|
|
552
|
+
throw new Error("a proof authorizer needs either `deviceSecret` or `signer`");
|
|
553
|
+
}
|
|
554
|
+
return async ({ method, path, body }) => {
|
|
555
|
+
const proof = await signRequestProof({
|
|
556
|
+
method,
|
|
557
|
+
path,
|
|
558
|
+
body,
|
|
559
|
+
credential: credential.credential,
|
|
560
|
+
deviceSecret: credential.deviceSecret,
|
|
561
|
+
signer: credential.signer,
|
|
562
|
+
validForSeconds: credential.validForSeconds
|
|
563
|
+
});
|
|
564
|
+
return { [REQUEST_PROOF_HEADER]: proof };
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function signedPath(url2) {
|
|
568
|
+
const parsed = new URL(url2, globalThis.location?.href ?? "http://localhost");
|
|
569
|
+
return parsed.pathname + parsed.search;
|
|
570
|
+
}
|
|
571
|
+
|
|
334
572
|
// src/http-client/web-client.ts
|
|
335
573
|
var TERMINAL_AUTH_ERRORS = /* @__PURE__ */ new Set(["token_reuse", "token_revoked"]);
|
|
336
574
|
function extractErrorMessage(bodyText) {
|
|
@@ -386,13 +624,31 @@ function headersToRecord(headers) {
|
|
|
386
624
|
});
|
|
387
625
|
return record;
|
|
388
626
|
}
|
|
389
|
-
function
|
|
627
|
+
function withoutBareQuery(url2) {
|
|
628
|
+
let parsed;
|
|
390
629
|
try {
|
|
391
|
-
|
|
630
|
+
parsed = new URL(url2, globalThis.location?.href);
|
|
392
631
|
} catch {
|
|
393
|
-
|
|
394
|
-
return query === -1 ? url2 : url2.slice(0, query);
|
|
632
|
+
return url2;
|
|
395
633
|
}
|
|
634
|
+
if (parsed.search === "") parsed.search = "";
|
|
635
|
+
return parsed.href;
|
|
636
|
+
}
|
|
637
|
+
function carriesProof(headers) {
|
|
638
|
+
return Object.keys(headers).some(
|
|
639
|
+
(name) => name.toLowerCase() === REQUEST_PROOF_HEADER.toLowerCase()
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
function refuseProofRedirect(response, url2) {
|
|
643
|
+
const redirected = response.type === "opaqueredirect" || response.status >= 300 && response.status < 400;
|
|
644
|
+
if (!redirected) return;
|
|
645
|
+
throw new HTTPError(
|
|
646
|
+
0,
|
|
647
|
+
"Redirect Not Followed",
|
|
648
|
+
url2,
|
|
649
|
+
response.headers,
|
|
650
|
+
`the node answered with a redirect; a request carrying ${REQUEST_PROOF_HEADER} is not followed to another location`
|
|
651
|
+
);
|
|
396
652
|
}
|
|
397
653
|
var WebHttpClient = class {
|
|
398
654
|
constructor(transport2) {
|
|
@@ -457,7 +713,7 @@ var WebHttpClient = class {
|
|
|
457
713
|
}
|
|
458
714
|
async makeRequest(path, init, retryCount = 0, requestStartTime) {
|
|
459
715
|
const MAX_RETRY_ATTEMPTS = 1;
|
|
460
|
-
const url2 = this.buildUrl(path);
|
|
716
|
+
const url2 = withoutBareQuery(this.buildUrl(path));
|
|
461
717
|
const startTime = requestStartTime ?? Date.now();
|
|
462
718
|
const signal = this.createAbortSignal(init);
|
|
463
719
|
const headers = await this.buildHeaders(init?.headers);
|
|
@@ -482,7 +738,7 @@ var WebHttpClient = class {
|
|
|
482
738
|
headersObj,
|
|
483
739
|
await this.transport.authorizeRequest({
|
|
484
740
|
method,
|
|
485
|
-
path:
|
|
741
|
+
path: signedPath(url2),
|
|
486
742
|
// Narrowed by the guard above; `BodyInit` is wider than what can be
|
|
487
743
|
// signed.
|
|
488
744
|
body
|
|
@@ -542,15 +798,20 @@ var WebHttpClient = class {
|
|
|
542
798
|
if (this.transport.getProof) {
|
|
543
799
|
const proof = await this.transport.getProof({
|
|
544
800
|
method: requestInit.method ?? "GET",
|
|
545
|
-
path:
|
|
801
|
+
path: signedPath(url2),
|
|
546
802
|
body: requestInit.body
|
|
547
803
|
});
|
|
548
804
|
if (proof) {
|
|
549
805
|
headersObj["X-Calimero-Proof"] = proof;
|
|
550
806
|
}
|
|
551
807
|
}
|
|
808
|
+
const proofAttached = carriesProof(headersObj);
|
|
809
|
+
if (proofAttached && requestInit.redirect !== "error") {
|
|
810
|
+
requestInit.redirect = "manual";
|
|
811
|
+
}
|
|
552
812
|
try {
|
|
553
813
|
const response = await this.transport.fetch(url2, requestInit);
|
|
814
|
+
if (proofAttached) refuseProofRedirect(response, url2);
|
|
554
815
|
if (!response.ok) {
|
|
555
816
|
const bodyText = await this.getBodyText(response);
|
|
556
817
|
const httpError = new HTTPError(
|
|
@@ -2723,204 +2984,123 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
|
|
|
2723
2984
|
return new AdminApiClient(httpClient);
|
|
2724
2985
|
}
|
|
2725
2986
|
|
|
2726
|
-
// src/
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
const accessToken = params.get("access_token");
|
|
2734
|
-
if (!accessToken) return null;
|
|
2735
|
-
return {
|
|
2736
|
-
accessToken,
|
|
2737
|
-
refreshToken: params.get("refresh_token") ?? "",
|
|
2738
|
-
applicationId: params.get("application_id") ?? "",
|
|
2739
|
-
contextId: params.get("context_id") ?? "",
|
|
2740
|
-
contextIdentity: params.get("context_identity") ?? "",
|
|
2741
|
-
nodeUrl: params.get("node_url") ?? ""
|
|
2742
|
-
};
|
|
2743
|
-
} catch {
|
|
2744
|
-
return null;
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
function buildAuthLoginUrl(nodeUrl, opts) {
|
|
2748
|
-
const params = new URLSearchParams();
|
|
2749
|
-
params.set("callback-url", opts.callbackUrl);
|
|
2750
|
-
if (opts.permissions && opts.permissions.length > 0) {
|
|
2751
|
-
params.set("permissions", opts.permissions.join(","));
|
|
2752
|
-
}
|
|
2753
|
-
params.set("mode", opts.mode);
|
|
2754
|
-
if (opts.packageName) {
|
|
2755
|
-
params.set("package-name", opts.packageName);
|
|
2756
|
-
if (opts.packageVersion) {
|
|
2757
|
-
params.set("package-version", opts.packageVersion);
|
|
2758
|
-
}
|
|
2759
|
-
if (opts.registryUrl) {
|
|
2760
|
-
params.set("registry-url", opts.registryUrl);
|
|
2761
|
-
}
|
|
2762
|
-
}
|
|
2763
|
-
const base = nodeUrl.replace(/\/+$/, "");
|
|
2764
|
-
return `${base}/auth/login?${params.toString()}`;
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
// src/crypto/internal.ts
|
|
2768
|
-
var PKCS8_ED25519_PREFIX = new Uint8Array([
|
|
2769
|
-
48,
|
|
2770
|
-
46,
|
|
2771
|
-
2,
|
|
2772
|
-
1,
|
|
2773
|
-
0,
|
|
2774
|
-
48,
|
|
2775
|
-
5,
|
|
2776
|
-
6,
|
|
2777
|
-
3,
|
|
2778
|
-
43,
|
|
2779
|
-
101,
|
|
2780
|
-
112,
|
|
2781
|
-
4,
|
|
2782
|
-
34,
|
|
2783
|
-
4,
|
|
2784
|
-
32
|
|
2785
|
-
]);
|
|
2786
|
-
function u32le(value) {
|
|
2787
|
-
const out = new Uint8Array(4);
|
|
2788
|
-
new DataView(out.buffer).setUint32(0, value, true);
|
|
2789
|
-
return out;
|
|
2790
|
-
}
|
|
2791
|
-
function hex(bytes) {
|
|
2792
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2793
|
-
}
|
|
2794
|
-
function fromHex(value, label2, expectedBytes) {
|
|
2795
|
-
const clean = value.trim();
|
|
2796
|
-
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
|
|
2797
|
-
throw new Error(
|
|
2798
|
-
`${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
|
|
2799
|
-
);
|
|
2800
|
-
}
|
|
2801
|
-
const bytes = new Uint8Array(expectedBytes);
|
|
2802
|
-
for (let i = 0; i < expectedBytes; i += 1) {
|
|
2803
|
-
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
2804
|
-
}
|
|
2805
|
-
return bytes;
|
|
2806
|
-
}
|
|
2807
|
-
function fromHexUnsized(value, label2) {
|
|
2808
|
-
const clean = value.trim();
|
|
2809
|
-
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
2810
|
-
throw new Error(`${label2} must be an even number of hex characters`);
|
|
2811
|
-
}
|
|
2812
|
-
const bytes = new Uint8Array(clean.length / 2);
|
|
2813
|
-
for (let i = 0; i < bytes.length; i += 1) {
|
|
2814
|
-
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
2815
|
-
}
|
|
2816
|
-
return bytes;
|
|
2817
|
-
}
|
|
2818
|
-
function u64le(value) {
|
|
2819
|
-
const out = new Uint8Array(8);
|
|
2820
|
-
new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
|
|
2821
|
-
return out;
|
|
2822
|
-
}
|
|
2823
|
-
function concat(...parts) {
|
|
2824
|
-
const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
|
|
2825
|
-
let offset = 0;
|
|
2826
|
-
for (const part of parts) {
|
|
2827
|
-
out.set(part, offset);
|
|
2828
|
-
offset += part.length;
|
|
2829
|
-
}
|
|
2830
|
-
return out;
|
|
2831
|
-
}
|
|
2832
|
-
async function domainHash(domain, parts) {
|
|
2833
|
-
const chunks = [u64le(domain.length), domain];
|
|
2834
|
-
for (const part of parts) {
|
|
2835
|
-
chunks.push(u64le(part.length), part);
|
|
2836
|
-
}
|
|
2837
|
-
return new Uint8Array(
|
|
2838
|
-
await crypto.subtle.digest("SHA-256", concat(...chunks))
|
|
2839
|
-
);
|
|
2840
|
-
}
|
|
2841
|
-
async function importSigningKey(secretHex) {
|
|
2842
|
-
const seed = fromHex(secretHex, "deviceSecret", 32);
|
|
2843
|
-
const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
|
|
2987
|
+
// src/cloud/link-state.ts
|
|
2988
|
+
var KEY = "calimero.cloud.link.pending";
|
|
2989
|
+
var STATE_PARAM = "link_state";
|
|
2990
|
+
var LIFETIME_MS = 10 * 60 * 1e3;
|
|
2991
|
+
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
2992
|
+
var MAX_PENDING = 20;
|
|
2993
|
+
function readPending(key, now) {
|
|
2844
2994
|
try {
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
);
|
|
2852
|
-
} catch (cause) {
|
|
2853
|
-
const detail = cause instanceof Error ? `: ${cause.message}` : "";
|
|
2854
|
-
throw new Error(
|
|
2855
|
-
"this runtime has no WebCrypto Ed25519, so a warrant cannot be signed here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; otherwise mint the warrant with `merod account warrant`" + detail
|
|
2995
|
+
const raw = localStorage.getItem(key);
|
|
2996
|
+
const pending = raw ? JSON.parse(raw) : {};
|
|
2997
|
+
return Object.fromEntries(
|
|
2998
|
+
Object.entries(pending).filter(
|
|
2999
|
+
([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
|
|
3000
|
+
)
|
|
2856
3001
|
);
|
|
3002
|
+
} catch {
|
|
3003
|
+
return {};
|
|
2857
3004
|
}
|
|
2858
3005
|
}
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
false,
|
|
2865
|
-
["verify"]
|
|
2866
|
-
);
|
|
2867
|
-
return crypto.subtle.verify({ name: "Ed25519" }, key, signature, payload);
|
|
3006
|
+
function writePending(key, pending) {
|
|
3007
|
+
try {
|
|
3008
|
+
localStorage.setItem(key, JSON.stringify(pending));
|
|
3009
|
+
} catch {
|
|
3010
|
+
}
|
|
2868
3011
|
}
|
|
2869
|
-
|
|
2870
|
-
const
|
|
2871
|
-
const
|
|
2872
|
-
const
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
{ name: "Ed25519" },
|
|
2876
|
-
true,
|
|
2877
|
-
["sign"]
|
|
2878
|
-
);
|
|
2879
|
-
const jwk = await crypto.subtle.exportKey("jwk", key);
|
|
2880
|
-
const raw = jwk.x;
|
|
2881
|
-
return Uint8Array.from(
|
|
2882
|
-
atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
|
|
2883
|
-
(c) => c.charCodeAt(0)
|
|
3012
|
+
function beginState(key, payload) {
|
|
3013
|
+
const now = Date.now();
|
|
3014
|
+
const pending = readPending(key, now);
|
|
3015
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
3016
|
+
const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
3017
|
+
""
|
|
2884
3018
|
);
|
|
3019
|
+
pending[state] = { ...payload, expiresAt: now + LIFETIME_MS };
|
|
3020
|
+
writePending(key, Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
|
|
3021
|
+
return state;
|
|
3022
|
+
}
|
|
3023
|
+
function takeState(key, state) {
|
|
3024
|
+
const pending = readPending(key, Date.now());
|
|
3025
|
+
if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
|
|
3026
|
+
return null;
|
|
3027
|
+
const entry = pending[state];
|
|
3028
|
+
delete pending[state];
|
|
3029
|
+
writePending(key, pending);
|
|
3030
|
+
return entry;
|
|
3031
|
+
}
|
|
3032
|
+
function beginLinkState(account, returnTo) {
|
|
3033
|
+
if (!ACCOUNT_HEX.test(account)) {
|
|
3034
|
+
throw new Error("accountId must be 64 lowercase hex characters");
|
|
3035
|
+
}
|
|
3036
|
+
const url2 = new URL(returnTo);
|
|
3037
|
+
url2.searchParams.set(STATE_PARAM, beginState(KEY, { account }));
|
|
3038
|
+
return url2.toString();
|
|
3039
|
+
}
|
|
3040
|
+
function takeLinkState(search) {
|
|
3041
|
+
return takeState(KEY, new URLSearchParams(search).get(STATE_PARAM));
|
|
3042
|
+
}
|
|
3043
|
+
function withoutLinkState(search) {
|
|
3044
|
+
const params = new URLSearchParams(search);
|
|
3045
|
+
params.delete(STATE_PARAM);
|
|
3046
|
+
const rest = params.toString();
|
|
3047
|
+
return rest ? `?${rest}` : "";
|
|
2885
3048
|
}
|
|
2886
3049
|
|
|
2887
|
-
// src/
|
|
2888
|
-
var
|
|
2889
|
-
var
|
|
2890
|
-
function
|
|
2891
|
-
|
|
2892
|
-
|
|
3050
|
+
// src/auth/index.ts
|
|
3051
|
+
var PENDING_KEY = "calimero.auth.login.pending";
|
|
3052
|
+
var STATE_PARAM2 = "auth_state";
|
|
3053
|
+
function parseAuthCallback(url2, options = {}) {
|
|
3054
|
+
try {
|
|
3055
|
+
const hashIndex = url2.indexOf("#");
|
|
3056
|
+
if (hashIndex === -1) return null;
|
|
3057
|
+
const hash = url2.substring(hashIndex + 1);
|
|
3058
|
+
const params = new URLSearchParams(hash);
|
|
3059
|
+
const accessToken = params.get("access_token");
|
|
3060
|
+
if (!accessToken) return null;
|
|
3061
|
+
if (!options.allowUnsolicited && !takeState(PENDING_KEY, stateOf(url2, hashIndex))) {
|
|
3062
|
+
console.warn(
|
|
3063
|
+
"[mero-js] ignoring auth callback tokens: they answer no login begun in this browser"
|
|
3064
|
+
);
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
3067
|
+
return {
|
|
3068
|
+
accessToken,
|
|
3069
|
+
refreshToken: params.get("refresh_token") ?? "",
|
|
3070
|
+
applicationId: params.get("application_id") ?? "",
|
|
3071
|
+
contextId: params.get("context_id") ?? "",
|
|
3072
|
+
contextIdentity: params.get("context_identity") ?? "",
|
|
3073
|
+
nodeUrl: params.get("node_url") ?? ""
|
|
3074
|
+
};
|
|
3075
|
+
} catch {
|
|
3076
|
+
return null;
|
|
3077
|
+
}
|
|
2893
3078
|
}
|
|
2894
|
-
|
|
2895
|
-
const
|
|
2896
|
-
|
|
3079
|
+
function stateOf(url2, hashIndex) {
|
|
3080
|
+
const queryIndex = url2.indexOf("?");
|
|
3081
|
+
if (queryIndex === -1 || queryIndex > hashIndex) return null;
|
|
3082
|
+
return new URLSearchParams(url2.substring(queryIndex + 1, hashIndex)).get(STATE_PARAM2);
|
|
2897
3083
|
}
|
|
2898
|
-
|
|
2899
|
-
const
|
|
2900
|
-
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
bodyHash,
|
|
2919
|
-
issuedAt,
|
|
2920
|
-
expiresAt,
|
|
2921
|
-
signature
|
|
2922
|
-
)
|
|
2923
|
-
);
|
|
3084
|
+
function buildAuthLoginUrl(nodeUrl, opts) {
|
|
3085
|
+
const callback = new URL(opts.callbackUrl);
|
|
3086
|
+
callback.searchParams.set(STATE_PARAM2, beginState(PENDING_KEY, {}));
|
|
3087
|
+
const params = new URLSearchParams();
|
|
3088
|
+
params.set("callback-url", callback.toString());
|
|
3089
|
+
if (opts.permissions && opts.permissions.length > 0) {
|
|
3090
|
+
params.set("permissions", opts.permissions.join(","));
|
|
3091
|
+
}
|
|
3092
|
+
params.set("mode", opts.mode);
|
|
3093
|
+
if (opts.packageName) {
|
|
3094
|
+
params.set("package-name", opts.packageName);
|
|
3095
|
+
if (opts.packageVersion) {
|
|
3096
|
+
params.set("package-version", opts.packageVersion);
|
|
3097
|
+
}
|
|
3098
|
+
if (opts.registryUrl) {
|
|
3099
|
+
params.set("registry-url", opts.registryUrl);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
const base = nodeUrl.replace(/\/+$/, "");
|
|
3103
|
+
return `${base}/auth/login?${params.toString()}`;
|
|
2924
3104
|
}
|
|
2925
3105
|
|
|
2926
3106
|
// src/request/caller-proof.ts
|
|
@@ -3151,13 +3331,17 @@ var SseClient = class {
|
|
|
3151
3331
|
this.abortController = new AbortController();
|
|
3152
3332
|
try {
|
|
3153
3333
|
const url2 = `${this.baseUrl}/sse`;
|
|
3334
|
+
const headers = {
|
|
3335
|
+
...await this.authorize({ method: "GET", path: signedPath(url2) }),
|
|
3336
|
+
"Accept": "text/event-stream"
|
|
3337
|
+
};
|
|
3338
|
+
const proofAttached = carriesProof(headers);
|
|
3154
3339
|
const response = await this.fetchImpl(url2, {
|
|
3155
|
-
headers
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
},
|
|
3159
|
-
signal: this.abortController.signal
|
|
3340
|
+
headers,
|
|
3341
|
+
signal: this.abortController.signal,
|
|
3342
|
+
...proofAttached && { redirect: "manual" }
|
|
3160
3343
|
});
|
|
3344
|
+
if (proofAttached) refuseProofRedirect(response, url2);
|
|
3161
3345
|
if (!response.ok) {
|
|
3162
3346
|
const terminal = isTerminalAuthFailure(response);
|
|
3163
3347
|
const bodyText = await response.text().catch(() => void 0);
|
|
@@ -3355,15 +3539,19 @@ var SseClient = class {
|
|
|
3355
3539
|
if (ids.groupIds && ids.groupIds.length > 0) params.groupIds = ids.groupIds;
|
|
3356
3540
|
const url2 = `${this.baseUrl}/sse/subscription`;
|
|
3357
3541
|
const body = JSON.stringify({ id: this.sessionId, method, params });
|
|
3542
|
+
const headers = {
|
|
3543
|
+
...await this.authorize({ method: "POST", path: signedPath(url2), body }),
|
|
3544
|
+
"Content-Type": "application/json"
|
|
3545
|
+
};
|
|
3546
|
+
const proofAttached = carriesProof(headers);
|
|
3358
3547
|
const response = await this.fetchImpl(url2, {
|
|
3359
3548
|
method: "POST",
|
|
3360
|
-
headers
|
|
3361
|
-
...await this.authorize({ method: "POST", path: new URL(url2).pathname, body }),
|
|
3362
|
-
"Content-Type": "application/json"
|
|
3363
|
-
},
|
|
3549
|
+
headers,
|
|
3364
3550
|
body,
|
|
3365
|
-
signal: timeout.signal
|
|
3551
|
+
signal: timeout.signal,
|
|
3552
|
+
...proofAttached && { redirect: "manual" }
|
|
3366
3553
|
});
|
|
3554
|
+
if (proofAttached) refuseProofRedirect(response, url2);
|
|
3367
3555
|
if (!response.ok) {
|
|
3368
3556
|
const terminal = isTerminalAuthFailure(response);
|
|
3369
3557
|
const bodyText = await response.text().catch(() => void 0);
|
|
@@ -6483,7 +6671,7 @@ var MAX_HANDSHAKE_REPLY = 1024;
|
|
|
6483
6671
|
var RENEW_BEFORE_MS = 6e4;
|
|
6484
6672
|
var TRANSPORT_BINDING_DOMAIN = "calimero.tee-attest.transport-key.v1";
|
|
6485
6673
|
var NULL_BODY_STATUSES = [101, 204, 205, 304];
|
|
6486
|
-
var
|
|
6674
|
+
var utf82 = new TextEncoder();
|
|
6487
6675
|
var StaleTransportKeyError = class extends Error {
|
|
6488
6676
|
constructor() {
|
|
6489
6677
|
super("The node no longer holds the attested transport key; attest again for the current one");
|
|
@@ -6502,7 +6690,7 @@ var SealedTransportError = class extends Error {
|
|
|
6502
6690
|
async function transportKeyBinding(inner, transportKey) {
|
|
6503
6691
|
const digest = await crypto.subtle.digest(
|
|
6504
6692
|
"SHA-256",
|
|
6505
|
-
concat(
|
|
6693
|
+
concat(utf82.encode(TRANSPORT_BINDING_DOMAIN), inner, transportKey)
|
|
6506
6694
|
);
|
|
6507
6695
|
return new Uint8Array(digest);
|
|
6508
6696
|
}
|
|
@@ -6706,7 +6894,7 @@ function nonce(requestId, index) {
|
|
|
6706
6894
|
return bytes;
|
|
6707
6895
|
}
|
|
6708
6896
|
function joinInner(head, body) {
|
|
6709
|
-
const encoded =
|
|
6897
|
+
const encoded = utf82.encode(JSON.stringify(head));
|
|
6710
6898
|
const len = new Uint8Array(4);
|
|
6711
6899
|
new DataView(len.buffer).setUint32(0, encoded.length, false);
|
|
6712
6900
|
return concat(len, encoded, body);
|
|
@@ -7196,7 +7384,7 @@ var FULCIO_ISSUER_V1 = "1.3.6.1.4.1.57264.1.1";
|
|
|
7196
7384
|
var SAN_URI = 134;
|
|
7197
7385
|
var UTF8_STRING = 12;
|
|
7198
7386
|
var SEQUENCE2 = 48;
|
|
7199
|
-
var
|
|
7387
|
+
var utf83 = new TextEncoder();
|
|
7200
7388
|
var fromUtf8 = (bytes) => new TextDecoder().decode(bytes);
|
|
7201
7389
|
async function verifyCosignBundle(body, bundle, signer, signature) {
|
|
7202
7390
|
const { base64Signature, cert, rekorBundle } = cosignBundle(bundle);
|
|
@@ -7209,7 +7397,7 @@ async function verifyCosignBundle(body, bundle, signer, signature) {
|
|
|
7209
7397
|
const leaf = parseCertificate(leafDer);
|
|
7210
7398
|
const log = REKOR_LOGS.find((candidate) => candidate.logId === payload.logID.toLowerCase());
|
|
7211
7399
|
if (!log) throw new Error(`The bundle names Rekor log ${payload.logID}, which the Sigstore trust root does not know`);
|
|
7212
|
-
const canonical =
|
|
7400
|
+
const canonical = utf83.encode(
|
|
7213
7401
|
`{"body":${JSON.stringify(payload.body)},"integratedTime":${payload.integratedTime},"logID":${JSON.stringify(payload.logID)},"logIndex":${payload.logIndex}}`
|
|
7214
7402
|
);
|
|
7215
7403
|
const setVerifies = await verifyEcdsa(
|
|
@@ -8947,92 +9135,6 @@ var RelayObserver = class {
|
|
|
8947
9135
|
}
|
|
8948
9136
|
};
|
|
8949
9137
|
|
|
8950
|
-
// src/request-proof/request-proof.ts
|
|
8951
|
-
var utf83 = (text) => new TextEncoder().encode(text);
|
|
8952
|
-
var REQUEST_SIGN_DOMAIN = utf83("calimero.auth.request.v1");
|
|
8953
|
-
var REQUEST_BODY_DOMAIN = utf83("calimero.auth.request.body.v1");
|
|
8954
|
-
var REQUEST_PROOF_HEADER = "X-Calimero-Proof";
|
|
8955
|
-
var DEFAULT_VALID_FOR = 300;
|
|
8956
|
-
async function signRequestProof(input) {
|
|
8957
|
-
const { method, path, credential } = input;
|
|
8958
|
-
if (!method) throw new Error("signRequestProof needs the method the request will use");
|
|
8959
|
-
if (!path.startsWith("/")) {
|
|
8960
|
-
throw new Error(`signRequestProof needs a path, got ${JSON.stringify(path)}`);
|
|
8961
|
-
}
|
|
8962
|
-
if (path.includes("?")) {
|
|
8963
|
-
throw new Error(
|
|
8964
|
-
`the path must carry no query string, got ${JSON.stringify(path)} \u2014 a query is excluded from the signature on purpose, because a proxy may rewrite one. Anything that must be bound belongs in the body.`
|
|
8965
|
-
);
|
|
8966
|
-
}
|
|
8967
|
-
const bodyBytes2 = input.body === void 0 ? new Uint8Array() : typeof input.body === "string" ? utf83(input.body) : input.body;
|
|
8968
|
-
const bodyHash = await domainHash(REQUEST_BODY_DOMAIN, [bodyBytes2]);
|
|
8969
|
-
const issuedAt = Math.floor(input.now ?? Date.now() / 1e3);
|
|
8970
|
-
const expiresAt = issuedAt + (input.validForSeconds ?? DEFAULT_VALID_FOR);
|
|
8971
|
-
const payload = await domainHash(REQUEST_SIGN_DOMAIN, [
|
|
8972
|
-
utf83(method),
|
|
8973
|
-
utf83(path),
|
|
8974
|
-
bodyHash,
|
|
8975
|
-
u64le(issuedAt),
|
|
8976
|
-
u64le(expiresAt)
|
|
8977
|
-
]);
|
|
8978
|
-
const signature = await signPayload(payload, input);
|
|
8979
|
-
if (signature.length !== 64) {
|
|
8980
|
-
throw new Error(`the signer returned ${signature.length} bytes, not a 64-byte signature`);
|
|
8981
|
-
}
|
|
8982
|
-
const requestSig = concat(
|
|
8983
|
-
u32le(utf83(method).length),
|
|
8984
|
-
utf83(method),
|
|
8985
|
-
u32le(utf83(path).length),
|
|
8986
|
-
utf83(path),
|
|
8987
|
-
bodyHash,
|
|
8988
|
-
u64le(issuedAt),
|
|
8989
|
-
u64le(expiresAt),
|
|
8990
|
-
signature
|
|
8991
|
-
);
|
|
8992
|
-
const accountProof = hexToBytes(credential, "credential");
|
|
8993
|
-
return hex(concat(accountProof, new Uint8Array([0]), requestSig));
|
|
8994
|
-
}
|
|
8995
|
-
async function signPayload(payload, input) {
|
|
8996
|
-
if (input.signer) return input.signer.sign(payload);
|
|
8997
|
-
if (!input.deviceSecret) {
|
|
8998
|
-
throw new Error("signRequestProof needs either `deviceSecret` or `signer`");
|
|
8999
|
-
}
|
|
9000
|
-
const key = await importSigningKey(input.deviceSecret);
|
|
9001
|
-
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, key, payload));
|
|
9002
|
-
}
|
|
9003
|
-
function hexToBytes(value, label2) {
|
|
9004
|
-
const clean = value.trim();
|
|
9005
|
-
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
9006
|
-
throw new Error(`${label2} is not an even-length hex string`);
|
|
9007
|
-
}
|
|
9008
|
-
return fromHex(clean, label2, clean.length / 2);
|
|
9009
|
-
}
|
|
9010
|
-
|
|
9011
|
-
// src/request-proof/authorizer.ts
|
|
9012
|
-
function createProofAuthorizer(credential) {
|
|
9013
|
-
if (!credential.credential) {
|
|
9014
|
-
throw new Error("a proof authorizer needs the `credential` from signDeviceCert");
|
|
9015
|
-
}
|
|
9016
|
-
if (!credential.deviceSecret && !credential.signer) {
|
|
9017
|
-
throw new Error("a proof authorizer needs either `deviceSecret` or `signer`");
|
|
9018
|
-
}
|
|
9019
|
-
return async ({ method, path, body }) => {
|
|
9020
|
-
const proof = await signRequestProof({
|
|
9021
|
-
method,
|
|
9022
|
-
path,
|
|
9023
|
-
body,
|
|
9024
|
-
credential: credential.credential,
|
|
9025
|
-
deviceSecret: credential.deviceSecret,
|
|
9026
|
-
signer: credential.signer,
|
|
9027
|
-
validForSeconds: credential.validForSeconds
|
|
9028
|
-
});
|
|
9029
|
-
return { [REQUEST_PROOF_HEADER]: proof };
|
|
9030
|
-
};
|
|
9031
|
-
}
|
|
9032
|
-
function signedPath(url2) {
|
|
9033
|
-
return new URL(url2).pathname;
|
|
9034
|
-
}
|
|
9035
|
-
|
|
9036
9138
|
// src/presence/statement.ts
|
|
9037
9139
|
var PRESENCE_DOMAIN = new TextEncoder().encode("calimero/presence/1");
|
|
9038
9140
|
async function stateHash(state) {
|
|
@@ -9399,12 +9501,12 @@ function createMeroClient(config) {
|
|
|
9399
9501
|
}
|
|
9400
9502
|
|
|
9401
9503
|
// src/account/session.ts
|
|
9402
|
-
var
|
|
9504
|
+
var KEY2 = "calimero.delegated.connection";
|
|
9403
9505
|
var CREDENTIAL_KEY = "calimero.delegated.credential";
|
|
9404
9506
|
function readDelegatedSession() {
|
|
9405
9507
|
if (typeof sessionStorage === "undefined") return null;
|
|
9406
9508
|
try {
|
|
9407
|
-
const raw = sessionStorage.getItem(
|
|
9509
|
+
const raw = sessionStorage.getItem(KEY2);
|
|
9408
9510
|
return raw ? JSON.parse(raw) : null;
|
|
9409
9511
|
} catch {
|
|
9410
9512
|
return null;
|
|
@@ -9412,13 +9514,13 @@ function readDelegatedSession() {
|
|
|
9412
9514
|
}
|
|
9413
9515
|
function saveDelegatedSession(s) {
|
|
9414
9516
|
try {
|
|
9415
|
-
sessionStorage.setItem(
|
|
9517
|
+
sessionStorage.setItem(KEY2, JSON.stringify(s));
|
|
9416
9518
|
} catch {
|
|
9417
9519
|
}
|
|
9418
9520
|
}
|
|
9419
9521
|
function clearDelegatedSession() {
|
|
9420
9522
|
try {
|
|
9421
|
-
sessionStorage.removeItem(
|
|
9523
|
+
sessionStorage.removeItem(KEY2);
|
|
9422
9524
|
} catch {
|
|
9423
9525
|
}
|
|
9424
9526
|
}
|
|
@@ -10216,64 +10318,6 @@ function base643(bytes) {
|
|
|
10216
10318
|
return btoa(binary);
|
|
10217
10319
|
}
|
|
10218
10320
|
|
|
10219
|
-
// src/cloud/link-state.ts
|
|
10220
|
-
var KEY2 = "calimero.cloud.link.pending";
|
|
10221
|
-
var STATE_PARAM = "link_state";
|
|
10222
|
-
var LIFETIME_MS = 10 * 60 * 1e3;
|
|
10223
|
-
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
10224
|
-
var MAX_PENDING = 20;
|
|
10225
|
-
function readPending(now) {
|
|
10226
|
-
try {
|
|
10227
|
-
const raw = localStorage.getItem(KEY2);
|
|
10228
|
-
const pending = raw ? JSON.parse(raw) : {};
|
|
10229
|
-
return Object.fromEntries(
|
|
10230
|
-
Object.entries(pending).filter(
|
|
10231
|
-
([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
|
|
10232
|
-
)
|
|
10233
|
-
);
|
|
10234
|
-
} catch {
|
|
10235
|
-
return {};
|
|
10236
|
-
}
|
|
10237
|
-
}
|
|
10238
|
-
function writePending(pending) {
|
|
10239
|
-
try {
|
|
10240
|
-
localStorage.setItem(KEY2, JSON.stringify(pending));
|
|
10241
|
-
} catch {
|
|
10242
|
-
}
|
|
10243
|
-
}
|
|
10244
|
-
function beginLinkState(account, returnTo) {
|
|
10245
|
-
if (!ACCOUNT_HEX.test(account)) {
|
|
10246
|
-
throw new Error("accountId must be 64 lowercase hex characters");
|
|
10247
|
-
}
|
|
10248
|
-
const url2 = new URL(returnTo);
|
|
10249
|
-
const now = Date.now();
|
|
10250
|
-
const pending = readPending(now);
|
|
10251
|
-
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
10252
|
-
const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
10253
|
-
""
|
|
10254
|
-
);
|
|
10255
|
-
pending[state] = { account, expiresAt: now + LIFETIME_MS };
|
|
10256
|
-
writePending(Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
|
|
10257
|
-
url2.searchParams.set(STATE_PARAM, state);
|
|
10258
|
-
return url2.toString();
|
|
10259
|
-
}
|
|
10260
|
-
function takeLinkState(search) {
|
|
10261
|
-
const state = new URLSearchParams(search).get(STATE_PARAM);
|
|
10262
|
-
const pending = readPending(Date.now());
|
|
10263
|
-
if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
|
|
10264
|
-
return null;
|
|
10265
|
-
const entry = pending[state];
|
|
10266
|
-
delete pending[state];
|
|
10267
|
-
writePending(pending);
|
|
10268
|
-
return entry;
|
|
10269
|
-
}
|
|
10270
|
-
function withoutLinkState(search) {
|
|
10271
|
-
const params = new URLSearchParams(search);
|
|
10272
|
-
params.delete(STATE_PARAM);
|
|
10273
|
-
const rest = params.toString();
|
|
10274
|
-
return rest ? `?${rest}` : "";
|
|
10275
|
-
}
|
|
10276
|
-
|
|
10277
10321
|
// src/cloud/link-redirect.ts
|
|
10278
10322
|
var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
|
|
10279
10323
|
function cloudLinkUrl(options) {
|
|
@@ -10330,7 +10374,8 @@ async function completeCloudLink(options) {
|
|
|
10330
10374
|
var DEVICE_PARAM = "enrol-device";
|
|
10331
10375
|
var KEM_PARAM = "enrol-kem";
|
|
10332
10376
|
var CALLBACK_PARAM = "callback-url";
|
|
10333
|
-
var
|
|
10377
|
+
var STATE_PARAM3 = "state";
|
|
10378
|
+
var PENDING_KEY2 = "calimero.device.enrol.pending";
|
|
10334
10379
|
function deviceEnrolmentUrl(options) {
|
|
10335
10380
|
assertHex(options.devicePublicKey, "devicePublicKey");
|
|
10336
10381
|
assertHex(options.kemPublicKey, "kemPublicKey");
|
|
@@ -10340,7 +10385,7 @@ function deviceEnrolmentUrl(options) {
|
|
|
10340
10385
|
url2.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
|
|
10341
10386
|
url2.searchParams.set(KEM_PARAM, options.kemPublicKey);
|
|
10342
10387
|
url2.searchParams.set(CALLBACK_PARAM, options.returnTo);
|
|
10343
|
-
|
|
10388
|
+
url2.searchParams.set(STATE_PARAM3, beginState(PENDING_KEY2, {}));
|
|
10344
10389
|
return url2.toString();
|
|
10345
10390
|
}
|
|
10346
10391
|
function beginDeviceEnrolment(options) {
|
|
@@ -10351,6 +10396,7 @@ function readEnrolmentCallback(location2 = window.location) {
|
|
|
10351
10396
|
const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
|
|
10352
10397
|
const error = params.get("error");
|
|
10353
10398
|
if (error) {
|
|
10399
|
+
takeState(PENDING_KEY2, params.get(STATE_PARAM3));
|
|
10354
10400
|
stripFragment2();
|
|
10355
10401
|
throw new Error(
|
|
10356
10402
|
error === "denied" || error === "cancelled" ? "the device was not approved at the wallet" : `the wallet refused this enrolment: ${error}`
|
|
@@ -10369,9 +10415,9 @@ function stripFragment2() {
|
|
|
10369
10415
|
history.replaceState(null, "", location.pathname + location.search);
|
|
10370
10416
|
}
|
|
10371
10417
|
async function completeDeviceEnrolment(options) {
|
|
10372
|
-
if (
|
|
10418
|
+
if (!takeState(PENDING_KEY2, options.state)) {
|
|
10373
10419
|
throw new Error(
|
|
10374
|
-
"this enrolment answers a request this app did not make
|
|
10420
|
+
"this enrolment answers a request this app did not make - start the enrolment again from here"
|
|
10375
10421
|
);
|
|
10376
10422
|
}
|
|
10377
10423
|
const certificate = await verifyDeviceCredential(options.credential);
|
|
@@ -12764,8 +12810,8 @@ var MeroJs = class {
|
|
|
12764
12810
|
/**
|
|
12765
12811
|
* Parse an auth callback URL hash fragment (static utility)
|
|
12766
12812
|
*/
|
|
12767
|
-
static parseAuthCallback(url2) {
|
|
12768
|
-
return parseAuthCallback(url2);
|
|
12813
|
+
static parseAuthCallback(url2, options) {
|
|
12814
|
+
return parseAuthCallback(url2, options);
|
|
12769
12815
|
}
|
|
12770
12816
|
/**
|
|
12771
12817
|
* Build an auth login URL (static utility)
|