@tinycloud/sdk-core 3.0.0-beta.7 → 3.0.0-beta.9
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/delegations/index.cjs +24 -122
- package/dist/delegations/index.cjs.map +1 -1
- package/dist/delegations/index.d.cts +3 -3
- package/dist/delegations/index.d.ts +3 -3
- package/dist/delegations/index.js +25 -127
- package/dist/delegations/index.js.map +1 -1
- package/dist/index.cjs +211 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -5
- package/dist/index.d.ts +58 -5
- package/dist/index.js +213 -129
- package/dist/index.js.map +1 -1
- package/dist/{recipient-types.schema-DjmM_yJu.d.cts → recipient-types.schema-C0igQ1kA.d.cts} +6 -100
- package/dist/{recipient-types.schema-DjmM_yJu.d.ts → recipient-types.schema-C0igQ1kA.d.ts} +6 -100
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -8879,25 +8879,33 @@ function parsePublishedShareLink(link, options2 = {}) {
|
|
|
8879
8879
|
}
|
|
8880
8880
|
const origin2 = normalizeOrigin(url.origin);
|
|
8881
8881
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
8882
|
-
if (url.
|
|
8883
|
-
if (url.pathname === "/
|
|
8882
|
+
if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
8883
|
+
if (url.pathname === "/viewer") {
|
|
8884
|
+
const secretPayload = url.search === "" && url.hash.startsWith("#tc2=") ? url.hash.slice("#tc2=".length) : void 0;
|
|
8885
|
+
const publicPayload = url.hash === "" && url.searchParams.size === 1 ? url.searchParams.get("tc2") ?? void 0 : void 0;
|
|
8886
|
+
if (secretPayload === void 0 && publicPayload === void 0) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
8887
|
+
if (publicPayload !== void 0 && url.search !== `?tc2=${publicPayload}`) throw new ShareEnvelopeError("invalid-link", "published share link is not canonical");
|
|
8884
8888
|
let payload;
|
|
8885
8889
|
try {
|
|
8886
|
-
const bytes2 = base64UrlToBytes(
|
|
8890
|
+
const bytes2 = base64UrlToBytes(secretPayload ?? publicPayload);
|
|
8887
8891
|
if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
|
|
8888
|
-
const
|
|
8892
|
+
const text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
8893
|
+
const value = JSON.parse(text2);
|
|
8889
8894
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
|
|
8895
|
+
if (canonicalJson(value) !== text2) throw new Error("inline payload is not canonical JSON");
|
|
8890
8896
|
payload = value;
|
|
8891
8897
|
} catch (error) {
|
|
8892
8898
|
throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
|
|
8893
8899
|
}
|
|
8894
|
-
|
|
8900
|
+
const payloadKeys = Object.keys(payload);
|
|
8901
|
+
if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || payloadKeys.some((key3) => !["v", "c", "cid", "k"].includes(key3)) || publicPayload !== void 0 && (payloadKeys.length !== 3 || payload.k !== void 0)) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
|
|
8895
8902
|
const sealed = base64UrlToBytes(payload.c);
|
|
8896
8903
|
if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
|
|
8897
8904
|
const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
|
|
8898
8905
|
if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
|
|
8899
8906
|
return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
|
|
8900
8907
|
}
|
|
8908
|
+
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
8901
8909
|
const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
|
|
8902
8910
|
if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
8903
8911
|
const key = base64UrlToBytes(url.hash.slice(3));
|
|
@@ -8917,13 +8925,13 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
8917
8925
|
} catch {
|
|
8918
8926
|
throw new ShareEnvelopeError("invalid-link", "share link must be an absolute URL");
|
|
8919
8927
|
}
|
|
8920
|
-
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
8921
8928
|
const origin2 = normalizeOrigin(url.origin);
|
|
8922
8929
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
8923
|
-
if (url.pathname === "/
|
|
8930
|
+
if (url.pathname === "/viewer" && (url.hash.startsWith("#tc2=") || url.searchParams.has("tc2"))) {
|
|
8924
8931
|
const published = parsePublishedShareLink(link, options2);
|
|
8925
8932
|
return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
|
|
8926
8933
|
}
|
|
8934
|
+
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
8927
8935
|
const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
|
|
8928
8936
|
if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
|
|
8929
8937
|
const fragmentParams = url.hash === "" ? void 0 : new URLSearchParams(url.hash.slice(1));
|
|
@@ -8946,7 +8954,8 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
8946
8954
|
}
|
|
8947
8955
|
function isV2ShareLink(link) {
|
|
8948
8956
|
try {
|
|
8949
|
-
|
|
8957
|
+
const url = new URL(link);
|
|
8958
|
+
return url.pathname.startsWith("/s/") || url.pathname === "/viewer";
|
|
8950
8959
|
} catch {
|
|
8951
8960
|
return link.startsWith(INLINE_PREFIX);
|
|
8952
8961
|
}
|
|
@@ -11759,114 +11768,13 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
|
11759
11768
|
return validateOwnerSharePolicyRegistration(value, expected);
|
|
11760
11769
|
}
|
|
11761
11770
|
|
|
11762
|
-
// src/delegations/share-delivery.ts
|
|
11763
|
-
import { ed25519 as ed255199 } from "@noble/curves/ed25519";
|
|
11764
|
-
var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
|
|
11765
|
-
function isCanonicalHttpsOrigin(value) {
|
|
11766
|
-
let url;
|
|
11767
|
-
try {
|
|
11768
|
-
url = new URL(value);
|
|
11769
|
-
} catch {
|
|
11770
|
-
return false;
|
|
11771
|
-
}
|
|
11772
|
-
return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
11773
|
-
}
|
|
11774
|
-
var AUTHORIZATION_KEYS = [
|
|
11775
|
-
"type",
|
|
11776
|
-
"version",
|
|
11777
|
-
"jti",
|
|
11778
|
-
"shareCid",
|
|
11779
|
-
"shareId",
|
|
11780
|
-
"registrationCid",
|
|
11781
|
-
"delegationCid",
|
|
11782
|
-
"enforcementDelegationCid",
|
|
11783
|
-
"envelopeCid",
|
|
11784
|
-
"policyCid",
|
|
11785
|
-
"nodeAudience",
|
|
11786
|
-
"targetOrigin",
|
|
11787
|
-
"openCredentialsAudience",
|
|
11788
|
-
"holder",
|
|
11789
|
-
"recipientMatcher",
|
|
11790
|
-
"deliveryEmail",
|
|
11791
|
-
"shareUrl",
|
|
11792
|
-
"returnOrigin",
|
|
11793
|
-
"documentName",
|
|
11794
|
-
"senderDid",
|
|
11795
|
-
"senderTrust",
|
|
11796
|
-
"contentSource",
|
|
11797
|
-
"contentSourceDigest",
|
|
11798
|
-
"shareExpiresAt",
|
|
11799
|
-
"issuedAt",
|
|
11800
|
-
"reportAbuseToken",
|
|
11801
|
-
"actions",
|
|
11802
|
-
"resource",
|
|
11803
|
-
"authorityMaterialHandle",
|
|
11804
|
-
"authorityMaterialDigest",
|
|
11805
|
-
"requestBodyDigest",
|
|
11806
|
-
"idempotencyKey",
|
|
11807
|
-
"expiresAt",
|
|
11808
|
-
"dataAuthority"
|
|
11809
|
-
];
|
|
11810
|
-
function assertObject2(value, keys, label) {
|
|
11811
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
11812
|
-
const record2 = value;
|
|
11813
|
-
if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
|
|
11814
|
-
return record2;
|
|
11815
|
-
}
|
|
11816
|
-
function fromB642(value) {
|
|
11817
|
-
if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
|
|
11818
|
-
if (typeof atob === "function") {
|
|
11819
|
-
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
11820
|
-
return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
11821
|
-
}
|
|
11822
|
-
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
|
|
11823
|
-
throw new Error("base64url decoding is unavailable");
|
|
11824
|
-
}
|
|
11825
|
-
function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
|
|
11826
|
-
let value;
|
|
11827
|
-
try {
|
|
11828
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
11829
|
-
} catch {
|
|
11830
|
-
throw new Error("share delivery authorization response is not valid UTF-8 JSON");
|
|
11831
|
-
}
|
|
11832
|
-
const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
|
|
11833
|
-
const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
|
|
11834
|
-
const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
|
|
11835
|
-
const request = expected.request;
|
|
11836
|
-
if (authorization.type !== "TinyCloudShareDeliveryAuthorization" || authorization.version !== 2 || authorization.dataAuthority !== false || authorization.jti !== request.jti || authorization.shareCid !== request.shareCid || authorization.shareId !== request.shareId || authorization.registrationCid !== request.registrationCid || authorization.delegationCid !== request.delegationCid || authorization.enforcementDelegationCid !== request.enforcementDelegationCid || authorization.envelopeCid !== request.envelopeCid || authorization.policyCid !== request.policyCid || authorization.deliveryEmail !== request.recipientEmail || authorization.shareUrl !== request.shareUrl || authorization.documentName !== request.documentName || authorization.requestBodyDigest !== request.requestBodyDigest || authorization.expiresAt !== request.expiresAt || authorization.idempotencyKey !== request.idempotencyKey || authorization.reportAbuseToken !== request.jti || authorization.authorityMaterialHandle !== request.registrationCid) {
|
|
11837
|
-
throw new Error("share delivery authorization is not bound to the submitted request");
|
|
11838
|
-
}
|
|
11839
|
-
if (typeof authorization.issuedAt !== "string" || Date.parse(authorization.issuedAt) > Date.now() + 6e4) throw new Error("share delivery authorization issuedAt is invalid");
|
|
11840
|
-
if (new Date(authorization.expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
|
|
11841
|
-
if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
|
|
11842
|
-
throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
|
|
11843
|
-
}
|
|
11844
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
|
|
11845
|
-
if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
|
|
11846
|
-
const encodedKid = expected.nodeProof.publicKey;
|
|
11847
|
-
const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
|
|
11848
|
-
if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
|
|
11849
|
-
const signatureBytes = fromB642(proof.signature);
|
|
11850
|
-
const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalizeSignedObjectUnsigned(authorization)}`);
|
|
11851
|
-
if (signatureBytes.length !== 64 || !ed255199.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
|
|
11852
|
-
return { authorization, proof };
|
|
11853
|
-
}
|
|
11854
|
-
function shareDeliveryTrustedKid(publicKey) {
|
|
11855
|
-
if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
|
|
11856
|
-
const prefixed = new Uint8Array(34);
|
|
11857
|
-
prefixed.set([237, 1]);
|
|
11858
|
-
prefixed.set(publicKey, 2);
|
|
11859
|
-
const did = `did:key:${base58btc.encode(prefixed)}`;
|
|
11860
|
-
return `${did}#${did.slice("did:key:".length)}`;
|
|
11861
|
-
}
|
|
11862
|
-
|
|
11863
11771
|
// src/delegations/share-delivery-v3.ts
|
|
11864
|
-
import { ed25519 as
|
|
11772
|
+
import { ed25519 as ed255199 } from "@noble/curves/ed25519";
|
|
11865
11773
|
import { sha256 as sha25611 } from "@noble/hashes/sha256";
|
|
11866
11774
|
var CREDENTIAL_INVITATION_REQUEST_DOMAIN = "xyz.tinycloud.credentials/invitation-request/v1\0";
|
|
11867
11775
|
var DELIVERY_ADMISSION_DOMAIN = "xyz.tinycloud.policy/delivery-admission/v0\0";
|
|
11868
|
-
var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
11869
|
-
var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
|
|
11776
|
+
var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
11777
|
+
var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
|
|
11870
11778
|
function exactObject2(value, keys, label) {
|
|
11871
11779
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
11872
11780
|
const record2 = value;
|
|
@@ -11910,15 +11818,16 @@ function validateShareDeliveryAuthorizationV3Bytes(bytes2, expected) {
|
|
|
11910
11818
|
const contentSource = object3(envelope.contentSource, "v3 content source");
|
|
11911
11819
|
const envelopeSignature = object3(envelope.signature, "v3 envelope signature");
|
|
11912
11820
|
const target = object3(envelope.target, "v3 target");
|
|
11913
|
-
const
|
|
11914
|
-
|
|
11821
|
+
const binding = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
11822
|
+
const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
11823
|
+
if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.label !== expected.request.documentName || request.shareExpiresAt !== envelope.expiry || request.audience !== expected.deliveryAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || target.nodeAudience !== binding.nodeAudience || signature.signerDid !== binding.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
|
|
11915
11824
|
const issuedAt = Date.parse(String(request.issuedAt));
|
|
11916
11825
|
const expiresAt = Date.parse(String(request.expiresAt));
|
|
11917
11826
|
if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || expiresAt <= issuedAt || expiresAt - issuedAt > 15 * 6e4) throw new Error("v3 share delivery authorization time is invalid");
|
|
11918
11827
|
const admissionSignature = decodeBase64Url3(signature.value);
|
|
11919
11828
|
const { signature: _signature, ...unsignedAdmission } = admission;
|
|
11920
11829
|
const digest4 = sha25611(new TextEncoder().encode(`${DELIVERY_ADMISSION_DOMAIN}${canonicalizeSignedObjectUnsigned(unsignedAdmission)}`));
|
|
11921
|
-
if (admissionSignature.length !== 64 || !
|
|
11830
|
+
if (admissionSignature.length !== 64 || !ed255199.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
|
|
11922
11831
|
return { request, admission };
|
|
11923
11832
|
}
|
|
11924
11833
|
|
|
@@ -12042,9 +11951,6 @@ import {
|
|
|
12042
11951
|
publishPolicyShare,
|
|
12043
11952
|
claimShare,
|
|
12044
11953
|
resumeShareAuthorization,
|
|
12045
|
-
isLegacyShareLink,
|
|
12046
|
-
receiveLegacyShare,
|
|
12047
|
-
migrateShare,
|
|
12048
11954
|
MemoryEncryptedShareHistoryStorage,
|
|
12049
11955
|
EncryptedSenderShareHistory
|
|
12050
11956
|
} from "@tinycloud/share-sdk";
|
|
@@ -12052,8 +11958,7 @@ import {
|
|
|
12052
11958
|
inspectShare,
|
|
12053
11959
|
receiveShare,
|
|
12054
11960
|
ShareReceiveError,
|
|
12055
|
-
toShareErrorInfo
|
|
12056
|
-
verifyBearerEnvelope
|
|
11961
|
+
toShareErrorInfo
|
|
12057
11962
|
} from "@tinycloud/share-sdk";
|
|
12058
11963
|
|
|
12059
11964
|
// src/networkId.ts
|
|
@@ -14786,6 +14691,7 @@ import {
|
|
|
14786
14691
|
SecretsService,
|
|
14787
14692
|
SECRET_NAME_RE as SECRET_NAME_RE2,
|
|
14788
14693
|
canonicalizeSecretScope,
|
|
14694
|
+
parseSecretCatalogKey,
|
|
14789
14695
|
resolveSecretListPrefix,
|
|
14790
14696
|
resolveSecretPath as resolveSecretPath2,
|
|
14791
14697
|
EncryptionService,
|
|
@@ -17688,7 +17594,7 @@ function portForProtocol(protocol) {
|
|
|
17688
17594
|
}
|
|
17689
17595
|
|
|
17690
17596
|
// src/location.ts
|
|
17691
|
-
import { ed25519 as
|
|
17597
|
+
import { ed25519 as ed2551910 } from "@noble/curves/ed25519";
|
|
17692
17598
|
import { verifyMessage as verifyMessage2 } from "viem";
|
|
17693
17599
|
var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
|
|
17694
17600
|
var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
|
|
@@ -17696,6 +17602,7 @@ var DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
|
|
|
17696
17602
|
var LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
|
|
17697
17603
|
var LOCAL_LINK_PROBE_TIMEOUT_MS = 750;
|
|
17698
17604
|
var LOCAL_LINK_HOST_SUFFIX = ".local.tinycloud.link";
|
|
17605
|
+
var OWNER_NODE_BINDING_TIMEOUT_MS = 5e3;
|
|
17699
17606
|
function createInMemoryLocalNodeIdentityStore() {
|
|
17700
17607
|
const pins = /* @__PURE__ */ new Map();
|
|
17701
17608
|
return {
|
|
@@ -17828,21 +17735,80 @@ async function verifyLocationRecord(input) {
|
|
|
17828
17735
|
}
|
|
17829
17736
|
return false;
|
|
17830
17737
|
}
|
|
17831
|
-
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
|
|
17738
|
+
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
|
|
17832
17739
|
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
17833
|
-
const response = await fetchFn(url
|
|
17740
|
+
const response = await fetchFn(url, {
|
|
17741
|
+
redirect: "error",
|
|
17742
|
+
headers: { accept: "application/json" },
|
|
17743
|
+
...signal === void 0 ? {} : { signal }
|
|
17744
|
+
});
|
|
17834
17745
|
if (response.status === 404) {
|
|
17835
17746
|
return null;
|
|
17836
17747
|
}
|
|
17837
17748
|
if (!response.ok) {
|
|
17838
17749
|
throw new Error(`location registry returned HTTP ${response.status}`);
|
|
17839
17750
|
}
|
|
17840
|
-
const
|
|
17751
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
17752
|
+
let body;
|
|
17753
|
+
try {
|
|
17754
|
+
body = JSON.parse(text2);
|
|
17755
|
+
} catch {
|
|
17756
|
+
throw new LocationRecordValidationError("registry response is not JSON");
|
|
17757
|
+
}
|
|
17841
17758
|
if (body.record === void 0) {
|
|
17842
17759
|
throw new LocationRecordValidationError("registry response missing record");
|
|
17843
17760
|
}
|
|
17844
17761
|
return validateLocationRecord(body.record);
|
|
17845
17762
|
}
|
|
17763
|
+
async function publishLocationRecord(options2) {
|
|
17764
|
+
validateSubject(options2.subject);
|
|
17765
|
+
const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
|
|
17766
|
+
const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
|
|
17767
|
+
const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
|
|
17768
|
+
const current = await fetchLocationRecord(registryUrl, options2.subject, fetchFn);
|
|
17769
|
+
if (current !== null) {
|
|
17770
|
+
if (current.subject !== options2.subject || !await verifyLocationRecord(current)) {
|
|
17771
|
+
throw new LocationRecordValidationError("existing location record signature is invalid");
|
|
17772
|
+
}
|
|
17773
|
+
if (publishedHttpOrigins(current).includes(nodeOrigin)) {
|
|
17774
|
+
return current;
|
|
17775
|
+
}
|
|
17776
|
+
}
|
|
17777
|
+
const record2 = await signLocationRecord({
|
|
17778
|
+
version: 1,
|
|
17779
|
+
subject: options2.subject,
|
|
17780
|
+
multiaddrs: [...current?.multiaddrs ?? [], httpUrlToMultiaddr(nodeOrigin)],
|
|
17781
|
+
updated_at: (options2.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
17782
|
+
sequence: (current?.sequence ?? -1) + 1
|
|
17783
|
+
}, options2.signer);
|
|
17784
|
+
const response = await fetchFn(
|
|
17785
|
+
`${registryUrl}/v1/locations/${encodeURIComponent(options2.subject)}`,
|
|
17786
|
+
{
|
|
17787
|
+
method: "PUT",
|
|
17788
|
+
redirect: "error",
|
|
17789
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
17790
|
+
body: JSON.stringify(record2)
|
|
17791
|
+
}
|
|
17792
|
+
);
|
|
17793
|
+
if (!response.ok) {
|
|
17794
|
+
throw new Error(`location registry publish returned HTTP ${response.status}`);
|
|
17795
|
+
}
|
|
17796
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
17797
|
+
let body;
|
|
17798
|
+
try {
|
|
17799
|
+
body = JSON.parse(text2);
|
|
17800
|
+
} catch {
|
|
17801
|
+
throw new LocationRecordValidationError("registry publish response is not JSON");
|
|
17802
|
+
}
|
|
17803
|
+
if (body.record === void 0) {
|
|
17804
|
+
throw new LocationRecordValidationError("registry publish response missing record");
|
|
17805
|
+
}
|
|
17806
|
+
const published = validateLocationRecord(body.record);
|
|
17807
|
+
if (canonicalLocationPayload(locationPayloadForRecord(published)) !== canonicalLocationPayload(locationPayloadForRecord(record2)) || published.signature !== record2.signature || !await verifyLocationRecord(published)) {
|
|
17808
|
+
throw new LocationRecordValidationError("registry publish response does not match the signed record");
|
|
17809
|
+
}
|
|
17810
|
+
return published;
|
|
17811
|
+
}
|
|
17846
17812
|
async function resolveCloudLocation(subject, options2 = {}) {
|
|
17847
17813
|
validateSubject(subject);
|
|
17848
17814
|
const verifyRecords = options2.verifyRecords ?? true;
|
|
@@ -18093,6 +18059,123 @@ function multiaddrToHttpUrl(input) {
|
|
|
18093
18059
|
}
|
|
18094
18060
|
return uri;
|
|
18095
18061
|
}
|
|
18062
|
+
function exactHttpOrigin(value, label) {
|
|
18063
|
+
let url;
|
|
18064
|
+
try {
|
|
18065
|
+
url = new URL(value);
|
|
18066
|
+
} catch {
|
|
18067
|
+
throw new LocationRecordValidationError(`${label} must be an absolute URL origin`);
|
|
18068
|
+
}
|
|
18069
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
18070
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
18071
|
+
throw new LocationRecordValidationError(`${label} must use HTTPS (or loopback HTTP)`);
|
|
18072
|
+
}
|
|
18073
|
+
if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
18074
|
+
throw new LocationRecordValidationError(`${label} must be an origin without credentials, path, query, or fragment`);
|
|
18075
|
+
}
|
|
18076
|
+
return url.origin;
|
|
18077
|
+
}
|
|
18078
|
+
function publishedHttpOrigins(record2) {
|
|
18079
|
+
const origins = [];
|
|
18080
|
+
for (const address of record2.multiaddrs) {
|
|
18081
|
+
try {
|
|
18082
|
+
origins.push(exactHttpOrigin(multiaddrToHttpUrl(address), "published node URL"));
|
|
18083
|
+
} catch {
|
|
18084
|
+
}
|
|
18085
|
+
}
|
|
18086
|
+
return origins;
|
|
18087
|
+
}
|
|
18088
|
+
async function boundedResponseText(response, label, maxBytes) {
|
|
18089
|
+
const declaredLength = response.headers.get("content-length");
|
|
18090
|
+
if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
|
|
18091
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
18092
|
+
}
|
|
18093
|
+
if (response.body === null) return "";
|
|
18094
|
+
const reader = response.body.getReader();
|
|
18095
|
+
const chunks = [];
|
|
18096
|
+
let total = 0;
|
|
18097
|
+
try {
|
|
18098
|
+
while (true) {
|
|
18099
|
+
const { done, value } = await reader.read();
|
|
18100
|
+
if (done) break;
|
|
18101
|
+
if (value === void 0) continue;
|
|
18102
|
+
total += value.byteLength;
|
|
18103
|
+
if (total > maxBytes) {
|
|
18104
|
+
await reader.cancel().catch(() => void 0);
|
|
18105
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
18106
|
+
}
|
|
18107
|
+
chunks.push(value);
|
|
18108
|
+
}
|
|
18109
|
+
} finally {
|
|
18110
|
+
reader.releaseLock();
|
|
18111
|
+
}
|
|
18112
|
+
const bytes2 = new Uint8Array(total);
|
|
18113
|
+
let offset = 0;
|
|
18114
|
+
for (const chunk of chunks) {
|
|
18115
|
+
bytes2.set(chunk, offset);
|
|
18116
|
+
offset += chunk.byteLength;
|
|
18117
|
+
}
|
|
18118
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
18119
|
+
}
|
|
18120
|
+
function boundedSignal(parent, timeoutMs) {
|
|
18121
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
18122
|
+
throw new LocationRecordValidationError("owner-node binding timeout must be positive");
|
|
18123
|
+
}
|
|
18124
|
+
const controller = new AbortController();
|
|
18125
|
+
const abortFromParent = () => controller.abort(parent?.reason);
|
|
18126
|
+
if (parent?.aborted) abortFromParent();
|
|
18127
|
+
else parent?.addEventListener("abort", abortFromParent, { once: true });
|
|
18128
|
+
const timer = setTimeout(() => controller.abort(new DOMException("owner-node binding timed out", "TimeoutError")), timeoutMs);
|
|
18129
|
+
return {
|
|
18130
|
+
signal: controller.signal,
|
|
18131
|
+
dispose() {
|
|
18132
|
+
clearTimeout(timer);
|
|
18133
|
+
parent?.removeEventListener("abort", abortFromParent);
|
|
18134
|
+
}
|
|
18135
|
+
};
|
|
18136
|
+
}
|
|
18137
|
+
async function verifyOwnerNodeBinding(options2) {
|
|
18138
|
+
validateSubject(options2.ownerDid);
|
|
18139
|
+
validateSubject(options2.nodeDid);
|
|
18140
|
+
const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
|
|
18141
|
+
const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
|
|
18142
|
+
const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
|
|
18143
|
+
const lookup = boundedSignal(options2.signal, options2.timeoutMs ?? OWNER_NODE_BINDING_TIMEOUT_MS);
|
|
18144
|
+
try {
|
|
18145
|
+
const record2 = await fetchLocationRecord(registryUrl, options2.ownerDid, fetchFn, lookup.signal);
|
|
18146
|
+
if (record2 === null) {
|
|
18147
|
+
throw new LocationRecordValidationError("owner has no published location record");
|
|
18148
|
+
}
|
|
18149
|
+
if (record2.subject !== options2.ownerDid || !await verifyLocationRecord(record2)) {
|
|
18150
|
+
throw new LocationRecordValidationError("owner location record signature is invalid");
|
|
18151
|
+
}
|
|
18152
|
+
const publishedOrigins = publishedHttpOrigins(record2);
|
|
18153
|
+
if (!publishedOrigins.includes(nodeOrigin)) {
|
|
18154
|
+
throw new LocationRecordValidationError("share target is not in the owner's signed location record");
|
|
18155
|
+
}
|
|
18156
|
+
const response = await fetchFn(new URL("/info", nodeOrigin), {
|
|
18157
|
+
redirect: "error",
|
|
18158
|
+
headers: { accept: "application/json" },
|
|
18159
|
+
signal: lookup.signal
|
|
18160
|
+
});
|
|
18161
|
+
if (!response.ok) {
|
|
18162
|
+
throw new LocationRecordValidationError(`target node /info returned HTTP ${response.status}`);
|
|
18163
|
+
}
|
|
18164
|
+
const text2 = await boundedResponseText(response, "target node /info", 16 * 1024);
|
|
18165
|
+
let body;
|
|
18166
|
+
try {
|
|
18167
|
+
body = JSON.parse(text2);
|
|
18168
|
+
} catch {
|
|
18169
|
+
throw new LocationRecordValidationError("target node /info response is not JSON");
|
|
18170
|
+
}
|
|
18171
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) || body.nodeId !== options2.nodeDid) {
|
|
18172
|
+
throw new LocationRecordValidationError("target node identity does not match the share attestation");
|
|
18173
|
+
}
|
|
18174
|
+
return Object.freeze({ record: record2, nodeOrigin, nodeDid: options2.nodeDid });
|
|
18175
|
+
} finally {
|
|
18176
|
+
lookup.dispose();
|
|
18177
|
+
}
|
|
18178
|
+
}
|
|
18096
18179
|
function httpUrlToMultiaddr(input) {
|
|
18097
18180
|
const url = new URL(input);
|
|
18098
18181
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
@@ -18260,7 +18343,7 @@ function verifyDidKeySignature(did, payload, signature) {
|
|
|
18260
18343
|
"did:key signature must be a base64url Ed25519 signature"
|
|
18261
18344
|
);
|
|
18262
18345
|
}
|
|
18263
|
-
return
|
|
18346
|
+
return ed2551910.verify(
|
|
18264
18347
|
signatureBytes,
|
|
18265
18348
|
new TextEncoder().encode(payload),
|
|
18266
18349
|
publicKey
|
|
@@ -18268,7 +18351,7 @@ function verifyDidKeySignature(did, payload, signature) {
|
|
|
18268
18351
|
}
|
|
18269
18352
|
function verifyDidKeyEd25519Signature(did, payload, signature) {
|
|
18270
18353
|
const publicKey = ed25519PublicKeyFromDidKey2(did);
|
|
18271
|
-
return
|
|
18354
|
+
return ed2551910.verify(signature, payload, publicKey);
|
|
18272
18355
|
}
|
|
18273
18356
|
function ed25519PublicKeyFromDidKey2(did) {
|
|
18274
18357
|
const identifier = did.slice("did:key:".length);
|
|
@@ -18429,6 +18512,7 @@ export {
|
|
|
18429
18512
|
NETWORK_NAME_PATTERN,
|
|
18430
18513
|
NetworkIdError,
|
|
18431
18514
|
OPENKEY_ACTION_ID_SEPARATOR,
|
|
18515
|
+
OWNER_NODE_BINDING_TIMEOUT_MS,
|
|
18432
18516
|
OpenKeyManageKeyError,
|
|
18433
18517
|
POLICY_CAPABILITY_V1_DOMAIN,
|
|
18434
18518
|
POLICY_CAPABILITY_V1_SCHEMA,
|
|
@@ -18468,7 +18552,6 @@ export {
|
|
|
18468
18552
|
SECRET_RECORDS_SCHEMA,
|
|
18469
18553
|
SERVICE_LONG_TO_SHORT,
|
|
18470
18554
|
SERVICE_SHORT_TO_LONG,
|
|
18471
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
18472
18555
|
SPACE,
|
|
18473
18556
|
SQL,
|
|
18474
18557
|
SQLAction,
|
|
@@ -18671,6 +18754,7 @@ export {
|
|
|
18671
18754
|
parsePolicySessionUcan,
|
|
18672
18755
|
parsePortableDelegation,
|
|
18673
18756
|
parseRecapCapabilities,
|
|
18757
|
+
parseSecretCatalogKey,
|
|
18674
18758
|
parseShareArtifact,
|
|
18675
18759
|
parseShareUrl,
|
|
18676
18760
|
parseSpaceUri,
|
|
@@ -18683,6 +18767,7 @@ export {
|
|
|
18683
18767
|
principalDidEquals,
|
|
18684
18768
|
projectUnifiedPolicyCapabilities,
|
|
18685
18769
|
projectUnifiedPolicyCapability,
|
|
18770
|
+
publishLocationRecord,
|
|
18686
18771
|
recapCaveatsEqual,
|
|
18687
18772
|
registerPolicyV3,
|
|
18688
18773
|
renewPolicyRootStatusV3,
|
|
@@ -18702,7 +18787,6 @@ export {
|
|
|
18702
18787
|
serviceError5 as serviceError,
|
|
18703
18788
|
sha256Base64Url,
|
|
18704
18789
|
shareCapabilityAllows,
|
|
18705
|
-
shareDeliveryTrustedKid,
|
|
18706
18790
|
signCompactUcanAuthorization,
|
|
18707
18791
|
signCompactUcanRootAuthorization,
|
|
18708
18792
|
signLocationRecord,
|
|
@@ -18739,7 +18823,6 @@ export {
|
|
|
18739
18823
|
validatePolicyStatusSignedShape,
|
|
18740
18824
|
validatePolicyStatusUnsigned,
|
|
18741
18825
|
validatePolicyUnsigned,
|
|
18742
|
-
validateShareDeliveryAuthorizationBytes,
|
|
18743
18826
|
validateShareDeliveryAuthorizationV3Bytes,
|
|
18744
18827
|
validateUnifiedPolicyV2,
|
|
18745
18828
|
verifyDecryptResponse,
|
|
@@ -18747,6 +18830,7 @@ export {
|
|
|
18747
18830
|
verifyEip191MessageSignature,
|
|
18748
18831
|
verifyIssuedCredential,
|
|
18749
18832
|
verifyLocationRecord,
|
|
18833
|
+
verifyOwnerNodeBinding,
|
|
18750
18834
|
verifyPolicy,
|
|
18751
18835
|
verifyPolicyEngineRecord,
|
|
18752
18836
|
verifyPolicyRootRevocationV3,
|