@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.cjs
CHANGED
|
@@ -139,6 +139,7 @@ __export(index_exports, {
|
|
|
139
139
|
NETWORK_NAME_PATTERN: () => import_sdk_services8.NETWORK_NAME_PATTERN,
|
|
140
140
|
NetworkIdError: () => import_sdk_services8.NetworkIdError,
|
|
141
141
|
OPENKEY_ACTION_ID_SEPARATOR: () => OPENKEY_ACTION_ID_SEPARATOR,
|
|
142
|
+
OWNER_NODE_BINDING_TIMEOUT_MS: () => OWNER_NODE_BINDING_TIMEOUT_MS,
|
|
142
143
|
OpenKeyManageKeyError: () => OpenKeyManageKeyError,
|
|
143
144
|
POLICY_CAPABILITY_V1_DOMAIN: () => POLICY_CAPABILITY_V1_DOMAIN,
|
|
144
145
|
POLICY_CAPABILITY_V1_SCHEMA: () => POLICY_CAPABILITY_V1_SCHEMA,
|
|
@@ -178,7 +179,6 @@ __export(index_exports, {
|
|
|
178
179
|
SECRET_RECORDS_SCHEMA: () => import_bootstrap4.SECRET_RECORDS_SCHEMA,
|
|
179
180
|
SERVICE_LONG_TO_SHORT: () => SERVICE_LONG_TO_SHORT,
|
|
180
181
|
SERVICE_SHORT_TO_LONG: () => SERVICE_SHORT_TO_LONG,
|
|
181
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN: () => SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
182
182
|
SPACE: () => import_bootstrap4.SPACE,
|
|
183
183
|
SQL: () => import_bootstrap4.SQL,
|
|
184
184
|
SQLAction: () => import_sdk_services8.SQLAction,
|
|
@@ -381,6 +381,7 @@ __export(index_exports, {
|
|
|
381
381
|
parsePolicySessionUcan: () => parsePolicySessionUcan,
|
|
382
382
|
parsePortableDelegation: () => parsePortableDelegation,
|
|
383
383
|
parseRecapCapabilities: () => parseRecapCapabilities,
|
|
384
|
+
parseSecretCatalogKey: () => import_sdk_services8.parseSecretCatalogKey,
|
|
384
385
|
parseShareArtifact: () => parseShareArtifact,
|
|
385
386
|
parseShareUrl: () => parseShareUrl,
|
|
386
387
|
parseSpaceUri: () => parseSpaceUri,
|
|
@@ -393,6 +394,7 @@ __export(index_exports, {
|
|
|
393
394
|
principalDidEquals: () => principalDidEquals,
|
|
394
395
|
projectUnifiedPolicyCapabilities: () => projectUnifiedPolicyCapabilities,
|
|
395
396
|
projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
|
|
397
|
+
publishLocationRecord: () => publishLocationRecord,
|
|
396
398
|
recapCaveatsEqual: () => recapCaveatsEqual,
|
|
397
399
|
registerPolicyV3: () => registerPolicyV3,
|
|
398
400
|
renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
|
|
@@ -412,7 +414,6 @@ __export(index_exports, {
|
|
|
412
414
|
serviceError: () => import_sdk_services8.serviceError,
|
|
413
415
|
sha256Base64Url: () => sha256Base64Url,
|
|
414
416
|
shareCapabilityAllows: () => shareCapabilityAllows,
|
|
415
|
-
shareDeliveryTrustedKid: () => shareDeliveryTrustedKid,
|
|
416
417
|
signCompactUcanAuthorization: () => signCompactUcanAuthorization,
|
|
417
418
|
signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
|
|
418
419
|
signLocationRecord: () => signLocationRecord,
|
|
@@ -449,7 +450,6 @@ __export(index_exports, {
|
|
|
449
450
|
validatePolicyStatusSignedShape: () => validatePolicyStatusSignedShape,
|
|
450
451
|
validatePolicyStatusUnsigned: () => validatePolicyStatusUnsigned,
|
|
451
452
|
validatePolicyUnsigned: () => validatePolicyUnsigned,
|
|
452
|
-
validateShareDeliveryAuthorizationBytes: () => validateShareDeliveryAuthorizationBytes,
|
|
453
453
|
validateShareDeliveryAuthorizationV3Bytes: () => validateShareDeliveryAuthorizationV3Bytes,
|
|
454
454
|
validateUnifiedPolicyV2: () => validateUnifiedPolicyV2,
|
|
455
455
|
verifyDecryptResponse: () => import_sdk_services8.verifyDecryptResponse,
|
|
@@ -457,6 +457,7 @@ __export(index_exports, {
|
|
|
457
457
|
verifyEip191MessageSignature: () => verifyEip191MessageSignature,
|
|
458
458
|
verifyIssuedCredential: () => verifyIssuedCredential,
|
|
459
459
|
verifyLocationRecord: () => verifyLocationRecord,
|
|
460
|
+
verifyOwnerNodeBinding: () => verifyOwnerNodeBinding,
|
|
460
461
|
verifyPolicy: () => verifyPolicy,
|
|
461
462
|
verifyPolicyEngineRecord: () => verifyPolicyEngineRecord,
|
|
462
463
|
verifyPolicyRootRevocationV3: () => verifyPolicyRootRevocationV3,
|
|
@@ -9331,25 +9332,33 @@ function parsePublishedShareLink(link, options2 = {}) {
|
|
|
9331
9332
|
}
|
|
9332
9333
|
const origin2 = normalizeOrigin(url.origin);
|
|
9333
9334
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
9334
|
-
if (url.
|
|
9335
|
-
if (url.pathname === "/
|
|
9335
|
+
if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
9336
|
+
if (url.pathname === "/viewer") {
|
|
9337
|
+
const secretPayload = url.search === "" && url.hash.startsWith("#tc2=") ? url.hash.slice("#tc2=".length) : void 0;
|
|
9338
|
+
const publicPayload = url.hash === "" && url.searchParams.size === 1 ? url.searchParams.get("tc2") ?? void 0 : void 0;
|
|
9339
|
+
if (secretPayload === void 0 && publicPayload === void 0) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
9340
|
+
if (publicPayload !== void 0 && url.search !== `?tc2=${publicPayload}`) throw new ShareEnvelopeError("invalid-link", "published share link is not canonical");
|
|
9336
9341
|
let payload;
|
|
9337
9342
|
try {
|
|
9338
|
-
const bytes2 = base64UrlToBytes(
|
|
9343
|
+
const bytes2 = base64UrlToBytes(secretPayload ?? publicPayload);
|
|
9339
9344
|
if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
|
|
9340
|
-
const
|
|
9345
|
+
const text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
9346
|
+
const value = JSON.parse(text2);
|
|
9341
9347
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
|
|
9348
|
+
if (canonicalJson(value) !== text2) throw new Error("inline payload is not canonical JSON");
|
|
9342
9349
|
payload = value;
|
|
9343
9350
|
} catch (error) {
|
|
9344
9351
|
throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
|
|
9345
9352
|
}
|
|
9346
|
-
|
|
9353
|
+
const payloadKeys = Object.keys(payload);
|
|
9354
|
+
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");
|
|
9347
9355
|
const sealed = base64UrlToBytes(payload.c);
|
|
9348
9356
|
if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
|
|
9349
9357
|
const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
|
|
9350
9358
|
if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
|
|
9351
9359
|
return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
|
|
9352
9360
|
}
|
|
9361
|
+
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
9353
9362
|
const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
|
|
9354
9363
|
if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
9355
9364
|
const key = base64UrlToBytes(url.hash.slice(3));
|
|
@@ -9369,13 +9378,13 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
9369
9378
|
} catch {
|
|
9370
9379
|
throw new ShareEnvelopeError("invalid-link", "share link must be an absolute URL");
|
|
9371
9380
|
}
|
|
9372
|
-
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
9373
9381
|
const origin2 = normalizeOrigin(url.origin);
|
|
9374
9382
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
9375
|
-
if (url.pathname === "/
|
|
9383
|
+
if (url.pathname === "/viewer" && (url.hash.startsWith("#tc2=") || url.searchParams.has("tc2"))) {
|
|
9376
9384
|
const published = parsePublishedShareLink(link, options2);
|
|
9377
9385
|
return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
|
|
9378
9386
|
}
|
|
9387
|
+
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
9379
9388
|
const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
|
|
9380
9389
|
if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
|
|
9381
9390
|
const fragmentParams = url.hash === "" ? void 0 : new URLSearchParams(url.hash.slice(1));
|
|
@@ -9398,7 +9407,8 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
9398
9407
|
}
|
|
9399
9408
|
function isV2ShareLink(link) {
|
|
9400
9409
|
try {
|
|
9401
|
-
|
|
9410
|
+
const url = new URL(link);
|
|
9411
|
+
return url.pathname.startsWith("/s/") || url.pathname === "/viewer";
|
|
9402
9412
|
} catch {
|
|
9403
9413
|
return link.startsWith(INLINE_PREFIX);
|
|
9404
9414
|
}
|
|
@@ -12211,114 +12221,13 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
|
12211
12221
|
return validateOwnerSharePolicyRegistration(value, expected);
|
|
12212
12222
|
}
|
|
12213
12223
|
|
|
12214
|
-
// src/delegations/share-delivery.ts
|
|
12215
|
-
var import_ed255199 = require("@noble/curves/ed25519");
|
|
12216
|
-
var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
|
|
12217
|
-
function isCanonicalHttpsOrigin(value) {
|
|
12218
|
-
let url;
|
|
12219
|
-
try {
|
|
12220
|
-
url = new URL(value);
|
|
12221
|
-
} catch {
|
|
12222
|
-
return false;
|
|
12223
|
-
}
|
|
12224
|
-
return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
12225
|
-
}
|
|
12226
|
-
var AUTHORIZATION_KEYS = [
|
|
12227
|
-
"type",
|
|
12228
|
-
"version",
|
|
12229
|
-
"jti",
|
|
12230
|
-
"shareCid",
|
|
12231
|
-
"shareId",
|
|
12232
|
-
"registrationCid",
|
|
12233
|
-
"delegationCid",
|
|
12234
|
-
"enforcementDelegationCid",
|
|
12235
|
-
"envelopeCid",
|
|
12236
|
-
"policyCid",
|
|
12237
|
-
"nodeAudience",
|
|
12238
|
-
"targetOrigin",
|
|
12239
|
-
"openCredentialsAudience",
|
|
12240
|
-
"holder",
|
|
12241
|
-
"recipientMatcher",
|
|
12242
|
-
"deliveryEmail",
|
|
12243
|
-
"shareUrl",
|
|
12244
|
-
"returnOrigin",
|
|
12245
|
-
"documentName",
|
|
12246
|
-
"senderDid",
|
|
12247
|
-
"senderTrust",
|
|
12248
|
-
"contentSource",
|
|
12249
|
-
"contentSourceDigest",
|
|
12250
|
-
"shareExpiresAt",
|
|
12251
|
-
"issuedAt",
|
|
12252
|
-
"reportAbuseToken",
|
|
12253
|
-
"actions",
|
|
12254
|
-
"resource",
|
|
12255
|
-
"authorityMaterialHandle",
|
|
12256
|
-
"authorityMaterialDigest",
|
|
12257
|
-
"requestBodyDigest",
|
|
12258
|
-
"idempotencyKey",
|
|
12259
|
-
"expiresAt",
|
|
12260
|
-
"dataAuthority"
|
|
12261
|
-
];
|
|
12262
|
-
function assertObject2(value, keys, label) {
|
|
12263
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
12264
|
-
const record2 = value;
|
|
12265
|
-
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`);
|
|
12266
|
-
return record2;
|
|
12267
|
-
}
|
|
12268
|
-
function fromB642(value) {
|
|
12269
|
-
if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
|
|
12270
|
-
if (typeof atob === "function") {
|
|
12271
|
-
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
12272
|
-
return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
12273
|
-
}
|
|
12274
|
-
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
|
|
12275
|
-
throw new Error("base64url decoding is unavailable");
|
|
12276
|
-
}
|
|
12277
|
-
function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
|
|
12278
|
-
let value;
|
|
12279
|
-
try {
|
|
12280
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
12281
|
-
} catch {
|
|
12282
|
-
throw new Error("share delivery authorization response is not valid UTF-8 JSON");
|
|
12283
|
-
}
|
|
12284
|
-
const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
|
|
12285
|
-
const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
|
|
12286
|
-
const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
|
|
12287
|
-
const request = expected.request;
|
|
12288
|
-
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) {
|
|
12289
|
-
throw new Error("share delivery authorization is not bound to the submitted request");
|
|
12290
|
-
}
|
|
12291
|
-
if (typeof authorization.issuedAt !== "string" || Date.parse(authorization.issuedAt) > Date.now() + 6e4) throw new Error("share delivery authorization issuedAt is invalid");
|
|
12292
|
-
if (new Date(authorization.expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
|
|
12293
|
-
if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
|
|
12294
|
-
throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
|
|
12295
|
-
}
|
|
12296
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
|
|
12297
|
-
if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
|
|
12298
|
-
const encodedKid = expected.nodeProof.publicKey;
|
|
12299
|
-
const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
|
|
12300
|
-
if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
|
|
12301
|
-
const signatureBytes = fromB642(proof.signature);
|
|
12302
|
-
const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalizeSignedObjectUnsigned(authorization)}`);
|
|
12303
|
-
if (signatureBytes.length !== 64 || !import_ed255199.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
|
|
12304
|
-
return { authorization, proof };
|
|
12305
|
-
}
|
|
12306
|
-
function shareDeliveryTrustedKid(publicKey) {
|
|
12307
|
-
if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
|
|
12308
|
-
const prefixed = new Uint8Array(34);
|
|
12309
|
-
prefixed.set([237, 1]);
|
|
12310
|
-
prefixed.set(publicKey, 2);
|
|
12311
|
-
const did = `did:key:${base58btc.encode(prefixed)}`;
|
|
12312
|
-
return `${did}#${did.slice("did:key:".length)}`;
|
|
12313
|
-
}
|
|
12314
|
-
|
|
12315
12224
|
// src/delegations/share-delivery-v3.ts
|
|
12316
|
-
var
|
|
12225
|
+
var import_ed255199 = require("@noble/curves/ed25519");
|
|
12317
12226
|
var import_sha2567 = require("@noble/hashes/sha256");
|
|
12318
12227
|
var CREDENTIAL_INVITATION_REQUEST_DOMAIN = "xyz.tinycloud.credentials/invitation-request/v1\0";
|
|
12319
12228
|
var DELIVERY_ADMISSION_DOMAIN = "xyz.tinycloud.policy/delivery-admission/v0\0";
|
|
12320
|
-
var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
12321
|
-
var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
|
|
12229
|
+
var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
12230
|
+
var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
|
|
12322
12231
|
function exactObject2(value, keys, label) {
|
|
12323
12232
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
12324
12233
|
const record2 = value;
|
|
@@ -12362,15 +12271,16 @@ function validateShareDeliveryAuthorizationV3Bytes(bytes2, expected) {
|
|
|
12362
12271
|
const contentSource = object3(envelope.contentSource, "v3 content source");
|
|
12363
12272
|
const envelopeSignature = object3(envelope.signature, "v3 envelope signature");
|
|
12364
12273
|
const target = object3(envelope.target, "v3 target");
|
|
12365
|
-
const
|
|
12366
|
-
|
|
12274
|
+
const binding = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
12275
|
+
const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
12276
|
+
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");
|
|
12367
12277
|
const issuedAt = Date.parse(String(request.issuedAt));
|
|
12368
12278
|
const expiresAt = Date.parse(String(request.expiresAt));
|
|
12369
12279
|
if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || expiresAt <= issuedAt || expiresAt - issuedAt > 15 * 6e4) throw new Error("v3 share delivery authorization time is invalid");
|
|
12370
12280
|
const admissionSignature = decodeBase64Url3(signature.value);
|
|
12371
12281
|
const { signature: _signature, ...unsignedAdmission } = admission;
|
|
12372
12282
|
const digest4 = (0, import_sha2567.sha256)(new TextEncoder().encode(`${DELIVERY_ADMISSION_DOMAIN}${canonicalizeSignedObjectUnsigned(unsignedAdmission)}`));
|
|
12373
|
-
if (admissionSignature.length !== 64 || !
|
|
12283
|
+
if (admissionSignature.length !== 64 || !import_ed255199.ed25519.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
|
|
12374
12284
|
return { request, admission };
|
|
12375
12285
|
}
|
|
12376
12286
|
|
|
@@ -17995,7 +17905,7 @@ function portForProtocol(protocol) {
|
|
|
17995
17905
|
}
|
|
17996
17906
|
|
|
17997
17907
|
// src/location.ts
|
|
17998
|
-
var
|
|
17908
|
+
var import_ed2551910 = require("@noble/curves/ed25519");
|
|
17999
17909
|
var import_viem5 = require("viem");
|
|
18000
17910
|
var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
|
|
18001
17911
|
var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
|
|
@@ -18003,6 +17913,7 @@ var DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
|
|
|
18003
17913
|
var LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
|
|
18004
17914
|
var LOCAL_LINK_PROBE_TIMEOUT_MS = 750;
|
|
18005
17915
|
var LOCAL_LINK_HOST_SUFFIX = ".local.tinycloud.link";
|
|
17916
|
+
var OWNER_NODE_BINDING_TIMEOUT_MS = 5e3;
|
|
18006
17917
|
function createInMemoryLocalNodeIdentityStore() {
|
|
18007
17918
|
const pins = /* @__PURE__ */ new Map();
|
|
18008
17919
|
return {
|
|
@@ -18135,21 +18046,80 @@ async function verifyLocationRecord(input) {
|
|
|
18135
18046
|
}
|
|
18136
18047
|
return false;
|
|
18137
18048
|
}
|
|
18138
|
-
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
|
|
18049
|
+
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
|
|
18139
18050
|
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
18140
|
-
const response = await fetchFn(url
|
|
18051
|
+
const response = await fetchFn(url, {
|
|
18052
|
+
redirect: "error",
|
|
18053
|
+
headers: { accept: "application/json" },
|
|
18054
|
+
...signal === void 0 ? {} : { signal }
|
|
18055
|
+
});
|
|
18141
18056
|
if (response.status === 404) {
|
|
18142
18057
|
return null;
|
|
18143
18058
|
}
|
|
18144
18059
|
if (!response.ok) {
|
|
18145
18060
|
throw new Error(`location registry returned HTTP ${response.status}`);
|
|
18146
18061
|
}
|
|
18147
|
-
const
|
|
18062
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
18063
|
+
let body;
|
|
18064
|
+
try {
|
|
18065
|
+
body = JSON.parse(text2);
|
|
18066
|
+
} catch {
|
|
18067
|
+
throw new LocationRecordValidationError("registry response is not JSON");
|
|
18068
|
+
}
|
|
18148
18069
|
if (body.record === void 0) {
|
|
18149
18070
|
throw new LocationRecordValidationError("registry response missing record");
|
|
18150
18071
|
}
|
|
18151
18072
|
return validateLocationRecord(body.record);
|
|
18152
18073
|
}
|
|
18074
|
+
async function publishLocationRecord(options2) {
|
|
18075
|
+
validateSubject(options2.subject);
|
|
18076
|
+
const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
|
|
18077
|
+
const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
|
|
18078
|
+
const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
|
|
18079
|
+
const current = await fetchLocationRecord(registryUrl, options2.subject, fetchFn);
|
|
18080
|
+
if (current !== null) {
|
|
18081
|
+
if (current.subject !== options2.subject || !await verifyLocationRecord(current)) {
|
|
18082
|
+
throw new LocationRecordValidationError("existing location record signature is invalid");
|
|
18083
|
+
}
|
|
18084
|
+
if (publishedHttpOrigins(current).includes(nodeOrigin)) {
|
|
18085
|
+
return current;
|
|
18086
|
+
}
|
|
18087
|
+
}
|
|
18088
|
+
const record2 = await signLocationRecord({
|
|
18089
|
+
version: 1,
|
|
18090
|
+
subject: options2.subject,
|
|
18091
|
+
multiaddrs: [...current?.multiaddrs ?? [], httpUrlToMultiaddr(nodeOrigin)],
|
|
18092
|
+
updated_at: (options2.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
18093
|
+
sequence: (current?.sequence ?? -1) + 1
|
|
18094
|
+
}, options2.signer);
|
|
18095
|
+
const response = await fetchFn(
|
|
18096
|
+
`${registryUrl}/v1/locations/${encodeURIComponent(options2.subject)}`,
|
|
18097
|
+
{
|
|
18098
|
+
method: "PUT",
|
|
18099
|
+
redirect: "error",
|
|
18100
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
18101
|
+
body: JSON.stringify(record2)
|
|
18102
|
+
}
|
|
18103
|
+
);
|
|
18104
|
+
if (!response.ok) {
|
|
18105
|
+
throw new Error(`location registry publish returned HTTP ${response.status}`);
|
|
18106
|
+
}
|
|
18107
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
18108
|
+
let body;
|
|
18109
|
+
try {
|
|
18110
|
+
body = JSON.parse(text2);
|
|
18111
|
+
} catch {
|
|
18112
|
+
throw new LocationRecordValidationError("registry publish response is not JSON");
|
|
18113
|
+
}
|
|
18114
|
+
if (body.record === void 0) {
|
|
18115
|
+
throw new LocationRecordValidationError("registry publish response missing record");
|
|
18116
|
+
}
|
|
18117
|
+
const published = validateLocationRecord(body.record);
|
|
18118
|
+
if (canonicalLocationPayload(locationPayloadForRecord(published)) !== canonicalLocationPayload(locationPayloadForRecord(record2)) || published.signature !== record2.signature || !await verifyLocationRecord(published)) {
|
|
18119
|
+
throw new LocationRecordValidationError("registry publish response does not match the signed record");
|
|
18120
|
+
}
|
|
18121
|
+
return published;
|
|
18122
|
+
}
|
|
18153
18123
|
async function resolveCloudLocation(subject, options2 = {}) {
|
|
18154
18124
|
validateSubject(subject);
|
|
18155
18125
|
const verifyRecords = options2.verifyRecords ?? true;
|
|
@@ -18400,6 +18370,123 @@ function multiaddrToHttpUrl(input) {
|
|
|
18400
18370
|
}
|
|
18401
18371
|
return uri;
|
|
18402
18372
|
}
|
|
18373
|
+
function exactHttpOrigin(value, label) {
|
|
18374
|
+
let url;
|
|
18375
|
+
try {
|
|
18376
|
+
url = new URL(value);
|
|
18377
|
+
} catch {
|
|
18378
|
+
throw new LocationRecordValidationError(`${label} must be an absolute URL origin`);
|
|
18379
|
+
}
|
|
18380
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
18381
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
18382
|
+
throw new LocationRecordValidationError(`${label} must use HTTPS (or loopback HTTP)`);
|
|
18383
|
+
}
|
|
18384
|
+
if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
18385
|
+
throw new LocationRecordValidationError(`${label} must be an origin without credentials, path, query, or fragment`);
|
|
18386
|
+
}
|
|
18387
|
+
return url.origin;
|
|
18388
|
+
}
|
|
18389
|
+
function publishedHttpOrigins(record2) {
|
|
18390
|
+
const origins = [];
|
|
18391
|
+
for (const address of record2.multiaddrs) {
|
|
18392
|
+
try {
|
|
18393
|
+
origins.push(exactHttpOrigin(multiaddrToHttpUrl(address), "published node URL"));
|
|
18394
|
+
} catch {
|
|
18395
|
+
}
|
|
18396
|
+
}
|
|
18397
|
+
return origins;
|
|
18398
|
+
}
|
|
18399
|
+
async function boundedResponseText(response, label, maxBytes) {
|
|
18400
|
+
const declaredLength = response.headers.get("content-length");
|
|
18401
|
+
if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
|
|
18402
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
18403
|
+
}
|
|
18404
|
+
if (response.body === null) return "";
|
|
18405
|
+
const reader = response.body.getReader();
|
|
18406
|
+
const chunks = [];
|
|
18407
|
+
let total = 0;
|
|
18408
|
+
try {
|
|
18409
|
+
while (true) {
|
|
18410
|
+
const { done, value } = await reader.read();
|
|
18411
|
+
if (done) break;
|
|
18412
|
+
if (value === void 0) continue;
|
|
18413
|
+
total += value.byteLength;
|
|
18414
|
+
if (total > maxBytes) {
|
|
18415
|
+
await reader.cancel().catch(() => void 0);
|
|
18416
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
18417
|
+
}
|
|
18418
|
+
chunks.push(value);
|
|
18419
|
+
}
|
|
18420
|
+
} finally {
|
|
18421
|
+
reader.releaseLock();
|
|
18422
|
+
}
|
|
18423
|
+
const bytes2 = new Uint8Array(total);
|
|
18424
|
+
let offset = 0;
|
|
18425
|
+
for (const chunk of chunks) {
|
|
18426
|
+
bytes2.set(chunk, offset);
|
|
18427
|
+
offset += chunk.byteLength;
|
|
18428
|
+
}
|
|
18429
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
18430
|
+
}
|
|
18431
|
+
function boundedSignal(parent, timeoutMs) {
|
|
18432
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
18433
|
+
throw new LocationRecordValidationError("owner-node binding timeout must be positive");
|
|
18434
|
+
}
|
|
18435
|
+
const controller = new AbortController();
|
|
18436
|
+
const abortFromParent = () => controller.abort(parent?.reason);
|
|
18437
|
+
if (parent?.aborted) abortFromParent();
|
|
18438
|
+
else parent?.addEventListener("abort", abortFromParent, { once: true });
|
|
18439
|
+
const timer = setTimeout(() => controller.abort(new DOMException("owner-node binding timed out", "TimeoutError")), timeoutMs);
|
|
18440
|
+
return {
|
|
18441
|
+
signal: controller.signal,
|
|
18442
|
+
dispose() {
|
|
18443
|
+
clearTimeout(timer);
|
|
18444
|
+
parent?.removeEventListener("abort", abortFromParent);
|
|
18445
|
+
}
|
|
18446
|
+
};
|
|
18447
|
+
}
|
|
18448
|
+
async function verifyOwnerNodeBinding(options2) {
|
|
18449
|
+
validateSubject(options2.ownerDid);
|
|
18450
|
+
validateSubject(options2.nodeDid);
|
|
18451
|
+
const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
|
|
18452
|
+
const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
|
|
18453
|
+
const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
|
|
18454
|
+
const lookup = boundedSignal(options2.signal, options2.timeoutMs ?? OWNER_NODE_BINDING_TIMEOUT_MS);
|
|
18455
|
+
try {
|
|
18456
|
+
const record2 = await fetchLocationRecord(registryUrl, options2.ownerDid, fetchFn, lookup.signal);
|
|
18457
|
+
if (record2 === null) {
|
|
18458
|
+
throw new LocationRecordValidationError("owner has no published location record");
|
|
18459
|
+
}
|
|
18460
|
+
if (record2.subject !== options2.ownerDid || !await verifyLocationRecord(record2)) {
|
|
18461
|
+
throw new LocationRecordValidationError("owner location record signature is invalid");
|
|
18462
|
+
}
|
|
18463
|
+
const publishedOrigins = publishedHttpOrigins(record2);
|
|
18464
|
+
if (!publishedOrigins.includes(nodeOrigin)) {
|
|
18465
|
+
throw new LocationRecordValidationError("share target is not in the owner's signed location record");
|
|
18466
|
+
}
|
|
18467
|
+
const response = await fetchFn(new URL("/info", nodeOrigin), {
|
|
18468
|
+
redirect: "error",
|
|
18469
|
+
headers: { accept: "application/json" },
|
|
18470
|
+
signal: lookup.signal
|
|
18471
|
+
});
|
|
18472
|
+
if (!response.ok) {
|
|
18473
|
+
throw new LocationRecordValidationError(`target node /info returned HTTP ${response.status}`);
|
|
18474
|
+
}
|
|
18475
|
+
const text2 = await boundedResponseText(response, "target node /info", 16 * 1024);
|
|
18476
|
+
let body;
|
|
18477
|
+
try {
|
|
18478
|
+
body = JSON.parse(text2);
|
|
18479
|
+
} catch {
|
|
18480
|
+
throw new LocationRecordValidationError("target node /info response is not JSON");
|
|
18481
|
+
}
|
|
18482
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) || body.nodeId !== options2.nodeDid) {
|
|
18483
|
+
throw new LocationRecordValidationError("target node identity does not match the share attestation");
|
|
18484
|
+
}
|
|
18485
|
+
return Object.freeze({ record: record2, nodeOrigin, nodeDid: options2.nodeDid });
|
|
18486
|
+
} finally {
|
|
18487
|
+
lookup.dispose();
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18403
18490
|
function httpUrlToMultiaddr(input) {
|
|
18404
18491
|
const url = new URL(input);
|
|
18405
18492
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
@@ -18567,7 +18654,7 @@ function verifyDidKeySignature(did, payload, signature) {
|
|
|
18567
18654
|
"did:key signature must be a base64url Ed25519 signature"
|
|
18568
18655
|
);
|
|
18569
18656
|
}
|
|
18570
|
-
return
|
|
18657
|
+
return import_ed2551910.ed25519.verify(
|
|
18571
18658
|
signatureBytes,
|
|
18572
18659
|
new TextEncoder().encode(payload),
|
|
18573
18660
|
publicKey
|
|
@@ -18575,7 +18662,7 @@ function verifyDidKeySignature(did, payload, signature) {
|
|
|
18575
18662
|
}
|
|
18576
18663
|
function verifyDidKeyEd25519Signature(did, payload, signature) {
|
|
18577
18664
|
const publicKey = ed25519PublicKeyFromDidKey2(did);
|
|
18578
|
-
return
|
|
18665
|
+
return import_ed2551910.ed25519.verify(signature, payload, publicKey);
|
|
18579
18666
|
}
|
|
18580
18667
|
function ed25519PublicKeyFromDidKey2(did) {
|
|
18581
18668
|
const identifier = did.slice("did:key:".length);
|