@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/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", ID);
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 (!ID2.test(key) || typeof value2 !== "string" || value2.length === 0 || value2.length > 4096) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claim is invalid");
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 > 31 * 24 * 60 * 60)
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 createCompactPolicyDescendant(input) {
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: [{ ...parentFact, remainingRedelegationDepth: depth - 1 }],
4100
+ exp: claims.expiresAt,
4101
+ fct: [claims.fact],
4072
4102
  iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
4073
- nbf: now,
4074
- nnc: input.nonce ?? encodeBase64Url2(crypto.getRandomValues(new Uint8Array(16))),
4075
- prf: [parent.cid]
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 > 60 || parsed.payload.iss.split("#", 1)[0] !== fact.nodeAudience || parsed.payload.aud !== fact.recipientDid)
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 fetchFn(
4619
+ const response = await postPolicyDelegation(
4620
+ fetchFn,
4576
4621
  new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
4577
4622
  {
4578
- method: "POST",
4579
- redirect: "error",
4580
- signal: input.signal,
4581
- headers: { accept: "application/json", "content-type": "application/json" },
4582
- body: JSON.stringify({
4583
- policyCid: input.policyCid,
4584
- challengeId: challenge.challengeId,
4585
- nonce: challenge.nonce,
4586
- requirement: validateCredentialRequirement(input.requirement),
4587
- credential: envelopeFromVerified(input.credential),
4588
- accountAuthorizationCid,
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 fetchFn(new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin), {
4634
- method: "POST",
4635
- redirect: "error",
4636
- signal: input.signal,
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 = 7 * 24 * 60 * 60 * 1e3;
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" && (url.hash.startsWith("#tc2=") || url.searchParams.has("tc2"))) {
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/") || url.pathname === "/viewer";
8992
+ return url.search === "" && url.pathname.startsWith("/s/");
8959
8993
  } catch {
8960
- return link.startsWith(INLINE_PREFIX);
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.findSuitableKeyForDelegation(
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
- * Check if any key in the registry can satisfy the delegation request.
10770
- * A key can satisfy if it has a delegation that:
10771
- * 1. Covers the required path (exact match or parent path)
10772
- * 2. Has all required actions
10773
- * 3. Has sufficient expiry (delegation.expiry >= requestedExpiry)
10774
- * 4. Allows sub-delegation
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
- findSuitableKeyForDelegation(path, actions, requestedExpiry) {
10778
- const allKeys = this.registry.getAllKeys();
10779
- for (const key of allKeys) {
10780
- const delegations = this.registry.getDelegationsForKey(key.id);
10781
- for (const delegation of delegations) {
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
- const delegationPath = delegation.path || "";
10798
- if (!this.pathMatches(delegationPath, path)) {
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
- const delegationActions = delegation.actions || [];
10802
- const hasAllActions = actions.every(
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 true;
10858
+ return "ok";
10809
10859
  }
10810
10860
  }
10811
- return false;
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
- `Failed to register delegation with server: ${registerRes.status} ${errorText}`
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 Error(`location registry returned HTTP ${response.status}`);
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 Error(`location registry publish returned HTTP ${response.status}`);
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,