@tinycloud/sdk-core 3.0.0-beta.9 → 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 +90 -79
- package/dist/delegations/index.cjs.map +1 -1
- package/dist/delegations/index.d.cts +2 -2
- package/dist/delegations/index.d.ts +2 -2
- package/dist/delegations/index.js +90 -79
- package/dist/delegations/index.js.map +1 -1
- package/dist/index.cjs +185 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -6
- package/dist/index.d.ts +20 -6
- package/dist/index.js +185 -108
- 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-C0igQ1kA.d.cts → recipient-types.schema-D9AI8tjN.d.cts} +39 -8
- package/dist/{recipient-types.schema-C0igQ1kA.d.ts → recipient-types.schema-D9AI8tjN.d.ts} +39 -8
- 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,
|
|
@@ -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,
|
|
@@ -414,6 +421,7 @@ __export(index_exports, {
|
|
|
414
421
|
serviceError: () => import_sdk_services8.serviceError,
|
|
415
422
|
sha256Base64Url: () => sha256Base64Url,
|
|
416
423
|
shareCapabilityAllows: () => shareCapabilityAllows,
|
|
424
|
+
signCompactPolicyDescendant: () => signCompactPolicyDescendant,
|
|
417
425
|
signCompactUcanAuthorization: () => signCompactUcanAuthorization,
|
|
418
426
|
signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
|
|
419
427
|
signLocationRecord: () => signLocationRecord,
|
|
@@ -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;
|
|
@@ -9333,31 +9403,6 @@ function parsePublishedShareLink(link, options2 = {}) {
|
|
|
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
9405
|
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");
|
|
9341
|
-
let payload;
|
|
9342
|
-
try {
|
|
9343
|
-
const bytes2 = base64UrlToBytes(secretPayload ?? publicPayload);
|
|
9344
|
-
if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
|
|
9345
|
-
const text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
9346
|
-
const value = JSON.parse(text2);
|
|
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");
|
|
9349
|
-
payload = value;
|
|
9350
|
-
} catch (error) {
|
|
9351
|
-
throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
|
|
9352
|
-
}
|
|
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");
|
|
9355
|
-
const sealed = base64UrlToBytes(payload.c);
|
|
9356
|
-
if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
|
|
9357
|
-
const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
|
|
9358
|
-
if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
|
|
9359
|
-
return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
|
|
9360
|
-
}
|
|
9361
9406
|
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
9362
9407
|
const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
|
|
9363
9408
|
if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
@@ -9380,10 +9425,7 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
9380
9425
|
}
|
|
9381
9426
|
const origin2 = normalizeOrigin(url.origin);
|
|
9382
9427
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
9383
|
-
if (url.pathname === "/viewer"
|
|
9384
|
-
const published = parsePublishedShareLink(link, options2);
|
|
9385
|
-
return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
|
|
9386
|
-
}
|
|
9428
|
+
if (url.pathname === "/viewer") throw new ShareEnvelopeError("invalid-link", "retired viewer links are not accepted");
|
|
9387
9429
|
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
9388
9430
|
const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
|
|
9389
9431
|
if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
|
|
@@ -9408,9 +9450,9 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
9408
9450
|
function isV2ShareLink(link) {
|
|
9409
9451
|
try {
|
|
9410
9452
|
const url = new URL(link);
|
|
9411
|
-
return url.pathname.startsWith("/s/")
|
|
9453
|
+
return url.search === "" && url.pathname.startsWith("/s/");
|
|
9412
9454
|
} catch {
|
|
9413
|
-
return
|
|
9455
|
+
return false;
|
|
9414
9456
|
}
|
|
9415
9457
|
}
|
|
9416
9458
|
async function encryptShareBytes(bytes2, key) {
|
|
@@ -10910,6 +10952,13 @@ var ShareRecipientClient = class {
|
|
|
10910
10952
|
};
|
|
10911
10953
|
|
|
10912
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
|
+
}
|
|
10913
10962
|
function inferShortServiceFromActionUrns(actions) {
|
|
10914
10963
|
let short;
|
|
10915
10964
|
for (const action of actions) {
|
|
@@ -11145,11 +11194,11 @@ var SharingService = class {
|
|
|
11145
11194
|
}
|
|
11146
11195
|
return result;
|
|
11147
11196
|
};
|
|
11148
|
-
const canSatisfyFromRegistry = this.
|
|
11197
|
+
const canSatisfyFromRegistry = this.classifyDelegationAuthority(
|
|
11149
11198
|
fullPath,
|
|
11150
11199
|
actions,
|
|
11151
11200
|
requestedExpiry
|
|
11152
|
-
);
|
|
11201
|
+
) === "ok";
|
|
11153
11202
|
if (canSatisfyFromRegistry) {
|
|
11154
11203
|
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
11155
11204
|
const childDelegationActiveError = this.retiredGraphError();
|
|
@@ -11219,49 +11268,58 @@ var SharingService = class {
|
|
|
11219
11268
|
return { ok: true, data: shareLink };
|
|
11220
11269
|
}
|
|
11221
11270
|
/**
|
|
11222
|
-
*
|
|
11223
|
-
*
|
|
11224
|
-
*
|
|
11225
|
-
*
|
|
11226
|
-
*
|
|
11227
|
-
*
|
|
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.
|
|
11228
11298
|
* @internal
|
|
11229
11299
|
*/
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
for (const key of
|
|
11233
|
-
const
|
|
11234
|
-
|
|
11235
|
-
if (delegation.spaceId !== this.session?.spaceId) {
|
|
11236
|
-
continue;
|
|
11237
|
-
}
|
|
11238
|
-
if ((delegation.caveats?.length ?? 0) > 0) {
|
|
11239
|
-
continue;
|
|
11240
|
-
}
|
|
11241
|
-
if (!this.registry.isDelegationValid(delegation)) {
|
|
11242
|
-
continue;
|
|
11243
|
-
}
|
|
11244
|
-
if (delegation.expiry < requestedExpiry) {
|
|
11245
|
-
continue;
|
|
11246
|
-
}
|
|
11247
|
-
if (delegation.allowSubDelegation === false) {
|
|
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 ?? "")) {
|
|
11248
11305
|
continue;
|
|
11249
11306
|
}
|
|
11250
|
-
|
|
11251
|
-
if (
|
|
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("*"))) {
|
|
11252
11313
|
continue;
|
|
11253
11314
|
}
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
(action) => delegationActions.includes(action) || delegationActions.includes("*")
|
|
11257
|
-
);
|
|
11258
|
-
if (!hasAllActions) {
|
|
11315
|
+
if ((delegation.caveats?.length ?? 0) > 0) {
|
|
11316
|
+
coveredOnlyByCaveated = true;
|
|
11259
11317
|
continue;
|
|
11260
11318
|
}
|
|
11261
|
-
return
|
|
11319
|
+
return "ok";
|
|
11262
11320
|
}
|
|
11263
11321
|
}
|
|
11264
|
-
return
|
|
11322
|
+
return coveredOnlyByCaveated ? "caveated" : "not-covered";
|
|
11265
11323
|
}
|
|
11266
11324
|
retiredGraphError() {
|
|
11267
11325
|
try {
|
|
@@ -11360,11 +11418,23 @@ var SharingService = class {
|
|
|
11360
11418
|
const errorText = await registerRes.text();
|
|
11361
11419
|
const registrationTextActiveError = this.retiredGraphError();
|
|
11362
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
|
+
}
|
|
11363
11433
|
return {
|
|
11364
11434
|
ok: false,
|
|
11365
11435
|
error: createError2(
|
|
11366
11436
|
DelegationErrorCodes.CREATION_FAILED,
|
|
11367
|
-
|
|
11437
|
+
message
|
|
11368
11438
|
)
|
|
11369
11439
|
};
|
|
11370
11440
|
}
|
|
@@ -17964,6 +18034,13 @@ var LocationRecordValidationError = class extends Error {
|
|
|
17964
18034
|
this.name = "LocationRecordValidationError";
|
|
17965
18035
|
}
|
|
17966
18036
|
};
|
|
18037
|
+
var LocationRegistryHttpError = class extends Error {
|
|
18038
|
+
constructor(message, status) {
|
|
18039
|
+
super(message);
|
|
18040
|
+
this.status = status;
|
|
18041
|
+
this.name = "LocationRegistryHttpError";
|
|
18042
|
+
}
|
|
18043
|
+
};
|
|
17967
18044
|
var CloudLocationResolutionError = class extends Error {
|
|
17968
18045
|
constructor(subject, attempts) {
|
|
17969
18046
|
super(`Unable to resolve TinyCloud location for ${subject}`);
|
|
@@ -18057,7 +18134,7 @@ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fe
|
|
|
18057
18134
|
return null;
|
|
18058
18135
|
}
|
|
18059
18136
|
if (!response.ok) {
|
|
18060
|
-
throw new
|
|
18137
|
+
throw new LocationRegistryHttpError(`location registry returned HTTP ${response.status}`, response.status);
|
|
18061
18138
|
}
|
|
18062
18139
|
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
18063
18140
|
let body;
|
|
@@ -18102,7 +18179,7 @@ async function publishLocationRecord(options2) {
|
|
|
18102
18179
|
}
|
|
18103
18180
|
);
|
|
18104
18181
|
if (!response.ok) {
|
|
18105
|
-
throw new
|
|
18182
|
+
throw new LocationRegistryHttpError(`location registry publish returned HTTP ${response.status}`, response.status);
|
|
18106
18183
|
}
|
|
18107
18184
|
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
18108
18185
|
let body;
|