@tinycloud/share-sdk 1.0.0-beta.7 → 1.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +76 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +74 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,7 @@ __export(index_exports, {
|
|
|
45
45
|
authorizationMethodForTarget: () => authorizationMethodForTarget,
|
|
46
46
|
authorizeShare: () => authorizeShare,
|
|
47
47
|
canonicalOwnerSharePolicy: () => canonicalOwnerSharePolicy,
|
|
48
|
+
canonicalShareFilename: () => canonicalShareFilename,
|
|
48
49
|
canonicalize: () => canonicalize,
|
|
49
50
|
checkBearerDelegation: () => checkBearerDelegation,
|
|
50
51
|
claimShare: () => claimShare,
|
|
@@ -56,6 +57,7 @@ __export(index_exports, {
|
|
|
56
57
|
createShareV2HolderBindingArtifact: () => createShareV2HolderBindingArtifact,
|
|
57
58
|
deliverCredentialInvitation: () => deliverCredentialInvitation,
|
|
58
59
|
fromBase64Url: () => fromBase64Url,
|
|
60
|
+
hasUnsafeFilenameCodePoint: () => hasUnsafeFilenameCodePoint,
|
|
59
61
|
historyRecordForPublishedShare: () => historyRecordForPublishedShare,
|
|
60
62
|
inspectShare: () => inspectShare,
|
|
61
63
|
listShares: () => listShares,
|
|
@@ -85,6 +87,19 @@ __export(index_exports, {
|
|
|
85
87
|
});
|
|
86
88
|
module.exports = __toCommonJS(index_exports);
|
|
87
89
|
|
|
90
|
+
// src/filename-policy.ts
|
|
91
|
+
var UNSAFE_FILENAME_CODE_POINT = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
|
|
92
|
+
function hasUnsafeFilenameCodePoint(value) {
|
|
93
|
+
return UNSAFE_FILENAME_CODE_POINT.test(value);
|
|
94
|
+
}
|
|
95
|
+
function canonicalShareFilename(value) {
|
|
96
|
+
const canonical = value.normalize("NFC");
|
|
97
|
+
if (canonical.length === 0 || canonical === "." || canonical === ".." || canonical.includes("/") || canonical.includes("\\") || hasUnsafeFilenameCodePoint(canonical)) {
|
|
98
|
+
throw new TypeError("share filename is unsafe");
|
|
99
|
+
}
|
|
100
|
+
return canonical;
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
// ../share-envelope/dist/index.js
|
|
89
104
|
var import_sha2 = require("@noble/hashes/sha2");
|
|
90
105
|
var import_ed25519 = require("@noble/curves/ed25519");
|
|
@@ -6566,8 +6581,17 @@ function normalizeShareTarget(target) {
|
|
|
6566
6581
|
function targetAuthorizationMethod(target) {
|
|
6567
6582
|
return authorizationMethodForTarget(target);
|
|
6568
6583
|
}
|
|
6584
|
+
function publishFilename(value) {
|
|
6585
|
+
try {
|
|
6586
|
+
return canonicalShareFilename(value);
|
|
6587
|
+
} catch {
|
|
6588
|
+
throw new SharePublishError("invalid-argument", hasUnsafeFilenameCodePoint(value) ? "filename contains control or invisible characters" : "filename must be one safe path segment");
|
|
6589
|
+
}
|
|
6590
|
+
}
|
|
6569
6591
|
async function publishTargetShare(input) {
|
|
6570
6592
|
const target = normalizeShareTarget(input.target);
|
|
6593
|
+
const filename = publishFilename(input.filename);
|
|
6594
|
+
const files = input.files?.map((file) => ({ ...file, filename: publishFilename(file.filename) }));
|
|
6571
6595
|
if (input.targetAdapter === void 0) {
|
|
6572
6596
|
if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
|
|
6573
6597
|
return {
|
|
@@ -6593,9 +6617,6 @@ async function publishTargetShare(input) {
|
|
|
6593
6617
|
return bytes3;
|
|
6594
6618
|
})();
|
|
6595
6619
|
if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
6596
|
-
if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
|
|
6597
|
-
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
6598
|
-
}
|
|
6599
6620
|
if (target.kind === "bearer" && input.allowBinary !== true) {
|
|
6600
6621
|
try {
|
|
6601
6622
|
new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
@@ -6603,10 +6624,10 @@ async function publishTargetShare(input) {
|
|
|
6603
6624
|
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
6604
6625
|
}
|
|
6605
6626
|
}
|
|
6606
|
-
const
|
|
6627
|
+
const publishFiles = files === void 0 || files.length === 0 ? [{ bytes: source }] : files;
|
|
6607
6628
|
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
6608
6629
|
let totalBytes = 0;
|
|
6609
|
-
for (const file of
|
|
6630
|
+
for (const file of publishFiles) {
|
|
6610
6631
|
totalBytes += file.bytes.byteLength;
|
|
6611
6632
|
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
6612
6633
|
throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
|
|
@@ -6617,8 +6638,8 @@ async function publishTargetShare(input) {
|
|
|
6617
6638
|
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
6618
6639
|
return input.targetAdapter.publish({
|
|
6619
6640
|
source,
|
|
6620
|
-
filename
|
|
6621
|
-
...
|
|
6641
|
+
filename,
|
|
6642
|
+
...files === void 0 ? {} : { files },
|
|
6622
6643
|
...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
|
|
6623
6644
|
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
6624
6645
|
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
@@ -6646,6 +6667,7 @@ function targetMatcher(target) {
|
|
|
6646
6667
|
function targetKind(target) {
|
|
6647
6668
|
return target.kind;
|
|
6648
6669
|
}
|
|
6670
|
+
var OWNER_SHARE_ACTIONS = /* @__PURE__ */ new Set(["tinycloud.kv/get", "tinycloud.kv/list", "tinycloud.kv/metadata", "tinycloud.kv/put"]);
|
|
6649
6671
|
function assertSafeInput(input) {
|
|
6650
6672
|
if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
|
|
6651
6673
|
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
|
|
@@ -6768,14 +6790,18 @@ function addressedCredentialRequirement(target) {
|
|
|
6768
6790
|
return mailboxCredentialCommitment(normalized);
|
|
6769
6791
|
}
|
|
6770
6792
|
function prepareAddressedShare(request) {
|
|
6771
|
-
|
|
6793
|
+
try {
|
|
6794
|
+
canonicalShareFilename(request.filename);
|
|
6795
|
+
} catch {
|
|
6796
|
+
throw new TypeError("addressed filename is invalid");
|
|
6797
|
+
}
|
|
6772
6798
|
if (request.actions.length === 0 || request.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
6799
|
+
if (request.policyActions.some((action) => !OWNER_SHARE_ACTIONS.has(action))) throw new TypeError("addressed share action is not supported");
|
|
6773
6800
|
const target = normalizeShareTarget(request.target);
|
|
6774
6801
|
if (target.kind === "bearer") throw new TypeError("addressed target is required");
|
|
6775
6802
|
if (target.kind === "recipientDid") return { target };
|
|
6776
|
-
if (target.kind === "emailDomain") {
|
|
6777
|
-
|
|
6778
|
-
if (request.deliveryEmail !== void 0) throw new TypeError("email-domain shares are not emailed");
|
|
6803
|
+
if (target.kind === "emailDomain" && request.deliveryEmail !== void 0 && (request.deliveryEmail !== request.deliveryEmail.toLowerCase() || !request.deliveryEmail.endsWith(`@${target.domain}`))) {
|
|
6804
|
+
throw new TypeError("email-domain delivery address must be a lowercase mailbox at the domain");
|
|
6779
6805
|
}
|
|
6780
6806
|
return { target, credentialRequirement: mailboxCredentialCommitment(target) };
|
|
6781
6807
|
}
|
|
@@ -6786,9 +6812,10 @@ function assertMailboxCommitment(commitment, target) {
|
|
|
6786
6812
|
if (canonicalize(commitment.profile) !== canonicalize(expected.profile) || canonicalize(commitment.credentialType) !== canonicalize(expected.credentialType) || commitment.descriptorDigest !== expected.descriptorDigest || commitment.issuerDid !== expected.issuerDid || commitment.issuerKid !== expected.issuerKid) throw new TypeError(`${label} shares require the ${label} credential profile`);
|
|
6787
6813
|
if (commitment.requirementDigest !== expected.requirementDigest) throw new TypeError(`credential requirement is not bound to the email ${target.kind === "email" ? "address" : "domain"}`);
|
|
6788
6814
|
}
|
|
6789
|
-
async function publishAddressedShare(
|
|
6790
|
-
assertSafeInput(
|
|
6791
|
-
const { target } = prepareAddressedShare(
|
|
6815
|
+
async function publishAddressedShare(input) {
|
|
6816
|
+
assertSafeInput(input);
|
|
6817
|
+
const { target } = prepareAddressedShare(input);
|
|
6818
|
+
const options = { ...input, filename: canonicalShareFilename(input.filename) };
|
|
6792
6819
|
if (target.kind !== "recipientDid") assertMailboxCommitment(options.credentialRequirement, target);
|
|
6793
6820
|
const expiry = rfc3339Seconds(options.expiresAt);
|
|
6794
6821
|
const matcher = targetMatcher(target);
|
|
@@ -8283,9 +8310,28 @@ function verifyV3PolicyAuthorization(input) {
|
|
|
8283
8310
|
const binding = object3(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
8284
8311
|
const policy = object3(input.envelope.policy, "v3 policy");
|
|
8285
8312
|
const now = Math.floor(Date.now() / 1e3);
|
|
8286
|
-
if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || compact.payload.exp
|
|
8313
|
+
if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || !(compact.payload.exp * 1e3 <= Date.parse(input.envelope.expiry)) || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
|
|
8287
8314
|
return compact;
|
|
8288
8315
|
}
|
|
8316
|
+
function verifyV3PolicyDescendants(session, descendants, holderDid) {
|
|
8317
|
+
let parent = session;
|
|
8318
|
+
for (const link of descendants) {
|
|
8319
|
+
const child = verifyCompactUcanAuthorization(link.authorization, link.cid);
|
|
8320
|
+
const { remainingRedelegationDepth: parentDepth, ...parentFacts } = parent.payload.fct[0];
|
|
8321
|
+
const { remainingRedelegationDepth: depth, ...facts } = child.payload.fct[0];
|
|
8322
|
+
if (child.payload.iss.split("#", 1)[0] !== parent.payload.aud || child.payload.prf.length !== 1 || child.payload.prf[0] !== parent.cid || child.payload.nbf <= parent.payload.nbf || child.payload.exp >= parent.payload.exp || typeof parentDepth !== "number" || parentDepth <= 0 || depth !== parentDepth - 1 || canonicalize(facts) !== canonicalize(parentFacts) || canonicalize(child.payload.att) !== canonicalize(parent.payload.att)) throw new Error("v3 policy descendant does not extend its parent");
|
|
8323
|
+
parent = child;
|
|
8324
|
+
}
|
|
8325
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
8326
|
+
if (parent.payload.aud !== holderDid) throw new Error("v3 policy delegation chain does not end at this holder");
|
|
8327
|
+
if (parent.payload.nbf > now || parent.payload.exp <= now) throw new Error("v3 policy delegation chain is not currently valid");
|
|
8328
|
+
return parent;
|
|
8329
|
+
}
|
|
8330
|
+
function requestedSessionExpiry(expiry) {
|
|
8331
|
+
const seconds = Math.floor(Date.parse(expiry) / 1e3);
|
|
8332
|
+
if (!Number.isFinite(seconds) || seconds <= Math.floor(Date.now() / 1e3) + 60) return void 0;
|
|
8333
|
+
return new Date(seconds * 1e3).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8334
|
+
}
|
|
8289
8335
|
function hex3(value) {
|
|
8290
8336
|
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8291
8337
|
}
|
|
@@ -8338,14 +8384,17 @@ var ShareRecipientClient = class {
|
|
|
8338
8384
|
this.signer = options.sign;
|
|
8339
8385
|
if (options.policyAuthorization !== void 0) {
|
|
8340
8386
|
if (options.envelope.version !== 3) throw new Error("policy delegation fast path requires a v3 envelope");
|
|
8341
|
-
const
|
|
8387
|
+
const { authorization, cid: cid2, descendants = [] } = options.policyAuthorization;
|
|
8388
|
+
const sessionHolder = descendants.length === 0 ? options.holderDid : verifyCompactUcanAuthorization(authorization, cid2).payload.aud;
|
|
8389
|
+
const compact = verifyV3PolicyAuthorization({ authorization, cid: cid2, envelope: options.envelope, holderDid: sessionHolder });
|
|
8390
|
+
const leaf = verifyV3PolicyDescendants(compact, descendants, options.holderDid);
|
|
8342
8391
|
const nodeAudience = compact.payload.fct[0].nodeAudience;
|
|
8343
|
-
this.v3Authorization =
|
|
8392
|
+
this.v3Authorization = leaf.authorization;
|
|
8344
8393
|
this.v3NodeAudience = nodeAudience;
|
|
8345
8394
|
this.nativeSigner = options.sign;
|
|
8346
8395
|
this.session = {
|
|
8347
|
-
sessionId:
|
|
8348
|
-
expiresAt: new Date(
|
|
8396
|
+
sessionId: leaf.cid,
|
|
8397
|
+
expiresAt: new Date(leaf.payload.exp * 1e3).toISOString(),
|
|
8349
8398
|
actions: options.envelope.actions,
|
|
8350
8399
|
resource: options.envelope.resource
|
|
8351
8400
|
};
|
|
@@ -8415,13 +8464,17 @@ var ShareRecipientClient = class {
|
|
|
8415
8464
|
throw new Error(accountless ? "v4 ceremony requires a verified credential and requirement" : "v3 ceremony requires a claim");
|
|
8416
8465
|
}
|
|
8417
8466
|
this.options.onStage?.("policy-admission");
|
|
8418
|
-
const
|
|
8467
|
+
const mintBody = accountless ? { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, requirement: material.requirement, credential: material.credentialEnvelope, presentation: material.presentation } : { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation };
|
|
8468
|
+
const mint = (body) => this.fetchFn(new URL("/policy/v3/delegations", this.options.nodeOrigin), {
|
|
8419
8469
|
method: "POST",
|
|
8420
8470
|
redirect: "error",
|
|
8421
8471
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
8422
|
-
body: JSON.stringify(
|
|
8472
|
+
body: JSON.stringify(body),
|
|
8423
8473
|
...this.options.signal === void 0 ? {} : { signal: this.options.signal }
|
|
8424
8474
|
});
|
|
8475
|
+
const requestedExpiresAt = requestedSessionExpiry(envelope.expiry);
|
|
8476
|
+
let delegationResponse = await mint(requestedExpiresAt === void 0 ? mintBody : { ...mintBody, requestedExpiresAt });
|
|
8477
|
+
if (requestedExpiresAt !== void 0 && delegationResponse.status === 422) delegationResponse = await mint(mintBody);
|
|
8425
8478
|
if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
|
|
8426
8479
|
const delegation = object3(await delegationResponse.json(), "v3 policy delegation");
|
|
8427
8480
|
if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
|
|
@@ -8655,6 +8708,7 @@ async function openNativeShare(sharing, link) {
|
|
|
8655
8708
|
authorizationMethodForTarget,
|
|
8656
8709
|
authorizeShare,
|
|
8657
8710
|
canonicalOwnerSharePolicy,
|
|
8711
|
+
canonicalShareFilename,
|
|
8658
8712
|
canonicalize,
|
|
8659
8713
|
checkBearerDelegation,
|
|
8660
8714
|
claimShare,
|
|
@@ -8666,6 +8720,7 @@ async function openNativeShare(sharing, link) {
|
|
|
8666
8720
|
createShareV2HolderBindingArtifact,
|
|
8667
8721
|
deliverCredentialInvitation,
|
|
8668
8722
|
fromBase64Url,
|
|
8723
|
+
hasUnsafeFilenameCodePoint,
|
|
8669
8724
|
historyRecordForPublishedShare,
|
|
8670
8725
|
inspectShare,
|
|
8671
8726
|
listShares,
|