@tinycloud/sdk-core 3.0.0-beta.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{credential-admission-CFjUChF8.d.cts → credential-admission-BxpkckU0.d.cts} +23 -3
- package/dist/{credential-admission-DIEPgQ0o.d.ts → credential-admission-CO9WMtGs.d.ts} +23 -3
- package/dist/delegations/index.cjs +101 -188
- 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 +102 -193
- package/dist/delegations/index.js.map +1 -1
- package/dist/index.cjs +381 -218
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -7
- package/dist/index.d.ts +74 -7
- package/dist/index.js +382 -223
- package/dist/index.js.map +1 -1
- package/dist/policy/index.cjs +82 -36
- package/dist/policy/index.cjs.map +1 -1
- package/dist/policy/index.d.cts +2 -2
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +82 -36
- package/dist/policy/index.js.map +1 -1
- package/dist/{recipient-types.schema-DjmM_yJu.d.cts → recipient-types.schema-D9AI8tjN.d.cts} +42 -105
- package/dist/{recipient-types.schema-DjmM_yJu.d.ts → recipient-types.schema-D9AI8tjN.d.ts} +42 -105
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -101,6 +101,7 @@ __export(index_exports, {
|
|
|
101
101
|
ED25519_JCS_SIGNATURE_SUITE: () => ED25519_JCS_SIGNATURE_SUITE,
|
|
102
102
|
EIP191_JCS_SIGNATURE_SUITE: () => EIP191_JCS_SIGNATURE_SUITE,
|
|
103
103
|
EMAIL_CREDENTIAL_MAX_AGE_SECONDS: () => EMAIL_CREDENTIAL_MAX_AGE_SECONDS,
|
|
104
|
+
EMAIL_DOMAIN_CREDENTIAL_MAX_AGE_SECONDS: () => EMAIL_DOMAIN_CREDENTIAL_MAX_AGE_SECONDS,
|
|
104
105
|
ENCRYPTION: () => import_bootstrap4.ENCRYPTION,
|
|
105
106
|
ENCRYPTION_MANIFEST_SPACE: () => ENCRYPTION_MANIFEST_SPACE,
|
|
106
107
|
ENCRYPTION_NETWORK_URN_PREFIX: () => import_sdk_services8.ENCRYPTION_NETWORK_URN_PREFIX,
|
|
@@ -126,6 +127,7 @@ __export(index_exports, {
|
|
|
126
127
|
LOCAL_LINK_PROBE_TIMEOUT_MS: () => LOCAL_LINK_PROBE_TIMEOUT_MS,
|
|
127
128
|
LOCAL_LOOPBACK_PROBE_TIMEOUT_MS: () => LOCAL_LOOPBACK_PROBE_TIMEOUT_MS,
|
|
128
129
|
LocationRecordValidationError: () => LocationRecordValidationError,
|
|
130
|
+
LocationRegistryHttpError: () => LocationRegistryHttpError,
|
|
129
131
|
MAX_CONTENT_BYTES: () => MAX_CONTENT_BYTES,
|
|
130
132
|
MAX_LEGACY_ENVELOPE_EXPIRES_AT: () => MAX_LEGACY_ENVELOPE_EXPIRES_AT,
|
|
131
133
|
MAX_SEALED_SHARE_CONTENT_BYTES: () => MAX_SEALED_SHARE_CONTENT_BYTES,
|
|
@@ -139,6 +141,7 @@ __export(index_exports, {
|
|
|
139
141
|
NETWORK_NAME_PATTERN: () => import_sdk_services8.NETWORK_NAME_PATTERN,
|
|
140
142
|
NetworkIdError: () => import_sdk_services8.NetworkIdError,
|
|
141
143
|
OPENKEY_ACTION_ID_SEPARATOR: () => OPENKEY_ACTION_ID_SEPARATOR,
|
|
144
|
+
OWNER_NODE_BINDING_TIMEOUT_MS: () => OWNER_NODE_BINDING_TIMEOUT_MS,
|
|
142
145
|
OpenKeyManageKeyError: () => OpenKeyManageKeyError,
|
|
143
146
|
POLICY_CAPABILITY_V1_DOMAIN: () => POLICY_CAPABILITY_V1_DOMAIN,
|
|
144
147
|
POLICY_CAPABILITY_V1_SCHEMA: () => POLICY_CAPABILITY_V1_SCHEMA,
|
|
@@ -178,7 +181,6 @@ __export(index_exports, {
|
|
|
178
181
|
SECRET_RECORDS_SCHEMA: () => import_bootstrap4.SECRET_RECORDS_SCHEMA,
|
|
179
182
|
SERVICE_LONG_TO_SHORT: () => SERVICE_LONG_TO_SHORT,
|
|
180
183
|
SERVICE_SHORT_TO_LONG: () => SERVICE_SHORT_TO_LONG,
|
|
181
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN: () => SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
182
184
|
SPACE: () => import_bootstrap4.SPACE,
|
|
183
185
|
SQL: () => import_bootstrap4.SQL,
|
|
184
186
|
SQLAction: () => import_sdk_services8.SQLAction,
|
|
@@ -245,8 +247,10 @@ __export(index_exports, {
|
|
|
245
247
|
buildPolicyCredentialPresentationV4: () => buildPolicyCredentialPresentationV4,
|
|
246
248
|
buildSpaceUri: () => buildSpaceUri,
|
|
247
249
|
canonicalDigest: () => canonicalDigest,
|
|
250
|
+
canonicalEmailDomain: () => import_share_envelope.canonicalEmailDomain,
|
|
248
251
|
canonicalHashHex: () => import_sdk_services8.canonicalHashHex,
|
|
249
252
|
canonicalLocationPayload: () => canonicalLocationPayload,
|
|
253
|
+
canonicalMailbox: () => import_share_envelope.canonicalMailbox,
|
|
250
254
|
canonicalOwnerSharePolicy: () => canonicalOwnerSharePolicy,
|
|
251
255
|
canonicalSignedResponse: () => import_sdk_services8.canonicalSignedResponse,
|
|
252
256
|
canonicalizeAddress: () => canonicalizeAddress,
|
|
@@ -274,6 +278,7 @@ __export(index_exports, {
|
|
|
274
278
|
createCapabilityKeyRegistry: () => createCapabilityKeyRegistry,
|
|
275
279
|
createDelegatedShareKey: () => createDelegatedShareKey,
|
|
276
280
|
createEmailCredentialRequirement: () => createEmailCredentialRequirement,
|
|
281
|
+
createEmailDomainCredentialRequirement: () => createEmailDomainCredentialRequirement,
|
|
277
282
|
createHolderBinding: () => createHolderBinding,
|
|
278
283
|
createInMemoryLocalNodeIdentityStore: () => createInMemoryLocalNodeIdentityStore,
|
|
279
284
|
createLocalStorageLocalNodeIdentityStore: () => createLocalStorageLocalNodeIdentityStore,
|
|
@@ -346,6 +351,7 @@ __export(index_exports, {
|
|
|
346
351
|
importPortableDelegation: () => importPortableDelegation,
|
|
347
352
|
installTinyCloudDebugGlobals: () => import_sdk_services8.installTinyCloudDebugGlobals,
|
|
348
353
|
intersectShareCapabilities: () => intersectShareCapabilities,
|
|
354
|
+
isCanonicalEmailDomain: () => import_share_envelope.isCanonicalEmailDomain,
|
|
349
355
|
isCapabilitySubset: () => isCapabilitySubset,
|
|
350
356
|
isDigest: () => isDigest,
|
|
351
357
|
isEvmAddress: () => isEvmAddress,
|
|
@@ -356,6 +362,7 @@ __export(index_exports, {
|
|
|
356
362
|
jcsCanonicalize: () => jcsCanonicalize,
|
|
357
363
|
loadManifest: () => loadManifest,
|
|
358
364
|
locationPayloadForRecord: () => locationPayloadForRecord,
|
|
365
|
+
mailboxBelongsToDomain: () => import_share_envelope.mailboxBelongsToDomain,
|
|
359
366
|
makePkhSpaceId: () => makePkhSpaceId,
|
|
360
367
|
makePublicSpaceId: () => makePublicSpaceId,
|
|
361
368
|
manifestAbilitiesUnion: () => manifestAbilitiesUnion,
|
|
@@ -394,6 +401,7 @@ __export(index_exports, {
|
|
|
394
401
|
principalDidEquals: () => principalDidEquals,
|
|
395
402
|
projectUnifiedPolicyCapabilities: () => projectUnifiedPolicyCapabilities,
|
|
396
403
|
projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
|
|
404
|
+
publishLocationRecord: () => publishLocationRecord,
|
|
397
405
|
recapCaveatsEqual: () => recapCaveatsEqual,
|
|
398
406
|
registerPolicyV3: () => registerPolicyV3,
|
|
399
407
|
renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
|
|
@@ -413,7 +421,7 @@ __export(index_exports, {
|
|
|
413
421
|
serviceError: () => import_sdk_services8.serviceError,
|
|
414
422
|
sha256Base64Url: () => sha256Base64Url,
|
|
415
423
|
shareCapabilityAllows: () => shareCapabilityAllows,
|
|
416
|
-
|
|
424
|
+
signCompactPolicyDescendant: () => signCompactPolicyDescendant,
|
|
417
425
|
signCompactUcanAuthorization: () => signCompactUcanAuthorization,
|
|
418
426
|
signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
|
|
419
427
|
signLocationRecord: () => signLocationRecord,
|
|
@@ -450,7 +458,6 @@ __export(index_exports, {
|
|
|
450
458
|
validatePolicyStatusSignedShape: () => validatePolicyStatusSignedShape,
|
|
451
459
|
validatePolicyStatusUnsigned: () => validatePolicyStatusUnsigned,
|
|
452
460
|
validatePolicyUnsigned: () => validatePolicyUnsigned,
|
|
453
|
-
validateShareDeliveryAuthorizationBytes: () => validateShareDeliveryAuthorizationBytes,
|
|
454
461
|
validateShareDeliveryAuthorizationV3Bytes: () => validateShareDeliveryAuthorizationV3Bytes,
|
|
455
462
|
validateUnifiedPolicyV2: () => validateUnifiedPolicyV2,
|
|
456
463
|
verifyDecryptResponse: () => import_sdk_services8.verifyDecryptResponse,
|
|
@@ -458,6 +465,7 @@ __export(index_exports, {
|
|
|
458
465
|
verifyEip191MessageSignature: () => verifyEip191MessageSignature,
|
|
459
466
|
verifyIssuedCredential: () => verifyIssuedCredential,
|
|
460
467
|
verifyLocationRecord: () => verifyLocationRecord,
|
|
468
|
+
verifyOwnerNodeBinding: () => verifyOwnerNodeBinding,
|
|
461
469
|
verifyPolicy: () => verifyPolicy,
|
|
462
470
|
verifyPolicyEngineRecord: () => verifyPolicyEngineRecord,
|
|
463
471
|
verifyPolicyRootRevocationV3: () => verifyPolicyRootRevocationV3,
|
|
@@ -768,6 +776,7 @@ var CREDENTIAL_ERROR_CODES = [
|
|
|
768
776
|
"ISSUER_UNREADY",
|
|
769
777
|
"OFFLINE",
|
|
770
778
|
"POPUP_BLOCKED",
|
|
779
|
+
"PROOF_REJECTED",
|
|
771
780
|
"REQUEST_EXPIRED",
|
|
772
781
|
"REQUEST_SUBSTITUTED",
|
|
773
782
|
"SIGNATURE_REJECTED",
|
|
@@ -781,6 +790,7 @@ var RECOVERABLE = /* @__PURE__ */ new Set([
|
|
|
781
790
|
"ISSUER_UNREADY",
|
|
782
791
|
"OFFLINE",
|
|
783
792
|
"POPUP_BLOCKED",
|
|
793
|
+
"PROOF_REJECTED",
|
|
784
794
|
"REQUEST_EXPIRED",
|
|
785
795
|
"SIGNATURE_REJECTED",
|
|
786
796
|
"UNSUPPORTED_PROFILE",
|
|
@@ -849,6 +859,7 @@ function holderBindingSigningBytes(binding) {
|
|
|
849
859
|
|
|
850
860
|
// src/credentials/descriptor.ts
|
|
851
861
|
var ID = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
|
|
862
|
+
var CLAIM_NAME = /^[a-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
852
863
|
var STEP_TYPES = /* @__PURE__ */ new Set(["collect_input", "mailbox_otp", "holder_signature"]);
|
|
853
864
|
var ENDPOINTS = {
|
|
854
865
|
request: "request",
|
|
@@ -929,7 +940,7 @@ function validateCredentialFlowDescriptor(value) {
|
|
|
929
940
|
for (const value2 of raw.claims) {
|
|
930
941
|
const claim = object(value2, "claim");
|
|
931
942
|
exact(claim, ["name", "matching", "selectiveDisclosure"], "claim");
|
|
932
|
-
const name2 = text(claim.name, "claim.name",
|
|
943
|
+
const name2 = text(claim.name, "claim.name", CLAIM_NAME);
|
|
933
944
|
if (claimNames.has(name2) || claim.matching !== "normalized_exact" || typeof claim.selectiveDisclosure !== "boolean") fail("claim is invalid");
|
|
934
945
|
claimNames.add(name2);
|
|
935
946
|
}
|
|
@@ -1020,9 +1031,14 @@ function isDigest(value) {
|
|
|
1020
1031
|
return typeof value === "string" && /^[A-Za-z0-9_-]{43}$/.test(value);
|
|
1021
1032
|
}
|
|
1022
1033
|
|
|
1034
|
+
// src/credentials/email-domain.ts
|
|
1035
|
+
var import_share_envelope = require("@tinycloud/share-envelope");
|
|
1036
|
+
|
|
1023
1037
|
// src/credentials/requirement.ts
|
|
1024
1038
|
var ID2 = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
|
|
1025
1039
|
var EMAIL_CREDENTIAL_MAX_AGE_SECONDS = 3600;
|
|
1040
|
+
var EMAIL_DOMAIN_CREDENTIAL_MAX_AGE_SECONDS = 300;
|
|
1041
|
+
var CLAIM_NAME2 = /^[a-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
1026
1042
|
function createEmailCredentialRequirement(input) {
|
|
1027
1043
|
return validateCredentialRequirement({
|
|
1028
1044
|
type: "TinyCloudCredentialRequirement",
|
|
@@ -1033,6 +1049,17 @@ function createEmailCredentialRequirement(input) {
|
|
|
1033
1049
|
maxAgeSeconds: EMAIL_CREDENTIAL_MAX_AGE_SECONDS
|
|
1034
1050
|
});
|
|
1035
1051
|
}
|
|
1052
|
+
function createEmailDomainCredentialRequirement(input) {
|
|
1053
|
+
if (!(0, import_share_envelope.isCanonicalEmailDomain)(input.domain)) throw new CredentialError("UNSUPPORTED_PROFILE", "Email domain is not canonical");
|
|
1054
|
+
return validateCredentialRequirement({
|
|
1055
|
+
type: "TinyCloudCredentialRequirement",
|
|
1056
|
+
version: 1,
|
|
1057
|
+
profile: input.profile,
|
|
1058
|
+
credentialType: input.credentialType,
|
|
1059
|
+
claims: { emailDomain: input.domain },
|
|
1060
|
+
maxAgeSeconds: EMAIL_DOMAIN_CREDENTIAL_MAX_AGE_SECONDS
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1036
1063
|
function validateCredentialRequirement(value) {
|
|
1037
1064
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
|
|
1038
1065
|
const raw = value;
|
|
@@ -1046,7 +1073,7 @@ function validateCredentialRequirement(value) {
|
|
|
1046
1073
|
if (typeof raw.claims !== "object" || raw.claims === null || Array.isArray(raw.claims)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claims are invalid");
|
|
1047
1074
|
const claims = {};
|
|
1048
1075
|
for (const [key, value2] of Object.entries(raw.claims).sort(([a], [b]) => a.localeCompare(b))) {
|
|
1049
|
-
if (!
|
|
1076
|
+
if (!CLAIM_NAME2.test(key) || typeof value2 !== "string" || value2.length === 0 || value2.length > 4096) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claim is invalid");
|
|
1050
1077
|
claims[key] = value2;
|
|
1051
1078
|
}
|
|
1052
1079
|
if (raw.maxAgeSeconds !== void 0 && (!Number.isSafeInteger(raw.maxAgeSeconds) || raw.maxAgeSeconds < 1)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential freshness is invalid");
|
|
@@ -4469,8 +4496,9 @@ async function signCompactUcanAuthorization(input) {
|
|
|
4469
4496
|
throw new Error("compact invocation lifetime must be between one and 60 seconds");
|
|
4470
4497
|
return signCompactUcan(input);
|
|
4471
4498
|
}
|
|
4499
|
+
var MAX_POLICY_ROOT_SECONDS = 31 * 24 * 60 * 60;
|
|
4472
4500
|
async function signCompactUcanRootAuthorization(input) {
|
|
4473
|
-
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore >
|
|
4501
|
+
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > MAX_POLICY_ROOT_SECONDS)
|
|
4474
4502
|
throw new Error("compact policy root lifetime must be between one second and 31 days");
|
|
4475
4503
|
return signCompactUcan({ ...input, proofs: [] });
|
|
4476
4504
|
}
|
|
@@ -4505,7 +4533,7 @@ function createCompactPolicyInvocation(input) {
|
|
|
4505
4533
|
const signature = import_ed255193.ed25519.sign(signingInput, input.privateKey);
|
|
4506
4534
|
return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url2(signature)}`);
|
|
4507
4535
|
}
|
|
4508
|
-
function
|
|
4536
|
+
function compactPolicyDescendantClaims(input) {
|
|
4509
4537
|
const parent = parseCompactUcanAuthorization(input.parentAuthorization, input.parentCid);
|
|
4510
4538
|
if (parent.payload.aud !== input.issuerDid) throw new Error("policy descendant issuer is not the parent audience");
|
|
4511
4539
|
const parentFact = parent.payload.fct[0];
|
|
@@ -4513,29 +4541,53 @@ function createCompactPolicyDescendant(input) {
|
|
|
4513
4541
|
if (!Number.isInteger(depth) || depth <= 0) throw new Error("policy descendant depth is exhausted");
|
|
4514
4542
|
if (!compactAttenuationContains(parent.payload.att, input.attenuation))
|
|
4515
4543
|
throw new Error("policy descendant exceeds the parent attenuation");
|
|
4544
|
+
const now = Math.max(input.now ?? parent.payload.nbf + 1, parent.payload.nbf + 1);
|
|
4545
|
+
const exp = Math.min(input.expiresAt ?? parent.payload.exp - 1, parent.payload.exp - 1);
|
|
4546
|
+
if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
|
|
4547
|
+
return {
|
|
4548
|
+
parentCid: parent.cid,
|
|
4549
|
+
fact: { ...parentFact, remainingRedelegationDepth: depth - 1 },
|
|
4550
|
+
notBefore: now,
|
|
4551
|
+
expiresAt: exp,
|
|
4552
|
+
nonce: input.nonce ?? encodeBase64Url2(crypto.getRandomValues(new Uint8Array(16)))
|
|
4553
|
+
};
|
|
4554
|
+
}
|
|
4555
|
+
function createCompactPolicyDescendant(input) {
|
|
4556
|
+
const claims = compactPolicyDescendantClaims(input);
|
|
4516
4557
|
const principal = input.issuerDid.split("#", 1)[0];
|
|
4517
4558
|
const didMaterial = base58btc.decode(principal.slice("did:key:".length));
|
|
4518
4559
|
const publicKey = import_ed255193.ed25519.getPublicKey(input.privateKey);
|
|
4519
4560
|
if (didMaterial.length !== 34 || !equalBytes(publicKey, didMaterial.slice(2))) throw new Error("policy descendant signer does not bind issuer DID");
|
|
4520
|
-
const now = Math.max(input.now ?? Math.floor(Date.now() / 1e3), parent.payload.nbf + 1);
|
|
4521
|
-
const exp = Math.min(input.expiresAt ?? now + 60, now + 60, parent.payload.exp - 1);
|
|
4522
|
-
if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
|
|
4523
4561
|
const header = { alg: "EdDSA", jwk: { alg: "EdDSA", crv: "Ed25519", kty: "OKP", x: encodeBase64Url2(publicKey) }, typ: "JWT", ucv: "0.10.0" };
|
|
4524
4562
|
const payload = {
|
|
4525
4563
|
att: input.attenuation,
|
|
4526
4564
|
aud: input.audienceDid,
|
|
4527
|
-
exp,
|
|
4528
|
-
fct: [
|
|
4565
|
+
exp: claims.expiresAt,
|
|
4566
|
+
fct: [claims.fact],
|
|
4529
4567
|
iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
|
|
4530
|
-
nbf:
|
|
4531
|
-
nnc:
|
|
4532
|
-
prf: [
|
|
4568
|
+
nbf: claims.notBefore,
|
|
4569
|
+
nnc: claims.nonce,
|
|
4570
|
+
prf: [claims.parentCid]
|
|
4533
4571
|
};
|
|
4534
4572
|
const protectedSegment = encodeBase64Url2(new TextEncoder().encode(jcsCanonicalize(header)));
|
|
4535
4573
|
const payloadSegment = encodeBase64Url2(new TextEncoder().encode(jcsCanonicalize(payload)));
|
|
4536
4574
|
const signature = import_ed255193.ed25519.sign(new TextEncoder().encode(`${protectedSegment}.${payloadSegment}`), input.privateKey);
|
|
4537
4575
|
return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url2(signature)}`);
|
|
4538
4576
|
}
|
|
4577
|
+
async function signCompactPolicyDescendant(input) {
|
|
4578
|
+
const claims = compactPolicyDescendantClaims(input);
|
|
4579
|
+
return signCompactUcan({
|
|
4580
|
+
issuerDid: input.issuerDid,
|
|
4581
|
+
audienceDid: input.audienceDid,
|
|
4582
|
+
attenuation: input.attenuation,
|
|
4583
|
+
facts: [claims.fact],
|
|
4584
|
+
proofs: [claims.parentCid],
|
|
4585
|
+
notBefore: claims.notBefore,
|
|
4586
|
+
expiresAt: claims.expiresAt,
|
|
4587
|
+
nonce: claims.nonce,
|
|
4588
|
+
sign: input.sign
|
|
4589
|
+
});
|
|
4590
|
+
}
|
|
4539
4591
|
function compactAttenuationContains(parent, child) {
|
|
4540
4592
|
const selector = (value) => {
|
|
4541
4593
|
if (value.length !== 1 || !isRecordValue(value[0])) return void 0;
|
|
@@ -4605,7 +4657,7 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
|
|
|
4605
4657
|
throw new Error("policy session UCAN fact is incomplete");
|
|
4606
4658
|
if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
|
|
4607
4659
|
throw new Error("policy session UCAN v4 audit facts are invalid");
|
|
4608
|
-
if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf >
|
|
4660
|
+
if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf > MAX_POLICY_ROOT_SECONDS || parsed.payload.iss.split("#", 1)[0] !== fact.nodeAudience || parsed.payload.aud !== fact.recipientDid)
|
|
4609
4661
|
throw new Error("policy session UCAN fact is invalid");
|
|
4610
4662
|
return {
|
|
4611
4663
|
authorization,
|
|
@@ -5029,24 +5081,21 @@ async function admitPolicyCredentialV3(input) {
|
|
|
5029
5081
|
policy,
|
|
5030
5082
|
challenge
|
|
5031
5083
|
});
|
|
5032
|
-
const response = await
|
|
5084
|
+
const response = await postPolicyDelegation(
|
|
5085
|
+
fetchFn,
|
|
5033
5086
|
new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
|
|
5034
5087
|
{
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
credentialSpaceId,
|
|
5047
|
-
presentation
|
|
5048
|
-
})
|
|
5049
|
-
}
|
|
5088
|
+
policyCid: input.policyCid,
|
|
5089
|
+
challengeId: challenge.challengeId,
|
|
5090
|
+
nonce: challenge.nonce,
|
|
5091
|
+
requirement: validateCredentialRequirement(input.requirement),
|
|
5092
|
+
credential: envelopeFromVerified(input.credential),
|
|
5093
|
+
accountAuthorizationCid,
|
|
5094
|
+
credentialSpaceId,
|
|
5095
|
+
presentation
|
|
5096
|
+
},
|
|
5097
|
+
requestedSessionExpiry(input.requestedExpiresAt, policy),
|
|
5098
|
+
input.signal
|
|
5050
5099
|
);
|
|
5051
5100
|
if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
|
|
5052
5101
|
const value = record(await response.json(), "policy delegation");
|
|
@@ -5087,20 +5136,20 @@ async function admitPolicyCredentialV4(input) {
|
|
|
5087
5136
|
policy,
|
|
5088
5137
|
challenge
|
|
5089
5138
|
});
|
|
5090
|
-
const response = await
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
headers: { accept: "application/json", "content-type": "application/json" },
|
|
5095
|
-
body: JSON.stringify({
|
|
5139
|
+
const response = await postPolicyDelegation(
|
|
5140
|
+
fetchFn,
|
|
5141
|
+
new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
|
|
5142
|
+
{
|
|
5096
5143
|
policyCid: input.policyCid,
|
|
5097
5144
|
challengeId: challenge.challengeId,
|
|
5098
5145
|
nonce: challenge.nonce,
|
|
5099
5146
|
requirement: validateCredentialRequirement(input.requirement),
|
|
5100
5147
|
credential: envelopeFromVerified(input.credential),
|
|
5101
5148
|
presentation
|
|
5102
|
-
}
|
|
5103
|
-
|
|
5149
|
+
},
|
|
5150
|
+
requestedSessionExpiry(input.requestedExpiresAt, policy),
|
|
5151
|
+
input.signal
|
|
5152
|
+
);
|
|
5104
5153
|
if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
|
|
5105
5154
|
const value = record(await response.json(), "policy delegation");
|
|
5106
5155
|
if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
|
|
@@ -5124,6 +5173,27 @@ async function admitPolicyCredentialV4(input) {
|
|
|
5124
5173
|
input.signal?.throwIfAborted();
|
|
5125
5174
|
return Object.freeze({ challenge, presentation, session, delegationImported: true });
|
|
5126
5175
|
}
|
|
5176
|
+
function requestedSessionExpiry(requested, policy) {
|
|
5177
|
+
const value = requested === void 0 ? policy.expiresAt : requested;
|
|
5178
|
+
if (value === null || value === void 0) return void 0;
|
|
5179
|
+
const seconds = Math.floor(Date.parse(value) / 1e3);
|
|
5180
|
+
if (!Number.isFinite(seconds) || seconds <= Math.floor(Date.now() / 1e3) + 60) return void 0;
|
|
5181
|
+
return new Date(seconds * 1e3).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5182
|
+
}
|
|
5183
|
+
async function postPolicyDelegation(fetchFn, url, body, requestedExpiresAt, signal) {
|
|
5184
|
+
const send = (payload) => fetchFn(url, {
|
|
5185
|
+
method: "POST",
|
|
5186
|
+
redirect: "error",
|
|
5187
|
+
signal,
|
|
5188
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
5189
|
+
body: JSON.stringify(payload)
|
|
5190
|
+
});
|
|
5191
|
+
if (requestedExpiresAt === void 0) return send(body);
|
|
5192
|
+
const response = await send({ ...body, requestedExpiresAt });
|
|
5193
|
+
if (response.status !== 422) return response;
|
|
5194
|
+
await response.body?.cancel().catch(() => void 0);
|
|
5195
|
+
return send(body);
|
|
5196
|
+
}
|
|
5127
5197
|
function policyRuntimeBasePath2(value) {
|
|
5128
5198
|
const path = value ?? "/policy/v3";
|
|
5129
5199
|
if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {
|
|
@@ -6499,6 +6569,7 @@ var DelegationErrorSchema = import_zod4.z.object({
|
|
|
6499
6569
|
});
|
|
6500
6570
|
var DelegationErrorCodes = {
|
|
6501
6571
|
AUTH_REQUIRED: "AUTH_REQUIRED",
|
|
6572
|
+
AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
|
|
6502
6573
|
AUTH_EXPIRED: "AUTH_EXPIRED",
|
|
6503
6574
|
NOT_INITIALIZED: "NOT_INITIALIZED",
|
|
6504
6575
|
NOT_FOUND: "NOT_FOUND",
|
|
@@ -6863,7 +6934,7 @@ var CreateDelegationWasmResultSchema = import_zod4.z.object({
|
|
|
6863
6934
|
// src/expiry.ts
|
|
6864
6935
|
var EPHEMERAL_MS = 60 * 60 * 1e3;
|
|
6865
6936
|
var SIGNED_READ_URL_MS = 5 * 60 * 1e3;
|
|
6866
|
-
var SESSION_MS =
|
|
6937
|
+
var SESSION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
6867
6938
|
var SHARE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6868
6939
|
var APP_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
6869
6940
|
var MAX_MS = 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
@@ -8627,7 +8698,6 @@ var import_ed255195 = require("@noble/curves/ed25519");
|
|
|
8627
8698
|
var import_blake33 = require("@noble/hashes/blake3");
|
|
8628
8699
|
var import_sha2563 = require("@noble/hashes/sha256");
|
|
8629
8700
|
var ENVELOPE_SCHEMA = "xyz.tinycloud.share/envelope";
|
|
8630
|
-
var INLINE_PREFIX = "tc2:";
|
|
8631
8701
|
var RAW_CID_CODEC = 85;
|
|
8632
8702
|
var BLAKE3_256_CODE = 30;
|
|
8633
8703
|
var DEFAULT_MAX_INLINE_BYTES = 256 * 1024;
|
|
@@ -9332,25 +9402,8 @@ function parsePublishedShareLink(link, options2 = {}) {
|
|
|
9332
9402
|
}
|
|
9333
9403
|
const origin2 = normalizeOrigin(url.origin);
|
|
9334
9404
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
9335
|
-
if (url.
|
|
9336
|
-
if (url.
|
|
9337
|
-
let payload;
|
|
9338
|
-
try {
|
|
9339
|
-
const bytes2 = base64UrlToBytes(url.hash.slice("#v=2&p=".length));
|
|
9340
|
-
if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
|
|
9341
|
-
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
|
|
9342
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
|
|
9343
|
-
payload = value;
|
|
9344
|
-
} catch (error) {
|
|
9345
|
-
throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
|
|
9346
|
-
}
|
|
9347
|
-
if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || Object.keys(payload).some((key3) => !["v", "c", "cid", "k"].includes(key3))) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
|
|
9348
|
-
const sealed = base64UrlToBytes(payload.c);
|
|
9349
|
-
if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
|
|
9350
|
-
const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
|
|
9351
|
-
if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
|
|
9352
|
-
return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
|
|
9353
|
-
}
|
|
9405
|
+
if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
9406
|
+
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
9354
9407
|
const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
|
|
9355
9408
|
if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
9356
9409
|
const key = base64UrlToBytes(url.hash.slice(3));
|
|
@@ -9370,13 +9423,10 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
9370
9423
|
} catch {
|
|
9371
9424
|
throw new ShareEnvelopeError("invalid-link", "share link must be an absolute URL");
|
|
9372
9425
|
}
|
|
9373
|
-
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
9374
9426
|
const origin2 = normalizeOrigin(url.origin);
|
|
9375
9427
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
9376
|
-
if (url.pathname === "/
|
|
9377
|
-
|
|
9378
|
-
return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
|
|
9379
|
-
}
|
|
9428
|
+
if (url.pathname === "/viewer") throw new ShareEnvelopeError("invalid-link", "retired viewer links are not accepted");
|
|
9429
|
+
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
9380
9430
|
const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
|
|
9381
9431
|
if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
|
|
9382
9432
|
const fragmentParams = url.hash === "" ? void 0 : new URLSearchParams(url.hash.slice(1));
|
|
@@ -9399,9 +9449,10 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
9399
9449
|
}
|
|
9400
9450
|
function isV2ShareLink(link) {
|
|
9401
9451
|
try {
|
|
9402
|
-
|
|
9452
|
+
const url = new URL(link);
|
|
9453
|
+
return url.search === "" && url.pathname.startsWith("/s/");
|
|
9403
9454
|
} catch {
|
|
9404
|
-
return
|
|
9455
|
+
return false;
|
|
9405
9456
|
}
|
|
9406
9457
|
}
|
|
9407
9458
|
async function encryptShareBytes(bytes2, key) {
|
|
@@ -10901,6 +10952,13 @@ var ShareRecipientClient = class {
|
|
|
10901
10952
|
};
|
|
10902
10953
|
|
|
10903
10954
|
// src/delegations/SharingService.ts
|
|
10955
|
+
function spaceIdsEqual(left, right) {
|
|
10956
|
+
const normalizeOwnerAddress = (space) => {
|
|
10957
|
+
const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
|
|
10958
|
+
return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
|
|
10959
|
+
};
|
|
10960
|
+
return normalizeOwnerAddress(left) === normalizeOwnerAddress(right);
|
|
10961
|
+
}
|
|
10904
10962
|
function inferShortServiceFromActionUrns(actions) {
|
|
10905
10963
|
let short;
|
|
10906
10964
|
for (const action of actions) {
|
|
@@ -11136,11 +11194,11 @@ var SharingService = class {
|
|
|
11136
11194
|
}
|
|
11137
11195
|
return result;
|
|
11138
11196
|
};
|
|
11139
|
-
const canSatisfyFromRegistry = this.
|
|
11197
|
+
const canSatisfyFromRegistry = this.classifyDelegationAuthority(
|
|
11140
11198
|
fullPath,
|
|
11141
11199
|
actions,
|
|
11142
11200
|
requestedExpiry
|
|
11143
|
-
);
|
|
11201
|
+
) === "ok";
|
|
11144
11202
|
if (canSatisfyFromRegistry) {
|
|
11145
11203
|
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
11146
11204
|
const childDelegationActiveError = this.retiredGraphError();
|
|
@@ -11210,49 +11268,58 @@ var SharingService = class {
|
|
|
11210
11268
|
return { ok: true, data: shareLink };
|
|
11211
11269
|
}
|
|
11212
11270
|
/**
|
|
11213
|
-
*
|
|
11214
|
-
*
|
|
11215
|
-
*
|
|
11216
|
-
*
|
|
11217
|
-
*
|
|
11218
|
-
*
|
|
11271
|
+
* Report, without side effects, whether this session's registry authority
|
|
11272
|
+
* can issue the child delegation {@link generate} would create for the same
|
|
11273
|
+
* `path`, `actions` and `expiry` (defaults and `pathPrefix` applied the
|
|
11274
|
+
* same way):
|
|
11275
|
+
*
|
|
11276
|
+
* - `"ok"`: an uncaveated entry covers it.
|
|
11277
|
+
* - `"caveated"`: only caveated entries cover it. SharingService cannot
|
|
11278
|
+
* carry signed ReCap caveats into a child delegation.
|
|
11279
|
+
* - `"not-covered"`: no entry covers it, or there is no session.
|
|
11280
|
+
*
|
|
11281
|
+
* It does not consult `onRootDelegationNeeded`, so a signer-backed session
|
|
11282
|
+
* may still generate when this is not `"ok"`.
|
|
11283
|
+
*/
|
|
11284
|
+
preflightGenerate(params) {
|
|
11285
|
+
if (!this.session || !params.path) return "not-covered";
|
|
11286
|
+
const fullPath = this.pathPrefix ? `${this.pathPrefix}/${params.path}`.replace(/\/+/g, "/") : params.path;
|
|
11287
|
+
return this.classifyDelegationAuthority(
|
|
11288
|
+
fullPath,
|
|
11289
|
+
params.actions ?? DEFAULT_READ_ACTIONS,
|
|
11290
|
+
params.expiry ?? new Date(Date.now() + DEFAULT_EXPIRY_MS)
|
|
11291
|
+
);
|
|
11292
|
+
}
|
|
11293
|
+
/**
|
|
11294
|
+
* Classify the registry's authority for a child delegation. An entry
|
|
11295
|
+
* satisfies it when it is in the session space, valid, lasts at least until
|
|
11296
|
+
* `requestedExpiry`, allows sub-delegation, covers `path` (exact or parent)
|
|
11297
|
+
* and grants every action. Satisfying entries with caveats are unusable.
|
|
11219
11298
|
* @internal
|
|
11220
11299
|
*/
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
for (const key of
|
|
11224
|
-
const
|
|
11225
|
-
|
|
11226
|
-
if (delegation.spaceId !== this.session?.spaceId) {
|
|
11227
|
-
continue;
|
|
11228
|
-
}
|
|
11229
|
-
if ((delegation.caveats?.length ?? 0) > 0) {
|
|
11230
|
-
continue;
|
|
11231
|
-
}
|
|
11232
|
-
if (!this.registry.isDelegationValid(delegation)) {
|
|
11300
|
+
classifyDelegationAuthority(path, actions, requestedExpiry) {
|
|
11301
|
+
let coveredOnlyByCaveated = false;
|
|
11302
|
+
for (const key of this.registry.getAllKeys()) {
|
|
11303
|
+
for (const delegation of this.registry.getDelegationsForKey(key.id)) {
|
|
11304
|
+
if (delegation.spaceId === void 0 || !spaceIdsEqual(delegation.spaceId, this.session?.spaceId ?? "")) {
|
|
11233
11305
|
continue;
|
|
11234
11306
|
}
|
|
11235
|
-
if (delegation
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
if (delegation.
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
const delegationPath = delegation.path || "";
|
|
11242
|
-
if (!this.pathMatches(delegationPath, path)) {
|
|
11307
|
+
if (!this.registry.isDelegationValid(delegation)) continue;
|
|
11308
|
+
if (delegation.expiry < requestedExpiry) continue;
|
|
11309
|
+
if (delegation.allowSubDelegation === false) continue;
|
|
11310
|
+
if (!this.pathMatches(delegation.path || "", path)) continue;
|
|
11311
|
+
const delegationActions = delegation.actions || [];
|
|
11312
|
+
if (!actions.every((action) => delegationActions.includes(action) || delegationActions.includes("*"))) {
|
|
11243
11313
|
continue;
|
|
11244
11314
|
}
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
(action) => delegationActions.includes(action) || delegationActions.includes("*")
|
|
11248
|
-
);
|
|
11249
|
-
if (!hasAllActions) {
|
|
11315
|
+
if ((delegation.caveats?.length ?? 0) > 0) {
|
|
11316
|
+
coveredOnlyByCaveated = true;
|
|
11250
11317
|
continue;
|
|
11251
11318
|
}
|
|
11252
|
-
return
|
|
11319
|
+
return "ok";
|
|
11253
11320
|
}
|
|
11254
11321
|
}
|
|
11255
|
-
return
|
|
11322
|
+
return coveredOnlyByCaveated ? "caveated" : "not-covered";
|
|
11256
11323
|
}
|
|
11257
11324
|
retiredGraphError() {
|
|
11258
11325
|
try {
|
|
@@ -11351,11 +11418,23 @@ var SharingService = class {
|
|
|
11351
11418
|
const errorText = await registerRes.text();
|
|
11352
11419
|
const registrationTextActiveError = this.retiredGraphError();
|
|
11353
11420
|
if (registrationTextActiveError) return { ok: false, error: registrationTextActiveError };
|
|
11421
|
+
const message = `Failed to register delegation with server: ${registerRes.status} ${errorText}`;
|
|
11422
|
+
if (registerRes.status === 401 || registerRes.status === 403) {
|
|
11423
|
+
return {
|
|
11424
|
+
ok: false,
|
|
11425
|
+
error: createError2(
|
|
11426
|
+
DelegationErrorCodes.AUTH_UNAUTHORIZED,
|
|
11427
|
+
message,
|
|
11428
|
+
void 0,
|
|
11429
|
+
{ status: registerRes.status }
|
|
11430
|
+
)
|
|
11431
|
+
};
|
|
11432
|
+
}
|
|
11354
11433
|
return {
|
|
11355
11434
|
ok: false,
|
|
11356
11435
|
error: createError2(
|
|
11357
11436
|
DelegationErrorCodes.CREATION_FAILED,
|
|
11358
|
-
|
|
11437
|
+
message
|
|
11359
11438
|
)
|
|
11360
11439
|
};
|
|
11361
11440
|
}
|
|
@@ -12212,114 +12291,13 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
|
12212
12291
|
return validateOwnerSharePolicyRegistration(value, expected);
|
|
12213
12292
|
}
|
|
12214
12293
|
|
|
12215
|
-
// src/delegations/share-delivery.ts
|
|
12216
|
-
var import_ed255199 = require("@noble/curves/ed25519");
|
|
12217
|
-
var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
|
|
12218
|
-
function isCanonicalHttpsOrigin(value) {
|
|
12219
|
-
let url;
|
|
12220
|
-
try {
|
|
12221
|
-
url = new URL(value);
|
|
12222
|
-
} catch {
|
|
12223
|
-
return false;
|
|
12224
|
-
}
|
|
12225
|
-
return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
12226
|
-
}
|
|
12227
|
-
var AUTHORIZATION_KEYS = [
|
|
12228
|
-
"type",
|
|
12229
|
-
"version",
|
|
12230
|
-
"jti",
|
|
12231
|
-
"shareCid",
|
|
12232
|
-
"shareId",
|
|
12233
|
-
"registrationCid",
|
|
12234
|
-
"delegationCid",
|
|
12235
|
-
"enforcementDelegationCid",
|
|
12236
|
-
"envelopeCid",
|
|
12237
|
-
"policyCid",
|
|
12238
|
-
"nodeAudience",
|
|
12239
|
-
"targetOrigin",
|
|
12240
|
-
"openCredentialsAudience",
|
|
12241
|
-
"holder",
|
|
12242
|
-
"recipientMatcher",
|
|
12243
|
-
"deliveryEmail",
|
|
12244
|
-
"shareUrl",
|
|
12245
|
-
"returnOrigin",
|
|
12246
|
-
"documentName",
|
|
12247
|
-
"senderDid",
|
|
12248
|
-
"senderTrust",
|
|
12249
|
-
"contentSource",
|
|
12250
|
-
"contentSourceDigest",
|
|
12251
|
-
"shareExpiresAt",
|
|
12252
|
-
"issuedAt",
|
|
12253
|
-
"reportAbuseToken",
|
|
12254
|
-
"actions",
|
|
12255
|
-
"resource",
|
|
12256
|
-
"authorityMaterialHandle",
|
|
12257
|
-
"authorityMaterialDigest",
|
|
12258
|
-
"requestBodyDigest",
|
|
12259
|
-
"idempotencyKey",
|
|
12260
|
-
"expiresAt",
|
|
12261
|
-
"dataAuthority"
|
|
12262
|
-
];
|
|
12263
|
-
function assertObject2(value, keys, label) {
|
|
12264
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
12265
|
-
const record2 = value;
|
|
12266
|
-
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`);
|
|
12267
|
-
return record2;
|
|
12268
|
-
}
|
|
12269
|
-
function fromB642(value) {
|
|
12270
|
-
if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
|
|
12271
|
-
if (typeof atob === "function") {
|
|
12272
|
-
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
12273
|
-
return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
12274
|
-
}
|
|
12275
|
-
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
|
|
12276
|
-
throw new Error("base64url decoding is unavailable");
|
|
12277
|
-
}
|
|
12278
|
-
function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
|
|
12279
|
-
let value;
|
|
12280
|
-
try {
|
|
12281
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
12282
|
-
} catch {
|
|
12283
|
-
throw new Error("share delivery authorization response is not valid UTF-8 JSON");
|
|
12284
|
-
}
|
|
12285
|
-
const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
|
|
12286
|
-
const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
|
|
12287
|
-
const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
|
|
12288
|
-
const request = expected.request;
|
|
12289
|
-
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) {
|
|
12290
|
-
throw new Error("share delivery authorization is not bound to the submitted request");
|
|
12291
|
-
}
|
|
12292
|
-
if (typeof authorization.issuedAt !== "string" || Date.parse(authorization.issuedAt) > Date.now() + 6e4) throw new Error("share delivery authorization issuedAt is invalid");
|
|
12293
|
-
if (new Date(authorization.expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
|
|
12294
|
-
if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
|
|
12295
|
-
throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
|
|
12296
|
-
}
|
|
12297
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
|
|
12298
|
-
if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
|
|
12299
|
-
const encodedKid = expected.nodeProof.publicKey;
|
|
12300
|
-
const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
|
|
12301
|
-
if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
|
|
12302
|
-
const signatureBytes = fromB642(proof.signature);
|
|
12303
|
-
const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalizeSignedObjectUnsigned(authorization)}`);
|
|
12304
|
-
if (signatureBytes.length !== 64 || !import_ed255199.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
|
|
12305
|
-
return { authorization, proof };
|
|
12306
|
-
}
|
|
12307
|
-
function shareDeliveryTrustedKid(publicKey) {
|
|
12308
|
-
if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
|
|
12309
|
-
const prefixed = new Uint8Array(34);
|
|
12310
|
-
prefixed.set([237, 1]);
|
|
12311
|
-
prefixed.set(publicKey, 2);
|
|
12312
|
-
const did = `did:key:${base58btc.encode(prefixed)}`;
|
|
12313
|
-
return `${did}#${did.slice("did:key:".length)}`;
|
|
12314
|
-
}
|
|
12315
|
-
|
|
12316
12294
|
// src/delegations/share-delivery-v3.ts
|
|
12317
|
-
var
|
|
12295
|
+
var import_ed255199 = require("@noble/curves/ed25519");
|
|
12318
12296
|
var import_sha2567 = require("@noble/hashes/sha256");
|
|
12319
12297
|
var CREDENTIAL_INVITATION_REQUEST_DOMAIN = "xyz.tinycloud.credentials/invitation-request/v1\0";
|
|
12320
12298
|
var DELIVERY_ADMISSION_DOMAIN = "xyz.tinycloud.policy/delivery-admission/v0\0";
|
|
12321
|
-
var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
12322
|
-
var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
|
|
12299
|
+
var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
12300
|
+
var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
|
|
12323
12301
|
function exactObject2(value, keys, label) {
|
|
12324
12302
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
12325
12303
|
const record2 = value;
|
|
@@ -12363,15 +12341,16 @@ function validateShareDeliveryAuthorizationV3Bytes(bytes2, expected) {
|
|
|
12363
12341
|
const contentSource = object3(envelope.contentSource, "v3 content source");
|
|
12364
12342
|
const envelopeSignature = object3(envelope.signature, "v3 envelope signature");
|
|
12365
12343
|
const target = object3(envelope.target, "v3 target");
|
|
12366
|
-
const
|
|
12367
|
-
|
|
12344
|
+
const binding = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
12345
|
+
const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
|
|
12346
|
+
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");
|
|
12368
12347
|
const issuedAt = Date.parse(String(request.issuedAt));
|
|
12369
12348
|
const expiresAt = Date.parse(String(request.expiresAt));
|
|
12370
12349
|
if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || expiresAt <= issuedAt || expiresAt - issuedAt > 15 * 6e4) throw new Error("v3 share delivery authorization time is invalid");
|
|
12371
12350
|
const admissionSignature = decodeBase64Url3(signature.value);
|
|
12372
12351
|
const { signature: _signature, ...unsignedAdmission } = admission;
|
|
12373
12352
|
const digest4 = (0, import_sha2567.sha256)(new TextEncoder().encode(`${DELIVERY_ADMISSION_DOMAIN}${canonicalizeSignedObjectUnsigned(unsignedAdmission)}`));
|
|
12374
|
-
if (admissionSignature.length !== 64 || !
|
|
12353
|
+
if (admissionSignature.length !== 64 || !import_ed255199.ed25519.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
|
|
12375
12354
|
return { request, admission };
|
|
12376
12355
|
}
|
|
12377
12356
|
|
|
@@ -17996,7 +17975,7 @@ function portForProtocol(protocol) {
|
|
|
17996
17975
|
}
|
|
17997
17976
|
|
|
17998
17977
|
// src/location.ts
|
|
17999
|
-
var
|
|
17978
|
+
var import_ed2551910 = require("@noble/curves/ed25519");
|
|
18000
17979
|
var import_viem5 = require("viem");
|
|
18001
17980
|
var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
|
|
18002
17981
|
var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
|
|
@@ -18004,6 +17983,7 @@ var DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
|
|
|
18004
17983
|
var LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
|
|
18005
17984
|
var LOCAL_LINK_PROBE_TIMEOUT_MS = 750;
|
|
18006
17985
|
var LOCAL_LINK_HOST_SUFFIX = ".local.tinycloud.link";
|
|
17986
|
+
var OWNER_NODE_BINDING_TIMEOUT_MS = 5e3;
|
|
18007
17987
|
function createInMemoryLocalNodeIdentityStore() {
|
|
18008
17988
|
const pins = /* @__PURE__ */ new Map();
|
|
18009
17989
|
return {
|
|
@@ -18054,6 +18034,13 @@ var LocationRecordValidationError = class extends Error {
|
|
|
18054
18034
|
this.name = "LocationRecordValidationError";
|
|
18055
18035
|
}
|
|
18056
18036
|
};
|
|
18037
|
+
var LocationRegistryHttpError = class extends Error {
|
|
18038
|
+
constructor(message, status) {
|
|
18039
|
+
super(message);
|
|
18040
|
+
this.status = status;
|
|
18041
|
+
this.name = "LocationRegistryHttpError";
|
|
18042
|
+
}
|
|
18043
|
+
};
|
|
18057
18044
|
var CloudLocationResolutionError = class extends Error {
|
|
18058
18045
|
constructor(subject, attempts) {
|
|
18059
18046
|
super(`Unable to resolve TinyCloud location for ${subject}`);
|
|
@@ -18136,21 +18123,80 @@ async function verifyLocationRecord(input) {
|
|
|
18136
18123
|
}
|
|
18137
18124
|
return false;
|
|
18138
18125
|
}
|
|
18139
|
-
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
|
|
18126
|
+
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
|
|
18140
18127
|
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
18141
|
-
const response = await fetchFn(url
|
|
18128
|
+
const response = await fetchFn(url, {
|
|
18129
|
+
redirect: "error",
|
|
18130
|
+
headers: { accept: "application/json" },
|
|
18131
|
+
...signal === void 0 ? {} : { signal }
|
|
18132
|
+
});
|
|
18142
18133
|
if (response.status === 404) {
|
|
18143
18134
|
return null;
|
|
18144
18135
|
}
|
|
18145
18136
|
if (!response.ok) {
|
|
18146
|
-
throw new
|
|
18137
|
+
throw new LocationRegistryHttpError(`location registry returned HTTP ${response.status}`, response.status);
|
|
18138
|
+
}
|
|
18139
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
18140
|
+
let body;
|
|
18141
|
+
try {
|
|
18142
|
+
body = JSON.parse(text2);
|
|
18143
|
+
} catch {
|
|
18144
|
+
throw new LocationRecordValidationError("registry response is not JSON");
|
|
18147
18145
|
}
|
|
18148
|
-
const body = await response.json();
|
|
18149
18146
|
if (body.record === void 0) {
|
|
18150
18147
|
throw new LocationRecordValidationError("registry response missing record");
|
|
18151
18148
|
}
|
|
18152
18149
|
return validateLocationRecord(body.record);
|
|
18153
18150
|
}
|
|
18151
|
+
async function publishLocationRecord(options2) {
|
|
18152
|
+
validateSubject(options2.subject);
|
|
18153
|
+
const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
|
|
18154
|
+
const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
|
|
18155
|
+
const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
|
|
18156
|
+
const current = await fetchLocationRecord(registryUrl, options2.subject, fetchFn);
|
|
18157
|
+
if (current !== null) {
|
|
18158
|
+
if (current.subject !== options2.subject || !await verifyLocationRecord(current)) {
|
|
18159
|
+
throw new LocationRecordValidationError("existing location record signature is invalid");
|
|
18160
|
+
}
|
|
18161
|
+
if (publishedHttpOrigins(current).includes(nodeOrigin)) {
|
|
18162
|
+
return current;
|
|
18163
|
+
}
|
|
18164
|
+
}
|
|
18165
|
+
const record2 = await signLocationRecord({
|
|
18166
|
+
version: 1,
|
|
18167
|
+
subject: options2.subject,
|
|
18168
|
+
multiaddrs: [...current?.multiaddrs ?? [], httpUrlToMultiaddr(nodeOrigin)],
|
|
18169
|
+
updated_at: (options2.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
18170
|
+
sequence: (current?.sequence ?? -1) + 1
|
|
18171
|
+
}, options2.signer);
|
|
18172
|
+
const response = await fetchFn(
|
|
18173
|
+
`${registryUrl}/v1/locations/${encodeURIComponent(options2.subject)}`,
|
|
18174
|
+
{
|
|
18175
|
+
method: "PUT",
|
|
18176
|
+
redirect: "error",
|
|
18177
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
18178
|
+
body: JSON.stringify(record2)
|
|
18179
|
+
}
|
|
18180
|
+
);
|
|
18181
|
+
if (!response.ok) {
|
|
18182
|
+
throw new LocationRegistryHttpError(`location registry publish returned HTTP ${response.status}`, response.status);
|
|
18183
|
+
}
|
|
18184
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
18185
|
+
let body;
|
|
18186
|
+
try {
|
|
18187
|
+
body = JSON.parse(text2);
|
|
18188
|
+
} catch {
|
|
18189
|
+
throw new LocationRecordValidationError("registry publish response is not JSON");
|
|
18190
|
+
}
|
|
18191
|
+
if (body.record === void 0) {
|
|
18192
|
+
throw new LocationRecordValidationError("registry publish response missing record");
|
|
18193
|
+
}
|
|
18194
|
+
const published = validateLocationRecord(body.record);
|
|
18195
|
+
if (canonicalLocationPayload(locationPayloadForRecord(published)) !== canonicalLocationPayload(locationPayloadForRecord(record2)) || published.signature !== record2.signature || !await verifyLocationRecord(published)) {
|
|
18196
|
+
throw new LocationRecordValidationError("registry publish response does not match the signed record");
|
|
18197
|
+
}
|
|
18198
|
+
return published;
|
|
18199
|
+
}
|
|
18154
18200
|
async function resolveCloudLocation(subject, options2 = {}) {
|
|
18155
18201
|
validateSubject(subject);
|
|
18156
18202
|
const verifyRecords = options2.verifyRecords ?? true;
|
|
@@ -18401,6 +18447,123 @@ function multiaddrToHttpUrl(input) {
|
|
|
18401
18447
|
}
|
|
18402
18448
|
return uri;
|
|
18403
18449
|
}
|
|
18450
|
+
function exactHttpOrigin(value, label) {
|
|
18451
|
+
let url;
|
|
18452
|
+
try {
|
|
18453
|
+
url = new URL(value);
|
|
18454
|
+
} catch {
|
|
18455
|
+
throw new LocationRecordValidationError(`${label} must be an absolute URL origin`);
|
|
18456
|
+
}
|
|
18457
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
18458
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
18459
|
+
throw new LocationRecordValidationError(`${label} must use HTTPS (or loopback HTTP)`);
|
|
18460
|
+
}
|
|
18461
|
+
if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
18462
|
+
throw new LocationRecordValidationError(`${label} must be an origin without credentials, path, query, or fragment`);
|
|
18463
|
+
}
|
|
18464
|
+
return url.origin;
|
|
18465
|
+
}
|
|
18466
|
+
function publishedHttpOrigins(record2) {
|
|
18467
|
+
const origins = [];
|
|
18468
|
+
for (const address of record2.multiaddrs) {
|
|
18469
|
+
try {
|
|
18470
|
+
origins.push(exactHttpOrigin(multiaddrToHttpUrl(address), "published node URL"));
|
|
18471
|
+
} catch {
|
|
18472
|
+
}
|
|
18473
|
+
}
|
|
18474
|
+
return origins;
|
|
18475
|
+
}
|
|
18476
|
+
async function boundedResponseText(response, label, maxBytes) {
|
|
18477
|
+
const declaredLength = response.headers.get("content-length");
|
|
18478
|
+
if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
|
|
18479
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
18480
|
+
}
|
|
18481
|
+
if (response.body === null) return "";
|
|
18482
|
+
const reader = response.body.getReader();
|
|
18483
|
+
const chunks = [];
|
|
18484
|
+
let total = 0;
|
|
18485
|
+
try {
|
|
18486
|
+
while (true) {
|
|
18487
|
+
const { done, value } = await reader.read();
|
|
18488
|
+
if (done) break;
|
|
18489
|
+
if (value === void 0) continue;
|
|
18490
|
+
total += value.byteLength;
|
|
18491
|
+
if (total > maxBytes) {
|
|
18492
|
+
await reader.cancel().catch(() => void 0);
|
|
18493
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
18494
|
+
}
|
|
18495
|
+
chunks.push(value);
|
|
18496
|
+
}
|
|
18497
|
+
} finally {
|
|
18498
|
+
reader.releaseLock();
|
|
18499
|
+
}
|
|
18500
|
+
const bytes2 = new Uint8Array(total);
|
|
18501
|
+
let offset = 0;
|
|
18502
|
+
for (const chunk of chunks) {
|
|
18503
|
+
bytes2.set(chunk, offset);
|
|
18504
|
+
offset += chunk.byteLength;
|
|
18505
|
+
}
|
|
18506
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
18507
|
+
}
|
|
18508
|
+
function boundedSignal(parent, timeoutMs) {
|
|
18509
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
18510
|
+
throw new LocationRecordValidationError("owner-node binding timeout must be positive");
|
|
18511
|
+
}
|
|
18512
|
+
const controller = new AbortController();
|
|
18513
|
+
const abortFromParent = () => controller.abort(parent?.reason);
|
|
18514
|
+
if (parent?.aborted) abortFromParent();
|
|
18515
|
+
else parent?.addEventListener("abort", abortFromParent, { once: true });
|
|
18516
|
+
const timer = setTimeout(() => controller.abort(new DOMException("owner-node binding timed out", "TimeoutError")), timeoutMs);
|
|
18517
|
+
return {
|
|
18518
|
+
signal: controller.signal,
|
|
18519
|
+
dispose() {
|
|
18520
|
+
clearTimeout(timer);
|
|
18521
|
+
parent?.removeEventListener("abort", abortFromParent);
|
|
18522
|
+
}
|
|
18523
|
+
};
|
|
18524
|
+
}
|
|
18525
|
+
async function verifyOwnerNodeBinding(options2) {
|
|
18526
|
+
validateSubject(options2.ownerDid);
|
|
18527
|
+
validateSubject(options2.nodeDid);
|
|
18528
|
+
const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
|
|
18529
|
+
const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
|
|
18530
|
+
const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
|
|
18531
|
+
const lookup = boundedSignal(options2.signal, options2.timeoutMs ?? OWNER_NODE_BINDING_TIMEOUT_MS);
|
|
18532
|
+
try {
|
|
18533
|
+
const record2 = await fetchLocationRecord(registryUrl, options2.ownerDid, fetchFn, lookup.signal);
|
|
18534
|
+
if (record2 === null) {
|
|
18535
|
+
throw new LocationRecordValidationError("owner has no published location record");
|
|
18536
|
+
}
|
|
18537
|
+
if (record2.subject !== options2.ownerDid || !await verifyLocationRecord(record2)) {
|
|
18538
|
+
throw new LocationRecordValidationError("owner location record signature is invalid");
|
|
18539
|
+
}
|
|
18540
|
+
const publishedOrigins = publishedHttpOrigins(record2);
|
|
18541
|
+
if (!publishedOrigins.includes(nodeOrigin)) {
|
|
18542
|
+
throw new LocationRecordValidationError("share target is not in the owner's signed location record");
|
|
18543
|
+
}
|
|
18544
|
+
const response = await fetchFn(new URL("/info", nodeOrigin), {
|
|
18545
|
+
redirect: "error",
|
|
18546
|
+
headers: { accept: "application/json" },
|
|
18547
|
+
signal: lookup.signal
|
|
18548
|
+
});
|
|
18549
|
+
if (!response.ok) {
|
|
18550
|
+
throw new LocationRecordValidationError(`target node /info returned HTTP ${response.status}`);
|
|
18551
|
+
}
|
|
18552
|
+
const text2 = await boundedResponseText(response, "target node /info", 16 * 1024);
|
|
18553
|
+
let body;
|
|
18554
|
+
try {
|
|
18555
|
+
body = JSON.parse(text2);
|
|
18556
|
+
} catch {
|
|
18557
|
+
throw new LocationRecordValidationError("target node /info response is not JSON");
|
|
18558
|
+
}
|
|
18559
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) || body.nodeId !== options2.nodeDid) {
|
|
18560
|
+
throw new LocationRecordValidationError("target node identity does not match the share attestation");
|
|
18561
|
+
}
|
|
18562
|
+
return Object.freeze({ record: record2, nodeOrigin, nodeDid: options2.nodeDid });
|
|
18563
|
+
} finally {
|
|
18564
|
+
lookup.dispose();
|
|
18565
|
+
}
|
|
18566
|
+
}
|
|
18404
18567
|
function httpUrlToMultiaddr(input) {
|
|
18405
18568
|
const url = new URL(input);
|
|
18406
18569
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
@@ -18568,7 +18731,7 @@ function verifyDidKeySignature(did, payload, signature) {
|
|
|
18568
18731
|
"did:key signature must be a base64url Ed25519 signature"
|
|
18569
18732
|
);
|
|
18570
18733
|
}
|
|
18571
|
-
return
|
|
18734
|
+
return import_ed2551910.ed25519.verify(
|
|
18572
18735
|
signatureBytes,
|
|
18573
18736
|
new TextEncoder().encode(payload),
|
|
18574
18737
|
publicKey
|
|
@@ -18576,7 +18739,7 @@ function verifyDidKeySignature(did, payload, signature) {
|
|
|
18576
18739
|
}
|
|
18577
18740
|
function verifyDidKeyEd25519Signature(did, payload, signature) {
|
|
18578
18741
|
const publicKey = ed25519PublicKeyFromDidKey2(did);
|
|
18579
|
-
return
|
|
18742
|
+
return import_ed2551910.ed25519.verify(signature, payload, publicKey);
|
|
18580
18743
|
}
|
|
18581
18744
|
function ed25519PublicKeyFromDidKey2(did) {
|
|
18582
18745
|
const identifier = did.slice("did:key:".length);
|