@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.js
CHANGED
|
@@ -311,6 +311,7 @@ var CREDENTIAL_ERROR_CODES = [
|
|
|
311
311
|
"ISSUER_UNREADY",
|
|
312
312
|
"OFFLINE",
|
|
313
313
|
"POPUP_BLOCKED",
|
|
314
|
+
"PROOF_REJECTED",
|
|
314
315
|
"REQUEST_EXPIRED",
|
|
315
316
|
"REQUEST_SUBSTITUTED",
|
|
316
317
|
"SIGNATURE_REJECTED",
|
|
@@ -324,6 +325,7 @@ var RECOVERABLE = /* @__PURE__ */ new Set([
|
|
|
324
325
|
"ISSUER_UNREADY",
|
|
325
326
|
"OFFLINE",
|
|
326
327
|
"POPUP_BLOCKED",
|
|
328
|
+
"PROOF_REJECTED",
|
|
327
329
|
"REQUEST_EXPIRED",
|
|
328
330
|
"SIGNATURE_REJECTED",
|
|
329
331
|
"UNSUPPORTED_PROFILE",
|
|
@@ -392,6 +394,7 @@ function holderBindingSigningBytes(binding) {
|
|
|
392
394
|
|
|
393
395
|
// src/credentials/descriptor.ts
|
|
394
396
|
var ID = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
|
|
397
|
+
var CLAIM_NAME = /^[a-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
395
398
|
var STEP_TYPES = /* @__PURE__ */ new Set(["collect_input", "mailbox_otp", "holder_signature"]);
|
|
396
399
|
var ENDPOINTS = {
|
|
397
400
|
request: "request",
|
|
@@ -472,7 +475,7 @@ function validateCredentialFlowDescriptor(value) {
|
|
|
472
475
|
for (const value2 of raw.claims) {
|
|
473
476
|
const claim = object(value2, "claim");
|
|
474
477
|
exact(claim, ["name", "matching", "selectiveDisclosure"], "claim");
|
|
475
|
-
const name2 = text(claim.name, "claim.name",
|
|
478
|
+
const name2 = text(claim.name, "claim.name", CLAIM_NAME);
|
|
476
479
|
if (claimNames.has(name2) || claim.matching !== "normalized_exact" || typeof claim.selectiveDisclosure !== "boolean") fail("claim is invalid");
|
|
477
480
|
claimNames.add(name2);
|
|
478
481
|
}
|
|
@@ -563,9 +566,14 @@ function isDigest(value) {
|
|
|
563
566
|
return typeof value === "string" && /^[A-Za-z0-9_-]{43}$/.test(value);
|
|
564
567
|
}
|
|
565
568
|
|
|
569
|
+
// src/credentials/email-domain.ts
|
|
570
|
+
import { canonicalEmailDomain, canonicalMailbox, isCanonicalEmailDomain, mailboxBelongsToDomain } from "@tinycloud/share-envelope";
|
|
571
|
+
|
|
566
572
|
// src/credentials/requirement.ts
|
|
567
573
|
var ID2 = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
|
|
568
574
|
var EMAIL_CREDENTIAL_MAX_AGE_SECONDS = 3600;
|
|
575
|
+
var EMAIL_DOMAIN_CREDENTIAL_MAX_AGE_SECONDS = 300;
|
|
576
|
+
var CLAIM_NAME2 = /^[a-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
569
577
|
function createEmailCredentialRequirement(input) {
|
|
570
578
|
return validateCredentialRequirement({
|
|
571
579
|
type: "TinyCloudCredentialRequirement",
|
|
@@ -576,6 +584,17 @@ function createEmailCredentialRequirement(input) {
|
|
|
576
584
|
maxAgeSeconds: EMAIL_CREDENTIAL_MAX_AGE_SECONDS
|
|
577
585
|
});
|
|
578
586
|
}
|
|
587
|
+
function createEmailDomainCredentialRequirement(input) {
|
|
588
|
+
if (!isCanonicalEmailDomain(input.domain)) throw new CredentialError("UNSUPPORTED_PROFILE", "Email domain is not canonical");
|
|
589
|
+
return validateCredentialRequirement({
|
|
590
|
+
type: "TinyCloudCredentialRequirement",
|
|
591
|
+
version: 1,
|
|
592
|
+
profile: input.profile,
|
|
593
|
+
credentialType: input.credentialType,
|
|
594
|
+
claims: { emailDomain: input.domain },
|
|
595
|
+
maxAgeSeconds: EMAIL_DOMAIN_CREDENTIAL_MAX_AGE_SECONDS
|
|
596
|
+
});
|
|
597
|
+
}
|
|
579
598
|
function validateCredentialRequirement(value) {
|
|
580
599
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
|
|
581
600
|
const raw = value;
|
|
@@ -589,7 +608,7 @@ function validateCredentialRequirement(value) {
|
|
|
589
608
|
if (typeof raw.claims !== "object" || raw.claims === null || Array.isArray(raw.claims)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claims are invalid");
|
|
590
609
|
const claims = {};
|
|
591
610
|
for (const [key, value2] of Object.entries(raw.claims).sort(([a], [b]) => a.localeCompare(b))) {
|
|
592
|
-
if (!
|
|
611
|
+
if (!CLAIM_NAME2.test(key) || typeof value2 !== "string" || value2.length === 0 || value2.length > 4096) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claim is invalid");
|
|
593
612
|
claims[key] = value2;
|
|
594
613
|
}
|
|
595
614
|
if (raw.maxAgeSeconds !== void 0 && (!Number.isSafeInteger(raw.maxAgeSeconds) || raw.maxAgeSeconds < 1)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential freshness is invalid");
|
|
@@ -4012,8 +4031,9 @@ async function signCompactUcanAuthorization(input) {
|
|
|
4012
4031
|
throw new Error("compact invocation lifetime must be between one and 60 seconds");
|
|
4013
4032
|
return signCompactUcan(input);
|
|
4014
4033
|
}
|
|
4034
|
+
var MAX_POLICY_ROOT_SECONDS = 31 * 24 * 60 * 60;
|
|
4015
4035
|
async function signCompactUcanRootAuthorization(input) {
|
|
4016
|
-
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore >
|
|
4036
|
+
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > MAX_POLICY_ROOT_SECONDS)
|
|
4017
4037
|
throw new Error("compact policy root lifetime must be between one second and 31 days");
|
|
4018
4038
|
return signCompactUcan({ ...input, proofs: [] });
|
|
4019
4039
|
}
|
|
@@ -4048,7 +4068,7 @@ function createCompactPolicyInvocation(input) {
|
|
|
4048
4068
|
const signature = ed255193.sign(signingInput, input.privateKey);
|
|
4049
4069
|
return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url2(signature)}`);
|
|
4050
4070
|
}
|
|
4051
|
-
function
|
|
4071
|
+
function compactPolicyDescendantClaims(input) {
|
|
4052
4072
|
const parent = parseCompactUcanAuthorization(input.parentAuthorization, input.parentCid);
|
|
4053
4073
|
if (parent.payload.aud !== input.issuerDid) throw new Error("policy descendant issuer is not the parent audience");
|
|
4054
4074
|
const parentFact = parent.payload.fct[0];
|
|
@@ -4056,29 +4076,53 @@ function createCompactPolicyDescendant(input) {
|
|
|
4056
4076
|
if (!Number.isInteger(depth) || depth <= 0) throw new Error("policy descendant depth is exhausted");
|
|
4057
4077
|
if (!compactAttenuationContains(parent.payload.att, input.attenuation))
|
|
4058
4078
|
throw new Error("policy descendant exceeds the parent attenuation");
|
|
4079
|
+
const now = Math.max(input.now ?? parent.payload.nbf + 1, parent.payload.nbf + 1);
|
|
4080
|
+
const exp = Math.min(input.expiresAt ?? parent.payload.exp - 1, parent.payload.exp - 1);
|
|
4081
|
+
if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
|
|
4082
|
+
return {
|
|
4083
|
+
parentCid: parent.cid,
|
|
4084
|
+
fact: { ...parentFact, remainingRedelegationDepth: depth - 1 },
|
|
4085
|
+
notBefore: now,
|
|
4086
|
+
expiresAt: exp,
|
|
4087
|
+
nonce: input.nonce ?? encodeBase64Url2(crypto.getRandomValues(new Uint8Array(16)))
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
function createCompactPolicyDescendant(input) {
|
|
4091
|
+
const claims = compactPolicyDescendantClaims(input);
|
|
4059
4092
|
const principal = input.issuerDid.split("#", 1)[0];
|
|
4060
4093
|
const didMaterial = base58btc.decode(principal.slice("did:key:".length));
|
|
4061
4094
|
const publicKey = ed255193.getPublicKey(input.privateKey);
|
|
4062
4095
|
if (didMaterial.length !== 34 || !equalBytes(publicKey, didMaterial.slice(2))) throw new Error("policy descendant signer does not bind issuer DID");
|
|
4063
|
-
const now = Math.max(input.now ?? Math.floor(Date.now() / 1e3), parent.payload.nbf + 1);
|
|
4064
|
-
const exp = Math.min(input.expiresAt ?? now + 60, now + 60, parent.payload.exp - 1);
|
|
4065
|
-
if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
|
|
4066
4096
|
const header = { alg: "EdDSA", jwk: { alg: "EdDSA", crv: "Ed25519", kty: "OKP", x: encodeBase64Url2(publicKey) }, typ: "JWT", ucv: "0.10.0" };
|
|
4067
4097
|
const payload = {
|
|
4068
4098
|
att: input.attenuation,
|
|
4069
4099
|
aud: input.audienceDid,
|
|
4070
|
-
exp,
|
|
4071
|
-
fct: [
|
|
4100
|
+
exp: claims.expiresAt,
|
|
4101
|
+
fct: [claims.fact],
|
|
4072
4102
|
iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
|
|
4073
|
-
nbf:
|
|
4074
|
-
nnc:
|
|
4075
|
-
prf: [
|
|
4103
|
+
nbf: claims.notBefore,
|
|
4104
|
+
nnc: claims.nonce,
|
|
4105
|
+
prf: [claims.parentCid]
|
|
4076
4106
|
};
|
|
4077
4107
|
const protectedSegment = encodeBase64Url2(new TextEncoder().encode(jcsCanonicalize(header)));
|
|
4078
4108
|
const payloadSegment = encodeBase64Url2(new TextEncoder().encode(jcsCanonicalize(payload)));
|
|
4079
4109
|
const signature = ed255193.sign(new TextEncoder().encode(`${protectedSegment}.${payloadSegment}`), input.privateKey);
|
|
4080
4110
|
return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url2(signature)}`);
|
|
4081
4111
|
}
|
|
4112
|
+
async function signCompactPolicyDescendant(input) {
|
|
4113
|
+
const claims = compactPolicyDescendantClaims(input);
|
|
4114
|
+
return signCompactUcan({
|
|
4115
|
+
issuerDid: input.issuerDid,
|
|
4116
|
+
audienceDid: input.audienceDid,
|
|
4117
|
+
attenuation: input.attenuation,
|
|
4118
|
+
facts: [claims.fact],
|
|
4119
|
+
proofs: [claims.parentCid],
|
|
4120
|
+
notBefore: claims.notBefore,
|
|
4121
|
+
expiresAt: claims.expiresAt,
|
|
4122
|
+
nonce: claims.nonce,
|
|
4123
|
+
sign: input.sign
|
|
4124
|
+
});
|
|
4125
|
+
}
|
|
4082
4126
|
function compactAttenuationContains(parent, child) {
|
|
4083
4127
|
const selector = (value) => {
|
|
4084
4128
|
if (value.length !== 1 || !isRecordValue(value[0])) return void 0;
|
|
@@ -4148,7 +4192,7 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
|
|
|
4148
4192
|
throw new Error("policy session UCAN fact is incomplete");
|
|
4149
4193
|
if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
|
|
4150
4194
|
throw new Error("policy session UCAN v4 audit facts are invalid");
|
|
4151
|
-
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 >
|
|
4195
|
+
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)
|
|
4152
4196
|
throw new Error("policy session UCAN fact is invalid");
|
|
4153
4197
|
return {
|
|
4154
4198
|
authorization,
|
|
@@ -4572,24 +4616,21 @@ async function admitPolicyCredentialV3(input) {
|
|
|
4572
4616
|
policy,
|
|
4573
4617
|
challenge
|
|
4574
4618
|
});
|
|
4575
|
-
const response = await
|
|
4619
|
+
const response = await postPolicyDelegation(
|
|
4620
|
+
fetchFn,
|
|
4576
4621
|
new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
|
|
4577
4622
|
{
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
credentialSpaceId,
|
|
4590
|
-
presentation
|
|
4591
|
-
})
|
|
4592
|
-
}
|
|
4623
|
+
policyCid: input.policyCid,
|
|
4624
|
+
challengeId: challenge.challengeId,
|
|
4625
|
+
nonce: challenge.nonce,
|
|
4626
|
+
requirement: validateCredentialRequirement(input.requirement),
|
|
4627
|
+
credential: envelopeFromVerified(input.credential),
|
|
4628
|
+
accountAuthorizationCid,
|
|
4629
|
+
credentialSpaceId,
|
|
4630
|
+
presentation
|
|
4631
|
+
},
|
|
4632
|
+
requestedSessionExpiry(input.requestedExpiresAt, policy),
|
|
4633
|
+
input.signal
|
|
4593
4634
|
);
|
|
4594
4635
|
if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
|
|
4595
4636
|
const value = record(await response.json(), "policy delegation");
|
|
@@ -4630,20 +4671,20 @@ async function admitPolicyCredentialV4(input) {
|
|
|
4630
4671
|
policy,
|
|
4631
4672
|
challenge
|
|
4632
4673
|
});
|
|
4633
|
-
const response = await
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
headers: { accept: "application/json", "content-type": "application/json" },
|
|
4638
|
-
body: JSON.stringify({
|
|
4674
|
+
const response = await postPolicyDelegation(
|
|
4675
|
+
fetchFn,
|
|
4676
|
+
new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
|
|
4677
|
+
{
|
|
4639
4678
|
policyCid: input.policyCid,
|
|
4640
4679
|
challengeId: challenge.challengeId,
|
|
4641
4680
|
nonce: challenge.nonce,
|
|
4642
4681
|
requirement: validateCredentialRequirement(input.requirement),
|
|
4643
4682
|
credential: envelopeFromVerified(input.credential),
|
|
4644
4683
|
presentation
|
|
4645
|
-
}
|
|
4646
|
-
|
|
4684
|
+
},
|
|
4685
|
+
requestedSessionExpiry(input.requestedExpiresAt, policy),
|
|
4686
|
+
input.signal
|
|
4687
|
+
);
|
|
4647
4688
|
if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
|
|
4648
4689
|
const value = record(await response.json(), "policy delegation");
|
|
4649
4690
|
if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
|
|
@@ -4667,6 +4708,27 @@ async function admitPolicyCredentialV4(input) {
|
|
|
4667
4708
|
input.signal?.throwIfAborted();
|
|
4668
4709
|
return Object.freeze({ challenge, presentation, session, delegationImported: true });
|
|
4669
4710
|
}
|
|
4711
|
+
function requestedSessionExpiry(requested, policy) {
|
|
4712
|
+
const value = requested === void 0 ? policy.expiresAt : requested;
|
|
4713
|
+
if (value === null || value === void 0) return void 0;
|
|
4714
|
+
const seconds = Math.floor(Date.parse(value) / 1e3);
|
|
4715
|
+
if (!Number.isFinite(seconds) || seconds <= Math.floor(Date.now() / 1e3) + 60) return void 0;
|
|
4716
|
+
return new Date(seconds * 1e3).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
4717
|
+
}
|
|
4718
|
+
async function postPolicyDelegation(fetchFn, url, body, requestedExpiresAt, signal) {
|
|
4719
|
+
const send = (payload) => fetchFn(url, {
|
|
4720
|
+
method: "POST",
|
|
4721
|
+
redirect: "error",
|
|
4722
|
+
signal,
|
|
4723
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
4724
|
+
body: JSON.stringify(payload)
|
|
4725
|
+
});
|
|
4726
|
+
if (requestedExpiresAt === void 0) return send(body);
|
|
4727
|
+
const response = await send({ ...body, requestedExpiresAt });
|
|
4728
|
+
if (response.status !== 422) return response;
|
|
4729
|
+
await response.body?.cancel().catch(() => void 0);
|
|
4730
|
+
return send(body);
|
|
4731
|
+
}
|
|
4670
4732
|
function policyRuntimeBasePath2(value) {
|
|
4671
4733
|
const path = value ?? "/policy/v3";
|
|
4672
4734
|
if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {
|
|
@@ -6042,6 +6104,7 @@ var DelegationErrorSchema = z4.object({
|
|
|
6042
6104
|
});
|
|
6043
6105
|
var DelegationErrorCodes = {
|
|
6044
6106
|
AUTH_REQUIRED: "AUTH_REQUIRED",
|
|
6107
|
+
AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
|
|
6045
6108
|
AUTH_EXPIRED: "AUTH_EXPIRED",
|
|
6046
6109
|
NOT_INITIALIZED: "NOT_INITIALIZED",
|
|
6047
6110
|
NOT_FOUND: "NOT_FOUND",
|
|
@@ -6406,7 +6469,7 @@ var CreateDelegationWasmResultSchema = z4.object({
|
|
|
6406
6469
|
// src/expiry.ts
|
|
6407
6470
|
var EPHEMERAL_MS = 60 * 60 * 1e3;
|
|
6408
6471
|
var SIGNED_READ_URL_MS = 5 * 60 * 1e3;
|
|
6409
|
-
var SESSION_MS =
|
|
6472
|
+
var SESSION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
6410
6473
|
var SHARE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6411
6474
|
var APP_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
6412
6475
|
var MAX_MS = 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
@@ -8174,7 +8237,6 @@ import { ed25519 as ed255195 } from "@noble/curves/ed25519";
|
|
|
8174
8237
|
import { blake3 as blake33 } from "@noble/hashes/blake3";
|
|
8175
8238
|
import { sha256 as sha2567 } from "@noble/hashes/sha256";
|
|
8176
8239
|
var ENVELOPE_SCHEMA = "xyz.tinycloud.share/envelope";
|
|
8177
|
-
var INLINE_PREFIX = "tc2:";
|
|
8178
8240
|
var RAW_CID_CODEC = 85;
|
|
8179
8241
|
var BLAKE3_256_CODE = 30;
|
|
8180
8242
|
var DEFAULT_MAX_INLINE_BYTES = 256 * 1024;
|
|
@@ -8880,31 +8942,6 @@ function parsePublishedShareLink(link, options2 = {}) {
|
|
|
8880
8942
|
const origin2 = normalizeOrigin(url.origin);
|
|
8881
8943
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
8882
8944
|
if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
8883
|
-
if (url.pathname === "/viewer") {
|
|
8884
|
-
const secretPayload = url.search === "" && url.hash.startsWith("#tc2=") ? url.hash.slice("#tc2=".length) : void 0;
|
|
8885
|
-
const publicPayload = url.hash === "" && url.searchParams.size === 1 ? url.searchParams.get("tc2") ?? void 0 : void 0;
|
|
8886
|
-
if (secretPayload === void 0 && publicPayload === void 0) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
8887
|
-
if (publicPayload !== void 0 && url.search !== `?tc2=${publicPayload}`) throw new ShareEnvelopeError("invalid-link", "published share link is not canonical");
|
|
8888
|
-
let payload;
|
|
8889
|
-
try {
|
|
8890
|
-
const bytes2 = base64UrlToBytes(secretPayload ?? publicPayload);
|
|
8891
|
-
if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
|
|
8892
|
-
const text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
8893
|
-
const value = JSON.parse(text2);
|
|
8894
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
|
|
8895
|
-
if (canonicalJson(value) !== text2) throw new Error("inline payload is not canonical JSON");
|
|
8896
|
-
payload = value;
|
|
8897
|
-
} catch (error) {
|
|
8898
|
-
throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
|
|
8899
|
-
}
|
|
8900
|
-
const payloadKeys = Object.keys(payload);
|
|
8901
|
-
if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || payloadKeys.some((key3) => !["v", "c", "cid", "k"].includes(key3)) || publicPayload !== void 0 && (payloadKeys.length !== 3 || payload.k !== void 0)) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
|
|
8902
|
-
const sealed = base64UrlToBytes(payload.c);
|
|
8903
|
-
if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
|
|
8904
|
-
const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
|
|
8905
|
-
if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
|
|
8906
|
-
return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
|
|
8907
|
-
}
|
|
8908
8945
|
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
|
|
8909
8946
|
const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
|
|
8910
8947
|
if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
|
|
@@ -8927,10 +8964,7 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
8927
8964
|
}
|
|
8928
8965
|
const origin2 = normalizeOrigin(url.origin);
|
|
8929
8966
|
if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
|
|
8930
|
-
if (url.pathname === "/viewer"
|
|
8931
|
-
const published = parsePublishedShareLink(link, options2);
|
|
8932
|
-
return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
|
|
8933
|
-
}
|
|
8967
|
+
if (url.pathname === "/viewer") throw new ShareEnvelopeError("invalid-link", "retired viewer links are not accepted");
|
|
8934
8968
|
if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
|
|
8935
8969
|
const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
|
|
8936
8970
|
if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
|
|
@@ -8955,9 +8989,9 @@ function parseShareUrl(link, options2 = {}) {
|
|
|
8955
8989
|
function isV2ShareLink(link) {
|
|
8956
8990
|
try {
|
|
8957
8991
|
const url = new URL(link);
|
|
8958
|
-
return url.pathname.startsWith("/s/")
|
|
8992
|
+
return url.search === "" && url.pathname.startsWith("/s/");
|
|
8959
8993
|
} catch {
|
|
8960
|
-
return
|
|
8994
|
+
return false;
|
|
8961
8995
|
}
|
|
8962
8996
|
}
|
|
8963
8997
|
async function encryptShareBytes(bytes2, key) {
|
|
@@ -10457,6 +10491,13 @@ var ShareRecipientClient = class {
|
|
|
10457
10491
|
};
|
|
10458
10492
|
|
|
10459
10493
|
// src/delegations/SharingService.ts
|
|
10494
|
+
function spaceIdsEqual(left, right) {
|
|
10495
|
+
const normalizeOwnerAddress = (space) => {
|
|
10496
|
+
const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
|
|
10497
|
+
return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
|
|
10498
|
+
};
|
|
10499
|
+
return normalizeOwnerAddress(left) === normalizeOwnerAddress(right);
|
|
10500
|
+
}
|
|
10460
10501
|
function inferShortServiceFromActionUrns(actions) {
|
|
10461
10502
|
let short;
|
|
10462
10503
|
for (const action of actions) {
|
|
@@ -10692,11 +10733,11 @@ var SharingService = class {
|
|
|
10692
10733
|
}
|
|
10693
10734
|
return result;
|
|
10694
10735
|
};
|
|
10695
|
-
const canSatisfyFromRegistry = this.
|
|
10736
|
+
const canSatisfyFromRegistry = this.classifyDelegationAuthority(
|
|
10696
10737
|
fullPath,
|
|
10697
10738
|
actions,
|
|
10698
10739
|
requestedExpiry
|
|
10699
|
-
);
|
|
10740
|
+
) === "ok";
|
|
10700
10741
|
if (canSatisfyFromRegistry) {
|
|
10701
10742
|
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
10702
10743
|
const childDelegationActiveError = this.retiredGraphError();
|
|
@@ -10766,49 +10807,58 @@ var SharingService = class {
|
|
|
10766
10807
|
return { ok: true, data: shareLink };
|
|
10767
10808
|
}
|
|
10768
10809
|
/**
|
|
10769
|
-
*
|
|
10770
|
-
*
|
|
10771
|
-
*
|
|
10772
|
-
*
|
|
10773
|
-
*
|
|
10774
|
-
*
|
|
10810
|
+
* Report, without side effects, whether this session's registry authority
|
|
10811
|
+
* can issue the child delegation {@link generate} would create for the same
|
|
10812
|
+
* `path`, `actions` and `expiry` (defaults and `pathPrefix` applied the
|
|
10813
|
+
* same way):
|
|
10814
|
+
*
|
|
10815
|
+
* - `"ok"`: an uncaveated entry covers it.
|
|
10816
|
+
* - `"caveated"`: only caveated entries cover it. SharingService cannot
|
|
10817
|
+
* carry signed ReCap caveats into a child delegation.
|
|
10818
|
+
* - `"not-covered"`: no entry covers it, or there is no session.
|
|
10819
|
+
*
|
|
10820
|
+
* It does not consult `onRootDelegationNeeded`, so a signer-backed session
|
|
10821
|
+
* may still generate when this is not `"ok"`.
|
|
10822
|
+
*/
|
|
10823
|
+
preflightGenerate(params) {
|
|
10824
|
+
if (!this.session || !params.path) return "not-covered";
|
|
10825
|
+
const fullPath = this.pathPrefix ? `${this.pathPrefix}/${params.path}`.replace(/\/+/g, "/") : params.path;
|
|
10826
|
+
return this.classifyDelegationAuthority(
|
|
10827
|
+
fullPath,
|
|
10828
|
+
params.actions ?? DEFAULT_READ_ACTIONS,
|
|
10829
|
+
params.expiry ?? new Date(Date.now() + DEFAULT_EXPIRY_MS)
|
|
10830
|
+
);
|
|
10831
|
+
}
|
|
10832
|
+
/**
|
|
10833
|
+
* Classify the registry's authority for a child delegation. An entry
|
|
10834
|
+
* satisfies it when it is in the session space, valid, lasts at least until
|
|
10835
|
+
* `requestedExpiry`, allows sub-delegation, covers `path` (exact or parent)
|
|
10836
|
+
* and grants every action. Satisfying entries with caveats are unusable.
|
|
10775
10837
|
* @internal
|
|
10776
10838
|
*/
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
for (const key of
|
|
10780
|
-
const
|
|
10781
|
-
|
|
10782
|
-
if (delegation.spaceId !== this.session?.spaceId) {
|
|
10783
|
-
continue;
|
|
10784
|
-
}
|
|
10785
|
-
if ((delegation.caveats?.length ?? 0) > 0) {
|
|
10786
|
-
continue;
|
|
10787
|
-
}
|
|
10788
|
-
if (!this.registry.isDelegationValid(delegation)) {
|
|
10789
|
-
continue;
|
|
10790
|
-
}
|
|
10791
|
-
if (delegation.expiry < requestedExpiry) {
|
|
10792
|
-
continue;
|
|
10793
|
-
}
|
|
10794
|
-
if (delegation.allowSubDelegation === false) {
|
|
10839
|
+
classifyDelegationAuthority(path, actions, requestedExpiry) {
|
|
10840
|
+
let coveredOnlyByCaveated = false;
|
|
10841
|
+
for (const key of this.registry.getAllKeys()) {
|
|
10842
|
+
for (const delegation of this.registry.getDelegationsForKey(key.id)) {
|
|
10843
|
+
if (delegation.spaceId === void 0 || !spaceIdsEqual(delegation.spaceId, this.session?.spaceId ?? "")) {
|
|
10795
10844
|
continue;
|
|
10796
10845
|
}
|
|
10797
|
-
|
|
10798
|
-
if (
|
|
10846
|
+
if (!this.registry.isDelegationValid(delegation)) continue;
|
|
10847
|
+
if (delegation.expiry < requestedExpiry) continue;
|
|
10848
|
+
if (delegation.allowSubDelegation === false) continue;
|
|
10849
|
+
if (!this.pathMatches(delegation.path || "", path)) continue;
|
|
10850
|
+
const delegationActions = delegation.actions || [];
|
|
10851
|
+
if (!actions.every((action) => delegationActions.includes(action) || delegationActions.includes("*"))) {
|
|
10799
10852
|
continue;
|
|
10800
10853
|
}
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
(action) => delegationActions.includes(action) || delegationActions.includes("*")
|
|
10804
|
-
);
|
|
10805
|
-
if (!hasAllActions) {
|
|
10854
|
+
if ((delegation.caveats?.length ?? 0) > 0) {
|
|
10855
|
+
coveredOnlyByCaveated = true;
|
|
10806
10856
|
continue;
|
|
10807
10857
|
}
|
|
10808
|
-
return
|
|
10858
|
+
return "ok";
|
|
10809
10859
|
}
|
|
10810
10860
|
}
|
|
10811
|
-
return
|
|
10861
|
+
return coveredOnlyByCaveated ? "caveated" : "not-covered";
|
|
10812
10862
|
}
|
|
10813
10863
|
retiredGraphError() {
|
|
10814
10864
|
try {
|
|
@@ -10907,11 +10957,23 @@ var SharingService = class {
|
|
|
10907
10957
|
const errorText = await registerRes.text();
|
|
10908
10958
|
const registrationTextActiveError = this.retiredGraphError();
|
|
10909
10959
|
if (registrationTextActiveError) return { ok: false, error: registrationTextActiveError };
|
|
10960
|
+
const message = `Failed to register delegation with server: ${registerRes.status} ${errorText}`;
|
|
10961
|
+
if (registerRes.status === 401 || registerRes.status === 403) {
|
|
10962
|
+
return {
|
|
10963
|
+
ok: false,
|
|
10964
|
+
error: createError2(
|
|
10965
|
+
DelegationErrorCodes.AUTH_UNAUTHORIZED,
|
|
10966
|
+
message,
|
|
10967
|
+
void 0,
|
|
10968
|
+
{ status: registerRes.status }
|
|
10969
|
+
)
|
|
10970
|
+
};
|
|
10971
|
+
}
|
|
10910
10972
|
return {
|
|
10911
10973
|
ok: false,
|
|
10912
10974
|
error: createError2(
|
|
10913
10975
|
DelegationErrorCodes.CREATION_FAILED,
|
|
10914
|
-
|
|
10976
|
+
message
|
|
10915
10977
|
)
|
|
10916
10978
|
};
|
|
10917
10979
|
}
|
|
@@ -17653,6 +17715,13 @@ var LocationRecordValidationError = class extends Error {
|
|
|
17653
17715
|
this.name = "LocationRecordValidationError";
|
|
17654
17716
|
}
|
|
17655
17717
|
};
|
|
17718
|
+
var LocationRegistryHttpError = class extends Error {
|
|
17719
|
+
constructor(message, status) {
|
|
17720
|
+
super(message);
|
|
17721
|
+
this.status = status;
|
|
17722
|
+
this.name = "LocationRegistryHttpError";
|
|
17723
|
+
}
|
|
17724
|
+
};
|
|
17656
17725
|
var CloudLocationResolutionError = class extends Error {
|
|
17657
17726
|
constructor(subject, attempts) {
|
|
17658
17727
|
super(`Unable to resolve TinyCloud location for ${subject}`);
|
|
@@ -17746,7 +17815,7 @@ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fe
|
|
|
17746
17815
|
return null;
|
|
17747
17816
|
}
|
|
17748
17817
|
if (!response.ok) {
|
|
17749
|
-
throw new
|
|
17818
|
+
throw new LocationRegistryHttpError(`location registry returned HTTP ${response.status}`, response.status);
|
|
17750
17819
|
}
|
|
17751
17820
|
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
17752
17821
|
let body;
|
|
@@ -17791,7 +17860,7 @@ async function publishLocationRecord(options2) {
|
|
|
17791
17860
|
}
|
|
17792
17861
|
);
|
|
17793
17862
|
if (!response.ok) {
|
|
17794
|
-
throw new
|
|
17863
|
+
throw new LocationRegistryHttpError(`location registry publish returned HTTP ${response.status}`, response.status);
|
|
17795
17864
|
}
|
|
17796
17865
|
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
17797
17866
|
let body;
|
|
@@ -18474,6 +18543,7 @@ export {
|
|
|
18474
18543
|
ED25519_JCS_SIGNATURE_SUITE,
|
|
18475
18544
|
EIP191_JCS_SIGNATURE_SUITE,
|
|
18476
18545
|
EMAIL_CREDENTIAL_MAX_AGE_SECONDS,
|
|
18546
|
+
EMAIL_DOMAIN_CREDENTIAL_MAX_AGE_SECONDS,
|
|
18477
18547
|
ENCRYPTION,
|
|
18478
18548
|
ENCRYPTION_MANIFEST_SPACE,
|
|
18479
18549
|
ENCRYPTION_NETWORK_URN_PREFIX,
|
|
@@ -18499,6 +18569,7 @@ export {
|
|
|
18499
18569
|
LOCAL_LINK_PROBE_TIMEOUT_MS,
|
|
18500
18570
|
LOCAL_LOOPBACK_PROBE_TIMEOUT_MS,
|
|
18501
18571
|
LocationRecordValidationError,
|
|
18572
|
+
LocationRegistryHttpError,
|
|
18502
18573
|
MAX_CONTENT_BYTES,
|
|
18503
18574
|
MAX_LEGACY_ENVELOPE_EXPIRES_AT,
|
|
18504
18575
|
MAX_SEALED_SHARE_CONTENT_BYTES,
|
|
@@ -18618,8 +18689,10 @@ export {
|
|
|
18618
18689
|
buildPolicyCredentialPresentationV4,
|
|
18619
18690
|
buildSpaceUri,
|
|
18620
18691
|
canonicalDigest,
|
|
18692
|
+
canonicalEmailDomain,
|
|
18621
18693
|
canonicalHashHex,
|
|
18622
18694
|
canonicalLocationPayload,
|
|
18695
|
+
canonicalMailbox,
|
|
18623
18696
|
canonicalOwnerSharePolicy,
|
|
18624
18697
|
canonicalSignedResponse,
|
|
18625
18698
|
canonicalizeAddress,
|
|
@@ -18647,6 +18720,7 @@ export {
|
|
|
18647
18720
|
createCapabilityKeyRegistry,
|
|
18648
18721
|
createDelegatedShareKey,
|
|
18649
18722
|
createEmailCredentialRequirement,
|
|
18723
|
+
createEmailDomainCredentialRequirement,
|
|
18650
18724
|
createHolderBinding,
|
|
18651
18725
|
createInMemoryLocalNodeIdentityStore,
|
|
18652
18726
|
createLocalStorageLocalNodeIdentityStore,
|
|
@@ -18719,6 +18793,7 @@ export {
|
|
|
18719
18793
|
importPortableDelegation,
|
|
18720
18794
|
installTinyCloudDebugGlobals,
|
|
18721
18795
|
intersectShareCapabilities,
|
|
18796
|
+
isCanonicalEmailDomain,
|
|
18722
18797
|
isCapabilitySubset,
|
|
18723
18798
|
isDigest,
|
|
18724
18799
|
isEvmAddress,
|
|
@@ -18729,6 +18804,7 @@ export {
|
|
|
18729
18804
|
jcsCanonicalize,
|
|
18730
18805
|
loadManifest,
|
|
18731
18806
|
locationPayloadForRecord,
|
|
18807
|
+
mailboxBelongsToDomain,
|
|
18732
18808
|
makePkhSpaceId,
|
|
18733
18809
|
makePublicSpaceId,
|
|
18734
18810
|
manifestAbilitiesUnion,
|
|
@@ -18787,6 +18863,7 @@ export {
|
|
|
18787
18863
|
serviceError5 as serviceError,
|
|
18788
18864
|
sha256Base64Url,
|
|
18789
18865
|
shareCapabilityAllows,
|
|
18866
|
+
signCompactPolicyDescendant,
|
|
18790
18867
|
signCompactUcanAuthorization,
|
|
18791
18868
|
signCompactUcanRootAuthorization,
|
|
18792
18869
|
signLocationRecord,
|