@tinycloud/share-sdk 0.3.0-beta.1 → 1.0.0-beta.3
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 +786 -1856
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +186 -316
- package/dist/index.d.ts +186 -316
- package/dist/index.js +782 -1846
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -29,12 +29,11 @@ __export(index_exports, {
|
|
|
29
29
|
MemorySenderShareKeyStorage: () => MemorySenderShareKeyStorage,
|
|
30
30
|
MemorySenderShareRecordStorage: () => MemorySenderShareRecordStorage,
|
|
31
31
|
MemoryShareCache: () => MemoryShareCache,
|
|
32
|
+
NATIVE_SHARE_FRAGMENT_PARAMETER: () => NATIVE_SHARE_FRAGMENT_PARAMETER,
|
|
32
33
|
OWNER_SHARE_REGISTRATION_DOMAIN: () => OWNER_SHARE_REGISTRATION_DOMAIN,
|
|
33
34
|
SHARE_CONTENT_LIMIT: () => SHARE_CONTENT_LIMIT,
|
|
34
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN: () => SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
35
35
|
SHARE_PUBLISH_RESULT_VERSION: () => SHARE_PUBLISH_RESULT_VERSION,
|
|
36
36
|
SHARE_RESULT_VERSION: () => SHARE_RESULT_VERSION,
|
|
37
|
-
SHARE_SEALED_OVERHEAD: () => SHARE_SEALED_OVERHEAD,
|
|
38
37
|
SHARE_V2_PROTOCOL: () => SHARE_V2_PROTOCOL,
|
|
39
38
|
SenderShareStore: () => SenderShareStore,
|
|
40
39
|
ShareNotifyError: () => ShareNotifyError,
|
|
@@ -50,23 +49,21 @@ __export(index_exports, {
|
|
|
50
49
|
computeOwnerShareRegistrationCid: () => computeOwnerShareRegistrationCid,
|
|
51
50
|
createAddressedAuthorization: () => createAddressedAuthorization,
|
|
52
51
|
createDelegatedShareKey: () => createDelegatedShareKey,
|
|
52
|
+
createNativeShare: () => createNativeShare,
|
|
53
53
|
createPolicyEnforcementDelegation: () => createPolicyEnforcementDelegation,
|
|
54
|
-
createRegisteredPolicyAuthority: () => createRegisteredPolicyAuthority,
|
|
55
54
|
createShareV2HolderBindingArtifact: () => createShareV2HolderBindingArtifact,
|
|
56
55
|
fromBase64Url: () => fromBase64Url,
|
|
57
56
|
historyRecordForPublishedShare: () => historyRecordForPublishedShare,
|
|
58
57
|
inspectShare: () => inspectShare,
|
|
59
|
-
isLegacyShareLink: () => isLegacyShareLink,
|
|
60
58
|
listShares: () => listShares,
|
|
61
|
-
migrateShare: () => migrateShare,
|
|
62
59
|
normalizeShareTarget: () => normalizeShareTarget,
|
|
63
60
|
notifyShare: () => notifyShare,
|
|
61
|
+
openNativeShare: () => openNativeShare,
|
|
64
62
|
parseAddressedEnvelope: () => parseAddressedEnvelope,
|
|
63
|
+
parseNativeShareUrl: () => parseNativeShareUrl,
|
|
65
64
|
publishAddressedShare: () => publishAddressedShare,
|
|
66
65
|
publishPolicyShare: () => publishPolicyShare,
|
|
67
|
-
publishShare: () => publishShare,
|
|
68
66
|
publishTargetShare: () => publishTargetShare,
|
|
69
|
-
receiveLegacyShare: () => receiveLegacyShare,
|
|
70
67
|
receiveShare: () => receiveShare,
|
|
71
68
|
recipientMatchesShareRecord: () => recipientMatchesShareRecord,
|
|
72
69
|
redactPublishedShare: () => redactPublishedShare,
|
|
@@ -75,15 +72,12 @@ __export(index_exports, {
|
|
|
75
72
|
restoreDelegatedShareKey: () => restoreDelegatedShareKey,
|
|
76
73
|
resumeShareAuthorization: () => resumeShareAuthorization,
|
|
77
74
|
revokeShare: () => revokeShare,
|
|
78
|
-
shareDeliveryTrustedKid: () => shareDeliveryTrustedKid,
|
|
79
75
|
showShare: () => showShare,
|
|
80
76
|
targetAuthorizationMethod: () => targetAuthorizationMethod,
|
|
81
77
|
toBase64Url: () => toBase64Url,
|
|
82
78
|
toShareErrorInfo: () => toShareErrorInfo,
|
|
83
79
|
validateOwnerSharePolicyRegistration: () => validateOwnerSharePolicyRegistration,
|
|
84
|
-
validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes
|
|
85
|
-
validateShareDeliveryAuthorizationBytes: () => validateShareDeliveryAuthorizationBytes,
|
|
86
|
-
verifyBearerEnvelope: () => verifyBearerEnvelope
|
|
80
|
+
validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes
|
|
87
81
|
});
|
|
88
82
|
module.exports = __toCommonJS(index_exports);
|
|
89
83
|
|
|
@@ -4614,25 +4608,25 @@ function encodeTo(int, target, offset = 0) {
|
|
|
4614
4608
|
function encodingLength(int) {
|
|
4615
4609
|
return varint_default.encodingLength(int);
|
|
4616
4610
|
}
|
|
4617
|
-
function create(code22,
|
|
4618
|
-
const size =
|
|
4611
|
+
function create(code22, digest3) {
|
|
4612
|
+
const size = digest3.byteLength;
|
|
4619
4613
|
const sizeOffset = encodingLength(code22);
|
|
4620
4614
|
const digestOffset = sizeOffset + encodingLength(size);
|
|
4621
4615
|
const bytes3 = new Uint8Array(digestOffset + size);
|
|
4622
4616
|
encodeTo(code22, bytes3, 0);
|
|
4623
4617
|
encodeTo(size, bytes3, sizeOffset);
|
|
4624
|
-
bytes3.set(
|
|
4625
|
-
return new Digest(code22, size,
|
|
4618
|
+
bytes3.set(digest3, digestOffset);
|
|
4619
|
+
return new Digest(code22, size, digest3, bytes3);
|
|
4626
4620
|
}
|
|
4627
4621
|
function decode4(multihash) {
|
|
4628
4622
|
const bytes3 = coerce2(multihash);
|
|
4629
4623
|
const [code22, sizeOffset] = decode3(bytes3);
|
|
4630
4624
|
const [size, digestOffset] = decode3(bytes3.subarray(sizeOffset));
|
|
4631
|
-
const
|
|
4632
|
-
if (
|
|
4625
|
+
const digest3 = bytes3.subarray(sizeOffset + digestOffset);
|
|
4626
|
+
if (digest3.byteLength !== size) {
|
|
4633
4627
|
throw new Error("Incorrect length");
|
|
4634
4628
|
}
|
|
4635
|
-
return new Digest(code22, size,
|
|
4629
|
+
return new Digest(code22, size, digest3, bytes3);
|
|
4636
4630
|
}
|
|
4637
4631
|
function equals2(a, b) {
|
|
4638
4632
|
if (a === b) {
|
|
@@ -4650,10 +4644,10 @@ var Digest = class {
|
|
|
4650
4644
|
/**
|
|
4651
4645
|
* Creates a multihash digest.
|
|
4652
4646
|
*/
|
|
4653
|
-
constructor(code22, size,
|
|
4647
|
+
constructor(code22, size, digest3, bytes3) {
|
|
4654
4648
|
this.code = code22;
|
|
4655
4649
|
this.size = size;
|
|
4656
|
-
this.digest =
|
|
4650
|
+
this.digest = digest3;
|
|
4657
4651
|
this.bytes = bytes3;
|
|
4658
4652
|
}
|
|
4659
4653
|
};
|
|
@@ -4734,8 +4728,8 @@ var CID = class _CID {
|
|
|
4734
4728
|
toV1() {
|
|
4735
4729
|
switch (this.version) {
|
|
4736
4730
|
case 0: {
|
|
4737
|
-
const { code: code22, digest:
|
|
4738
|
-
const multihash = create(code22,
|
|
4731
|
+
const { code: code22, digest: digest3 } = this.multihash;
|
|
4732
|
+
const multihash = create(code22, digest3);
|
|
4739
4733
|
return _CID.createV1(this.code, multihash);
|
|
4740
4734
|
}
|
|
4741
4735
|
case 1: {
|
|
@@ -4789,8 +4783,8 @@ var CID = class _CID {
|
|
|
4789
4783
|
return new _CID(version, code22, multihash, bytes3 ?? encodeCID(version, code22, multihash.bytes));
|
|
4790
4784
|
} else if (value[cidSymbol] === true) {
|
|
4791
4785
|
const { version, multihash, code: code22 } = value;
|
|
4792
|
-
const
|
|
4793
|
-
return _CID.create(version, code22,
|
|
4786
|
+
const digest3 = decode4(multihash);
|
|
4787
|
+
return _CID.create(version, code22, digest3);
|
|
4794
4788
|
} else {
|
|
4795
4789
|
return null;
|
|
4796
4790
|
}
|
|
@@ -4800,11 +4794,11 @@ var CID = class _CID {
|
|
|
4800
4794
|
* @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
|
|
4801
4795
|
* @param digest - (Multi)hash of the of the content.
|
|
4802
4796
|
*/
|
|
4803
|
-
static create(version, code22,
|
|
4797
|
+
static create(version, code22, digest3) {
|
|
4804
4798
|
if (typeof code22 !== "number") {
|
|
4805
4799
|
throw new Error("String codecs are no longer supported");
|
|
4806
4800
|
}
|
|
4807
|
-
if (!(
|
|
4801
|
+
if (!(digest3.bytes instanceof Uint8Array)) {
|
|
4808
4802
|
throw new Error("Invalid digest");
|
|
4809
4803
|
}
|
|
4810
4804
|
switch (version) {
|
|
@@ -4812,12 +4806,12 @@ var CID = class _CID {
|
|
|
4812
4806
|
if (code22 !== DAG_PB_CODE) {
|
|
4813
4807
|
throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE}) block encoding`);
|
|
4814
4808
|
} else {
|
|
4815
|
-
return new _CID(version, code22,
|
|
4809
|
+
return new _CID(version, code22, digest3, digest3.bytes);
|
|
4816
4810
|
}
|
|
4817
4811
|
}
|
|
4818
4812
|
case 1: {
|
|
4819
|
-
const bytes3 = encodeCID(version, code22,
|
|
4820
|
-
return new _CID(version, code22,
|
|
4813
|
+
const bytes3 = encodeCID(version, code22, digest3.bytes);
|
|
4814
|
+
return new _CID(version, code22, digest3, bytes3);
|
|
4821
4815
|
}
|
|
4822
4816
|
default: {
|
|
4823
4817
|
throw new Error("Invalid version");
|
|
@@ -4827,8 +4821,8 @@ var CID = class _CID {
|
|
|
4827
4821
|
/**
|
|
4828
4822
|
* Simplified version of `create` for CIDv0.
|
|
4829
4823
|
*/
|
|
4830
|
-
static createV0(
|
|
4831
|
-
return _CID.create(0, DAG_PB_CODE,
|
|
4824
|
+
static createV0(digest3) {
|
|
4825
|
+
return _CID.create(0, DAG_PB_CODE, digest3);
|
|
4832
4826
|
}
|
|
4833
4827
|
/**
|
|
4834
4828
|
* Simplified version of `create` for CIDv1.
|
|
@@ -4836,8 +4830,8 @@ var CID = class _CID {
|
|
|
4836
4830
|
* @param code - Content encoding format code.
|
|
4837
4831
|
* @param digest - Multihash of the content.
|
|
4838
4832
|
*/
|
|
4839
|
-
static createV1(code22,
|
|
4840
|
-
return _CID.create(1, code22,
|
|
4833
|
+
static createV1(code22, digest3) {
|
|
4834
|
+
return _CID.create(1, code22, digest3);
|
|
4841
4835
|
}
|
|
4842
4836
|
/**
|
|
4843
4837
|
* Decoded a CID from its binary representation. The byte array must contain
|
|
@@ -4870,8 +4864,8 @@ var CID = class _CID {
|
|
|
4870
4864
|
throw new Error("Incorrect length");
|
|
4871
4865
|
}
|
|
4872
4866
|
const digestBytes3 = multihashBytes.subarray(specs.multihashSize - specs.digestSize);
|
|
4873
|
-
const
|
|
4874
|
-
const cid2 = specs.version === 0 ? _CID.createV0(
|
|
4867
|
+
const digest3 = new Digest(specs.multihashCode, specs.digestSize, digestBytes3, multihashBytes);
|
|
4868
|
+
const cid2 = specs.version === 0 ? _CID.createV0(digest3) : _CID.createV1(specs.codec, digest3);
|
|
4875
4869
|
return [cid2, bytes3.subarray(specs.size)];
|
|
4876
4870
|
}
|
|
4877
4871
|
/**
|
|
@@ -4994,8 +4988,8 @@ var cidSymbol = /* @__PURE__ */ Symbol.for("@ipld/js-cid/CID");
|
|
|
4994
4988
|
var code = 85;
|
|
4995
4989
|
var SHA256_CODE = 18;
|
|
4996
4990
|
async function computeCid(bytes3) {
|
|
4997
|
-
const
|
|
4998
|
-
return CID.create(1, code,
|
|
4991
|
+
const digest3 = create(SHA256_CODE, (0, import_sha2.sha256)(new Uint8Array(bytes3)));
|
|
4992
|
+
return CID.create(1, code, digest3).toString();
|
|
4999
4993
|
}
|
|
5000
4994
|
function isCanonicalRawCid(cidString) {
|
|
5001
4995
|
let cid2;
|
|
@@ -5442,7 +5436,7 @@ function validateV3Invariants(value, ctx) {
|
|
|
5442
5436
|
if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
|
|
5443
5437
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
|
|
5444
5438
|
}
|
|
5445
|
-
if (value.attestedEnforcerBinding.
|
|
5439
|
+
if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
5446
5440
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
5447
5441
|
}
|
|
5448
5442
|
if (value.policy.contentSource.shareId !== value.contentSource.shareId || value.policy.contentSource.kvResource !== value.contentSource.kvResource || value.policy.contentSource.selector !== value.contentSource.selector || value.policy.contentSource.encryptionNetwork !== value.encryptionNetwork || value.policy.contentSource.encryptedSymmetricKeyDigestHex !== value.contentSource.encryptedSymmetricKeyDigestHex || value.policy.contentSource.keyVersion !== value.contentSource.keyVersion || value.policy.contentSource.mode !== value.contentSource.mode || value.policy.contentSource.initialCiphertextDigestHex !== value.contentSource.initialCiphertextDigestHex) {
|
|
@@ -5637,105 +5631,8 @@ function checkBearerDelegation(envelope, options = {}) {
|
|
|
5637
5631
|
}
|
|
5638
5632
|
return { ok: true, delegateeDid: audience };
|
|
5639
5633
|
}
|
|
5640
|
-
function mintBearerDelegation(options) {
|
|
5641
|
-
const { issuerPrivateKey, audienceDid, resourceUri, expiresAtSeconds } = options;
|
|
5642
|
-
if (!audienceDid.startsWith("did:")) {
|
|
5643
|
-
throw new TypeError(`audienceDid must be a DID, got ${audienceDid}`);
|
|
5644
|
-
}
|
|
5645
|
-
if (resourceUri.length === 0) {
|
|
5646
|
-
throw new TypeError("resourceUri must be non-empty");
|
|
5647
|
-
}
|
|
5648
|
-
if (!Number.isSafeInteger(expiresAtSeconds) || expiresAtSeconds <= 0) {
|
|
5649
|
-
throw new TypeError(
|
|
5650
|
-
`expiresAtSeconds must be a positive integer (epoch seconds), got ${expiresAtSeconds}`
|
|
5651
|
-
);
|
|
5652
|
-
}
|
|
5653
|
-
const issuerDid = didKeyFromEd25519PublicKey(
|
|
5654
|
-
import_ed25519.ed25519.getPublicKey(issuerPrivateKey)
|
|
5655
|
-
);
|
|
5656
|
-
const header = { alg: "EdDSA", typ: "JWT", ucv: "0.9.1" };
|
|
5657
|
-
const payload = {
|
|
5658
|
-
iss: issuerDid,
|
|
5659
|
-
aud: audienceDid,
|
|
5660
|
-
att: [{ with: resourceUri, can: BEARER_READ_ABILITY }],
|
|
5661
|
-
prf: [],
|
|
5662
|
-
exp: expiresAtSeconds
|
|
5663
|
-
};
|
|
5664
|
-
const signingInput = `${toBase64Url(utf8Bytes(JSON.stringify(header)))}.${toBase64Url(
|
|
5665
|
-
utf8Bytes(JSON.stringify(payload))
|
|
5666
|
-
)}`;
|
|
5667
|
-
const signature = import_ed25519.ed25519.sign(utf8Bytes(signingInput), issuerPrivateKey);
|
|
5668
|
-
return `${signingInput}.${toBase64Url(signature)}`;
|
|
5669
|
-
}
|
|
5670
5634
|
var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
|
|
5671
|
-
var SEALED_BLOB_VERSION = 1;
|
|
5672
5635
|
var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
|
|
5673
|
-
var KEY_LENGTH = 32;
|
|
5674
|
-
var NONCE_LENGTH = 12;
|
|
5675
|
-
var TAG_LENGTH = 16;
|
|
5676
|
-
var HEADER_LENGTH = 1;
|
|
5677
|
-
function assertKey(key32) {
|
|
5678
|
-
if (key32.length !== KEY_LENGTH) {
|
|
5679
|
-
throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
|
|
5680
|
-
}
|
|
5681
|
-
}
|
|
5682
|
-
async function importAesKey(key32, usage) {
|
|
5683
|
-
assertKey(key32);
|
|
5684
|
-
return globalThis.crypto.subtle.importKey(
|
|
5685
|
-
"raw",
|
|
5686
|
-
key32,
|
|
5687
|
-
"AES-GCM",
|
|
5688
|
-
false,
|
|
5689
|
-
[usage]
|
|
5690
|
-
);
|
|
5691
|
-
}
|
|
5692
|
-
function generateKey() {
|
|
5693
|
-
return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
|
5694
|
-
}
|
|
5695
|
-
async function seal(plaintextBytes, key32) {
|
|
5696
|
-
const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
|
|
5697
|
-
const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
|
|
5698
|
-
blob[0] = SEALED_BLOB_VERSION;
|
|
5699
|
-
blob.set(nonce, HEADER_LENGTH);
|
|
5700
|
-
blob.set(ciphertext, HEADER_LENGTH + nonce.length);
|
|
5701
|
-
return { blob, cid: await computeCid(blob) };
|
|
5702
|
-
}
|
|
5703
|
-
async function open(blob, key32) {
|
|
5704
|
-
if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
|
|
5705
|
-
throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
|
|
5706
|
-
}
|
|
5707
|
-
if (blob[0] !== SEALED_BLOB_VERSION) {
|
|
5708
|
-
throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
|
|
5709
|
-
}
|
|
5710
|
-
const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
|
|
5711
|
-
const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
|
|
5712
|
-
return decryptEnvelope(nonce, ciphertext, key32);
|
|
5713
|
-
}
|
|
5714
|
-
async function encryptEnvelope(plaintextBytes, key32) {
|
|
5715
|
-
const key = await importAesKey(key32, "encrypt");
|
|
5716
|
-
const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
|
5717
|
-
const ciphertext = new Uint8Array(
|
|
5718
|
-
await globalThis.crypto.subtle.encrypt(
|
|
5719
|
-
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
5720
|
-
key,
|
|
5721
|
-
plaintextBytes
|
|
5722
|
-
)
|
|
5723
|
-
);
|
|
5724
|
-
return { nonce, ciphertext };
|
|
5725
|
-
}
|
|
5726
|
-
async function decryptEnvelope(nonce, ciphertext, key32) {
|
|
5727
|
-
if (nonce.length !== NONCE_LENGTH) {
|
|
5728
|
-
throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
|
|
5729
|
-
}
|
|
5730
|
-
const key = await importAesKey(key32, "decrypt");
|
|
5731
|
-
return new Uint8Array(
|
|
5732
|
-
await globalThis.crypto.subtle.decrypt(
|
|
5733
|
-
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
5734
|
-
key,
|
|
5735
|
-
ciphertext
|
|
5736
|
-
)
|
|
5737
|
-
);
|
|
5738
|
-
}
|
|
5739
5636
|
function isPlainRecord(value) {
|
|
5740
5637
|
const proto = Object.getPrototypeOf(value);
|
|
5741
5638
|
return proto === Object.prototype || proto === null;
|
|
@@ -5866,8 +5763,6 @@ function equal(left, right) {
|
|
|
5866
5763
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
5867
5764
|
}
|
|
5868
5765
|
var ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
5869
|
-
var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
|
|
5870
|
-
var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
5871
5766
|
var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
5872
5767
|
var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
5873
5768
|
var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
@@ -5875,22 +5770,6 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
|
5875
5770
|
var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
5876
5771
|
var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
5877
5772
|
var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
5878
|
-
function signingBytes(unsigned) {
|
|
5879
|
-
const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
|
|
5880
|
-
const body = utf8Bytes(canonicalize(unsigned));
|
|
5881
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5882
|
-
bytes3.set(domain);
|
|
5883
|
-
bytes3.set(body, domain.length);
|
|
5884
|
-
return bytes3;
|
|
5885
|
-
}
|
|
5886
|
-
function signingBytesV2(unsigned) {
|
|
5887
|
-
const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
|
|
5888
|
-
const body = utf8Bytes(canonicalize(unsigned));
|
|
5889
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5890
|
-
bytes3.set(domain);
|
|
5891
|
-
bytes3.set(body, domain.length);
|
|
5892
|
-
return bytes3;
|
|
5893
|
-
}
|
|
5894
5773
|
function signingBytesV3(unsigned) {
|
|
5895
5774
|
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
5896
5775
|
const body = utf8Bytes(canonicalize(unsigned));
|
|
@@ -5899,53 +5778,6 @@ function signingBytesV3(unsigned) {
|
|
|
5899
5778
|
bytes3.set(body, domain.length);
|
|
5900
5779
|
return bytes3;
|
|
5901
5780
|
}
|
|
5902
|
-
function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
|
|
5903
|
-
const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
|
|
5904
|
-
const publicKey = import_ed255193.ed25519.getPublicKey(ed25519PrivKey);
|
|
5905
|
-
const signature = import_ed255193.ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
|
|
5906
|
-
return {
|
|
5907
|
-
...unsigned,
|
|
5908
|
-
signature: {
|
|
5909
|
-
signerDid: didKeyFromEd25519PublicKey(publicKey),
|
|
5910
|
-
algorithm: "Ed25519",
|
|
5911
|
-
value: toBase64Url(signature)
|
|
5912
|
-
}
|
|
5913
|
-
};
|
|
5914
|
-
}
|
|
5915
|
-
function verifyEnvelopeSignatureOnly(envelope) {
|
|
5916
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
5917
|
-
const { signature, ...unsigned } = parsed;
|
|
5918
|
-
const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
|
|
5919
|
-
return import_ed255193.ed25519.verify(
|
|
5920
|
-
fromBase64Url(signature.value),
|
|
5921
|
-
signingBytes(unsigned),
|
|
5922
|
-
publicKey,
|
|
5923
|
-
ED25519_VERIFY_OPTS2
|
|
5924
|
-
);
|
|
5925
|
-
}
|
|
5926
|
-
async function verifyEnvelope(envelope, options) {
|
|
5927
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
5928
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid) return false;
|
|
5929
|
-
if (!verifyEnvelopeSignatureOnly(parsed)) return false;
|
|
5930
|
-
if (parsed.authorizationTarget.kind === "policy") {
|
|
5931
|
-
const policyBytes = fromBase64Url(parsed.authorizationTarget.policyBytes);
|
|
5932
|
-
if (await computeCid(policyBytes) !== parsed.authorizationTarget.policyCid) {
|
|
5933
|
-
return false;
|
|
5934
|
-
}
|
|
5935
|
-
}
|
|
5936
|
-
return true;
|
|
5937
|
-
}
|
|
5938
|
-
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
5939
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5940
|
-
const { signature, ...unsigned } = parsed;
|
|
5941
|
-
return import_ed255193.ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
5942
|
-
}
|
|
5943
|
-
async function verifyEnvelopeV2(envelope, options) {
|
|
5944
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5945
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
|
|
5946
|
-
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
5947
|
-
return true;
|
|
5948
|
-
}
|
|
5949
5781
|
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
5950
5782
|
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
5951
5783
|
const { signature, ...unsigned } = parsed;
|
|
@@ -5979,8 +5811,8 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
5979
5811
|
if (hex(sourceDigest) !== parsed.contentSourceDigestHex) return false;
|
|
5980
5812
|
const sortedCapabilities = [...policy.capabilityCeiling].sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
|
|
5981
5813
|
const capabilityCeilingHashHex = hex((0, import_sha256.sha256)(new TextEncoder().encode(`${POLICY_CAPABILITY_V1_DOMAIN}${canonicalize(sortedCapabilities)}`)));
|
|
5982
|
-
const
|
|
5983
|
-
const nativeProjectionHashHex = hex((0, import_sha256.sha256)(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize(
|
|
5814
|
+
const nativeProjection2 = sortedCapabilities.map((capability) => capability.kind === "encryption" ? { service: "tinycloud.encryption", space: capability.resource, path: capability.resource, actions: [capability.action] } : { service: "tinycloud.kv", space: capability.resource.slice(0, capability.resource.indexOf("/kv/")), path: capability.resource.split("/kv/")[1], actions: [...capability.actions], caveat: { type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: capability.resource } }).sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
|
|
5815
|
+
const nativeProjectionHashHex = hex((0, import_sha256.sha256)(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize(nativeProjection2)}`)));
|
|
5984
5816
|
const expectedAttenuation = Object.fromEntries(sortedCapabilities.map((capability) => capability.kind === "encryption" ? [capability.resource, { [capability.action]: [{}] }] : [capability.resource, Object.fromEntries(capability.actions.map((action) => [action, [{ kind: capability.selector, type: "xyz.tinycloud.resource/selector", value: capability.resource }]]))]));
|
|
5985
5817
|
const kv = policy.capabilityCeiling.find((capability) => capability.kind === "kv");
|
|
5986
5818
|
const expectedKvActions = parsed.actions.flatMap((action) => action === "read" ? ["tinycloud.kv/get", "tinycloud.kv/metadata"] : action === "list" ? ["tinycloud.kv/list"] : ["tinycloud.kv/put"]);
|
|
@@ -5991,10 +5823,10 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
5991
5823
|
const binding = parsed.attestedEnforcerBinding;
|
|
5992
5824
|
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
5993
5825
|
const expectedBindingDigestHex = hex((0, import_sha256.sha256)(new TextEncoder().encode(canonicalize({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
5994
|
-
if (binding.
|
|
5826
|
+
if (binding.nodeAudience !== parsed.target.nodeAudience || binding.attestationBindingDigestHex !== expectedBindingDigestHex || bindingSignature.signerDid !== binding.nodeAudience || bindingSignature.suite !== "Ed25519" || Date.parse(binding.issuedAt) > Date.now() || Date.parse(binding.expiresAt) <= Date.now() || Date.parse(binding.expiresAt) < Date.parse(parsed.expiry)) return false;
|
|
5995
5827
|
try {
|
|
5996
|
-
const
|
|
5997
|
-
if (!import_ed255193.ed25519.verify(fromBase64Url(bindingSignature.value),
|
|
5828
|
+
const digest3 = (0, import_sha256.sha256)(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize(unsignedBinding)}`));
|
|
5829
|
+
if (!import_ed255193.ed25519.verify(fromBase64Url(bindingSignature.value), digest3, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
|
|
5998
5830
|
} catch {
|
|
5999
5831
|
return false;
|
|
6000
5832
|
}
|
|
@@ -6033,7 +5865,8 @@ function base32Lower(bytes3) {
|
|
|
6033
5865
|
return output;
|
|
6034
5866
|
}
|
|
6035
5867
|
var KEY_LENGTH2 = 32;
|
|
6036
|
-
var INLINE_PREFIX = "#
|
|
5868
|
+
var INLINE_PREFIX = "#tc2=";
|
|
5869
|
+
var PUBLIC_INLINE_PARAMETER = "tc2";
|
|
6037
5870
|
var MAX_INLINE_BYTES = 256 * 1024;
|
|
6038
5871
|
function assertCanonicalCid(cidString) {
|
|
6039
5872
|
const cid2 = CID.parse(cidString);
|
|
@@ -6042,77 +5875,30 @@ function assertCanonicalCid(cidString) {
|
|
|
6042
5875
|
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
6043
5876
|
}
|
|
6044
5877
|
}
|
|
6045
|
-
function
|
|
6046
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
6047
|
-
throw new TypeError(`key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
6048
|
-
}
|
|
6049
|
-
assertCanonicalCid(ciphertextCid);
|
|
6050
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
6051
|
-
throw new TypeError(`origin must be a canonical https origin, got ${origin}`);
|
|
6052
|
-
}
|
|
6053
|
-
return `${origin}/s/${ciphertextCid}#k=${toBase64Url(key32)}`;
|
|
6054
|
-
}
|
|
6055
|
-
function parseShareUrl(url, options = {}) {
|
|
6056
|
-
const parsed = new URL(url);
|
|
6057
|
-
if (parsed.protocol !== "https:") {
|
|
6058
|
-
throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
|
|
6059
|
-
}
|
|
6060
|
-
if (parsed.username !== "" || parsed.password !== "") {
|
|
6061
|
-
throw new TypeError("share URL must not carry userinfo");
|
|
6062
|
-
}
|
|
6063
|
-
if (parsed.search !== "") {
|
|
6064
|
-
throw new TypeError("share URL must not have a query string");
|
|
6065
|
-
}
|
|
6066
|
-
if (options.expectedOrigin !== void 0) {
|
|
6067
|
-
if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
|
|
6068
|
-
throw new TypeError(
|
|
6069
|
-
`expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
|
|
6070
|
-
);
|
|
6071
|
-
}
|
|
6072
|
-
if (parsed.origin !== options.expectedOrigin) {
|
|
6073
|
-
throw new TypeError(
|
|
6074
|
-
`share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
|
|
6075
|
-
);
|
|
6076
|
-
}
|
|
6077
|
-
}
|
|
6078
|
-
const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
|
|
6079
|
-
if (!match || match[1] === void 0) {
|
|
6080
|
-
throw new TypeError(`not a share URL path: ${parsed.pathname}`);
|
|
6081
|
-
}
|
|
6082
|
-
const ciphertextCid = match[1];
|
|
6083
|
-
assertCanonicalCid(ciphertextCid);
|
|
6084
|
-
if (!parsed.hash.startsWith("#k=")) {
|
|
6085
|
-
throw new TypeError("share URL is missing the #k= key fragment");
|
|
6086
|
-
}
|
|
6087
|
-
const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
|
|
6088
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
6089
|
-
throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
6090
|
-
}
|
|
6091
|
-
return { ciphertextCid, key32 };
|
|
6092
|
-
}
|
|
6093
|
-
async function encodeInlineShareUrl(parts) {
|
|
5878
|
+
async function encodePublicInlineShareUrl(parts) {
|
|
6094
5879
|
if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
|
|
6095
|
-
if (parts.
|
|
6096
|
-
|
|
6097
|
-
const
|
|
6098
|
-
const payload = canonicalize({
|
|
5880
|
+
if (parts.plaintext.byteLength === 0 || parts.plaintext.byteLength > MAX_INLINE_BYTES) throw new RangeError("public inline envelope is outside the allowed size");
|
|
5881
|
+
const ciphertextCid = await computeCid(parts.plaintext);
|
|
5882
|
+
const payloadBytes = new TextEncoder().encode(canonicalize({
|
|
6099
5883
|
v: 2,
|
|
6100
|
-
c: toBase64Url(parts.
|
|
6101
|
-
cid: ciphertextCid
|
|
6102
|
-
|
|
6103
|
-
});
|
|
6104
|
-
const payloadBytes = new TextEncoder().encode(payload);
|
|
5884
|
+
c: toBase64Url(parts.plaintext),
|
|
5885
|
+
cid: ciphertextCid
|
|
5886
|
+
}));
|
|
6105
5887
|
if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
|
|
6106
|
-
return `${parts.origin}/
|
|
5888
|
+
return `${parts.origin}/viewer?${PUBLIC_INLINE_PARAMETER}=${toBase64Url(payloadBytes)}`;
|
|
6107
5889
|
}
|
|
6108
5890
|
function parseInlineShareUrl(url, options = {}) {
|
|
6109
5891
|
const parsed = new URL(url);
|
|
6110
|
-
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== ""
|
|
5892
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("inline share URL must be canonical HTTPS without userinfo");
|
|
6111
5893
|
if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
|
|
6112
|
-
if (parsed.pathname !== "/
|
|
5894
|
+
if (parsed.pathname !== "/viewer") throw new TypeError("not a TinyCloud policy share URL");
|
|
5895
|
+
const secretPayload = parsed.search === "" && parsed.hash.startsWith(INLINE_PREFIX) ? parsed.hash.slice(INLINE_PREFIX.length) : void 0;
|
|
5896
|
+
const publicPayload = parsed.hash === "" && parsed.searchParams.size === 1 ? parsed.searchParams.get(PUBLIC_INLINE_PARAMETER) ?? void 0 : void 0;
|
|
5897
|
+
if (secretPayload === void 0 && publicPayload === void 0) throw new TypeError("not a canonical TinyCloud policy share URL");
|
|
5898
|
+
if (publicPayload !== void 0 && parsed.search !== `?${PUBLIC_INLINE_PARAMETER}=${publicPayload}`) throw new TypeError("public inline URL is not canonical");
|
|
6113
5899
|
let payloadBytes;
|
|
6114
5900
|
try {
|
|
6115
|
-
payloadBytes = fromBase64Url(
|
|
5901
|
+
payloadBytes = fromBase64Url(secretPayload ?? publicPayload);
|
|
6116
5902
|
} catch {
|
|
6117
5903
|
throw new TypeError("inline payload is not canonical base64url");
|
|
6118
5904
|
}
|
|
@@ -6125,37 +5911,32 @@ function parseInlineShareUrl(url, options = {}) {
|
|
|
6125
5911
|
}
|
|
6126
5912
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload must be an object");
|
|
6127
5913
|
if (canonicalize(value) !== new TextDecoder("utf-8").decode(payloadBytes)) throw new TypeError("inline payload is not canonical JSON");
|
|
6128
|
-
const
|
|
6129
|
-
const keys = Object.keys(
|
|
6130
|
-
if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") ||
|
|
5914
|
+
const record = value;
|
|
5915
|
+
const keys = Object.keys(record);
|
|
5916
|
+
if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || publicPayload !== void 0 && (keys.length !== 3 || record.k !== void 0)) throw new TypeError("inline payload has invalid fields");
|
|
6131
5917
|
let ciphertext;
|
|
6132
5918
|
try {
|
|
6133
|
-
ciphertext = fromBase64Url(
|
|
5919
|
+
ciphertext = fromBase64Url(record.c);
|
|
6134
5920
|
} catch {
|
|
6135
5921
|
throw new TypeError("inline ciphertext is not canonical base64url");
|
|
6136
5922
|
}
|
|
6137
5923
|
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
|
|
6138
|
-
assertCanonicalCid(
|
|
6139
|
-
if (
|
|
5924
|
+
assertCanonicalCid(record.cid);
|
|
5925
|
+
if (record.k !== void 0 && typeof record.k !== "string") throw new TypeError("inline key must be base64url");
|
|
6140
5926
|
let key32;
|
|
6141
5927
|
try {
|
|
6142
|
-
key32 =
|
|
5928
|
+
key32 = record.k === void 0 ? void 0 : fromBase64Url(record.k);
|
|
6143
5929
|
} catch {
|
|
6144
5930
|
throw new TypeError("inline key is not canonical base64url");
|
|
6145
5931
|
}
|
|
6146
5932
|
if (key32 !== void 0 && key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
|
|
6147
|
-
return { kind: "inline", ciphertextCid:
|
|
6148
|
-
}
|
|
6149
|
-
function parseCompactOrInlineShareUrl(url, options = {}) {
|
|
6150
|
-
if (new URL(url).pathname === "/s/inline") return parseInlineShareUrl(url, options);
|
|
6151
|
-
return { kind: "compact", ...parseShareUrl(url, options) };
|
|
5933
|
+
return { kind: "inline", ciphertextCid: record.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
|
|
6152
5934
|
}
|
|
6153
5935
|
|
|
6154
5936
|
// src/receive.ts
|
|
6155
5937
|
var SHARE_RESULT_VERSION = 1;
|
|
6156
5938
|
var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
|
|
6157
5939
|
var DEFAULT_MAX_CONTENT_BLOB_BYTES = DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
6158
|
-
var CONTENT_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
6159
5940
|
var ShareReceiveError = class extends Error {
|
|
6160
5941
|
code;
|
|
6161
5942
|
details;
|
|
@@ -6165,7 +5946,6 @@ var ShareReceiveError = class extends Error {
|
|
|
6165
5946
|
this.code = code3;
|
|
6166
5947
|
this.details = details;
|
|
6167
5948
|
}
|
|
6168
|
-
/** Machine output is deliberately code-only; diagnostics belong on stderr. */
|
|
6169
5949
|
toJSON() {
|
|
6170
5950
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: this.code } };
|
|
6171
5951
|
}
|
|
@@ -6174,338 +5954,79 @@ function toShareErrorInfo(error) {
|
|
|
6174
5954
|
if (error instanceof ShareReceiveError) return error.toJSON();
|
|
6175
5955
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: "fetch-failed" } };
|
|
6176
5956
|
}
|
|
6177
|
-
function boundedBytes(value, limit, code3) {
|
|
6178
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("fetch-failed", "share byte limit is invalid");
|
|
6179
|
-
if (!(value instanceof Uint8Array)) throw new ShareReceiveError("fetch-failed", "registry returned invalid bytes");
|
|
6180
|
-
if (value.byteLength > limit) throw new ShareReceiveError(code3, "share blob exceeds the configured byte limit");
|
|
6181
|
-
return value;
|
|
6182
|
-
}
|
|
6183
|
-
async function readResponseBytes(response, limit, tooLargeCode) {
|
|
6184
|
-
const contentLength = response.headers.get("content-length");
|
|
6185
|
-
if (contentLength !== null && Number.isSafeInteger(Number(contentLength)) && Number(contentLength) > limit) {
|
|
6186
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
6187
|
-
}
|
|
6188
|
-
if (response.body === null) {
|
|
6189
|
-
return boundedBytes(new Uint8Array(await response.arrayBuffer()), limit, tooLargeCode);
|
|
6190
|
-
}
|
|
6191
|
-
const reader = response.body.getReader();
|
|
6192
|
-
const chunks = [];
|
|
6193
|
-
let total = 0;
|
|
6194
|
-
try {
|
|
6195
|
-
for (; ; ) {
|
|
6196
|
-
const part = await reader.read();
|
|
6197
|
-
if (part.done) break;
|
|
6198
|
-
total += part.value.byteLength;
|
|
6199
|
-
if (total > limit) {
|
|
6200
|
-
await reader.cancel();
|
|
6201
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
6202
|
-
}
|
|
6203
|
-
chunks.push(part.value);
|
|
6204
|
-
}
|
|
6205
|
-
} finally {
|
|
6206
|
-
reader.releaseLock();
|
|
6207
|
-
}
|
|
6208
|
-
const bytes3 = new Uint8Array(total);
|
|
6209
|
-
let offset = 0;
|
|
6210
|
-
for (const chunk of chunks) {
|
|
6211
|
-
bytes3.set(chunk, offset);
|
|
6212
|
-
offset += chunk.byteLength;
|
|
6213
|
-
}
|
|
6214
|
-
return bytes3;
|
|
6215
|
-
}
|
|
6216
|
-
function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
|
|
6217
|
-
if (options.fetchBlob !== void 0) return async (input) => {
|
|
6218
|
-
try {
|
|
6219
|
-
options.signal?.throwIfAborted();
|
|
6220
|
-
const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
6221
|
-
options.signal?.throwIfAborted();
|
|
6222
|
-
return boundedBytes(bytes3, limit, tooLargeCode);
|
|
6223
|
-
} catch (error) {
|
|
6224
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6225
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
6226
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6227
|
-
}
|
|
6228
|
-
};
|
|
6229
|
-
if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "a registry fetch adapter is required");
|
|
6230
|
-
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
6231
|
-
const base3 = options.registryBaseUrl.replace(/\/+$/, "");
|
|
6232
|
-
return async ({ cid: cid2 }) => {
|
|
6233
|
-
try {
|
|
6234
|
-
options.signal?.throwIfAborted();
|
|
6235
|
-
const response = await fetchFn(`${base3}/ipfs/${cid2}?format=raw`, {
|
|
6236
|
-
headers: { accept: "application/vnd.ipld.raw" },
|
|
6237
|
-
redirect: "error",
|
|
6238
|
-
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
6239
|
-
});
|
|
6240
|
-
options.signal?.throwIfAborted();
|
|
6241
|
-
if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
|
|
6242
|
-
const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
|
|
6243
|
-
options.signal?.throwIfAborted();
|
|
6244
|
-
return bytes3;
|
|
6245
|
-
} catch (error) {
|
|
6246
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6247
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
6248
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6249
|
-
}
|
|
6250
|
-
};
|
|
6251
|
-
}
|
|
6252
5957
|
function metadataFor(envelope, origin) {
|
|
6253
|
-
|
|
6254
|
-
const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
6255
|
-
return {
|
|
6256
|
-
protocol: "tinycloud-share",
|
|
6257
|
-
version: 1,
|
|
6258
|
-
shareId: envelope.shareId,
|
|
6259
|
-
origin,
|
|
6260
|
-
target: { ...envelope.target, kind: targetKind3 },
|
|
6261
|
-
resource: envelope.resource,
|
|
6262
|
-
actions: envelope.actions,
|
|
6263
|
-
expiresAt: envelope.expiry,
|
|
6264
|
-
display: {
|
|
6265
|
-
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
6266
|
-
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
6267
|
-
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6268
|
-
},
|
|
6269
|
-
...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
|
|
6270
|
-
};
|
|
6271
|
-
}
|
|
5958
|
+
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
|
|
6272
5959
|
return {
|
|
6273
5960
|
protocol: "tinycloud-share",
|
|
6274
5961
|
version: 1,
|
|
6275
5962
|
shareId: envelope.shareId,
|
|
6276
5963
|
origin,
|
|
6277
|
-
target: { ...envelope.target, kind
|
|
6278
|
-
resource: envelope.
|
|
6279
|
-
actions: [
|
|
5964
|
+
target: { ...envelope.target, kind },
|
|
5965
|
+
resource: { ...envelope.resource },
|
|
5966
|
+
actions: [...envelope.actions],
|
|
6280
5967
|
expiresAt: envelope.expiry,
|
|
6281
5968
|
display: {
|
|
6282
5969
|
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
6283
5970
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
6284
5971
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6285
|
-
}
|
|
6286
|
-
...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
|
|
5972
|
+
}
|
|
6287
5973
|
};
|
|
6288
5974
|
}
|
|
6289
|
-
function
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
const value = JSON.parse(text);
|
|
6293
|
-
if (canonicalize(value) !== text) throw new Error("share envelope is not canonical JSON");
|
|
6294
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
6295
|
-
const version = value.version;
|
|
6296
|
-
if (version === 3) return shareEnvelopeV3Schema.parse(value);
|
|
6297
|
-
if (version === 2) return shareEnvelopeV2Schema.parse(value);
|
|
6298
|
-
}
|
|
6299
|
-
return shareEnvelopeSchema.parse(value);
|
|
6300
|
-
} catch {
|
|
6301
|
-
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
6302
|
-
}
|
|
6303
|
-
}
|
|
6304
|
-
async function resolveShareEnvelope(link, options = {}) {
|
|
5975
|
+
async function resolvePolicyShare(link, options) {
|
|
5976
|
+
options.signal?.throwIfAborted();
|
|
5977
|
+
let url;
|
|
6305
5978
|
let parsed;
|
|
6306
5979
|
try {
|
|
6307
|
-
|
|
5980
|
+
url = new URL(link);
|
|
5981
|
+
if (url.pathname !== "/viewer" || url.hash !== "" || url.searchParams.size !== 1 || !url.searchParams.has("tc2")) throw new Error("not public Policy/v3");
|
|
5982
|
+
parsed = parseInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
|
|
5983
|
+
if (parsed.key32 !== void 0) throw new Error("secret inline transport is retired");
|
|
6308
5984
|
} catch {
|
|
6309
5985
|
throw new ShareReceiveError("invalid-link", "share link format is invalid");
|
|
6310
5986
|
}
|
|
5987
|
+
const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
5988
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "public policy envelope exceeds the configured byte limit");
|
|
5989
|
+
if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "public envelope bytes do not match the link CID");
|
|
5990
|
+
options.signal?.throwIfAborted();
|
|
5991
|
+
let envelope;
|
|
6311
5992
|
try {
|
|
6312
|
-
|
|
6313
|
-
const
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
let plaintext;
|
|
6317
|
-
try {
|
|
6318
|
-
plaintext = parsed.key32 === void 0 ? sealed : await open(sealed, parsed.key32);
|
|
6319
|
-
} catch {
|
|
6320
|
-
throw new ShareReceiveError("decrypt-failed", "share envelope could not be opened");
|
|
6321
|
-
}
|
|
6322
|
-
return { envelope: parseEnvelope(plaintext), origin: url.origin, cid: parsed.ciphertextCid, kind: parsed.kind };
|
|
6323
|
-
} finally {
|
|
6324
|
-
parsed.key32?.fill(0);
|
|
6325
|
-
}
|
|
6326
|
-
}
|
|
6327
|
-
function actionName(value) {
|
|
6328
|
-
return value === "read" ? "tinycloud.kv/get" : value === "list" ? "tinycloud.kv/list" : value === "edit" ? "tinycloud.kv/put" : value;
|
|
6329
|
-
}
|
|
6330
|
-
function resourceContains(outer, inner) {
|
|
6331
|
-
if (outer.kind === "exact") return inner.kind === "exact" && outer.path === inner.path;
|
|
6332
|
-
const prefix = outer.path.endsWith("/") ? outer.path : `${outer.path}/`;
|
|
6333
|
-
return inner.path === outer.path || inner.path.startsWith(prefix);
|
|
6334
|
-
}
|
|
6335
|
-
function policyEvidenceMatches(envelope, evidence) {
|
|
6336
|
-
const registeredActions = new Set(evidence.actions.map(actionName));
|
|
6337
|
-
return evidence.policyCid === (envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "") && evidence.signerDid === envelope.signature.signerDid && evidence.registrationCid.length > 0 && evidence.shareId === envelope.shareId && canonicalize(evidence.recipientMatcher) === canonicalize(envelope.recipientMatcher) && canonicalize(evidence.target) === canonicalize(envelope.target) && resourceContains(evidence.resource, envelope.resource) && envelope.actions.every((action) => registeredActions.has(actionName(action))) && canonicalize(evidence.contentSource) === canonicalize(envelope.contentSource) && evidence.contentSourceDigest === envelope.contentSourceDigest && evidence.delegationCid === envelope.delegationCid && evidence.authorityMaterialHandle === envelope.authorityMaterialHandle && evidence.authorityMaterialDigest === envelope.authorityMaterialDigest && evidence.expiresAt === envelope.expiry && Number.isFinite(Date.parse(evidence.expiresAt)) && Date.parse(evidence.expiresAt) === Date.parse(envelope.expiry);
|
|
6338
|
-
}
|
|
6339
|
-
async function verifyV2Envelope(envelope, linkOrigin, options) {
|
|
6340
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
6341
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
6342
|
-
}
|
|
6343
|
-
try {
|
|
6344
|
-
if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
5993
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(parsed.ciphertext);
|
|
5994
|
+
const value = JSON.parse(encoded);
|
|
5995
|
+
if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
|
|
5996
|
+
envelope = shareEnvelopeV3Schema.parse(value);
|
|
6345
5997
|
} catch {
|
|
6346
|
-
throw new ShareReceiveError("
|
|
6347
|
-
}
|
|
6348
|
-
const expiry = Date.parse(envelope.expiry);
|
|
6349
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6350
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6351
|
-
if (envelope.authorizationTarget.kind === "policy") {
|
|
6352
|
-
if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "an external policy authority is required");
|
|
6353
|
-
let evidence;
|
|
6354
|
-
try {
|
|
6355
|
-
evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
|
|
6356
|
-
} catch {
|
|
6357
|
-
evidence = void 0;
|
|
6358
|
-
}
|
|
6359
|
-
if (evidence === void 0 || !policyEvidenceMatches(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "share policy authority evidence is not bound to the envelope");
|
|
6360
|
-
if (options.trustedSignerDid !== void 0 && (evidence.signerDid !== options.trustedSignerDid || envelope.signature.signerDid !== options.trustedSignerDid)) {
|
|
6361
|
-
throw new ShareReceiveError("signature-invalid", "share signer is not trusted");
|
|
6362
|
-
}
|
|
6363
|
-
} else if (envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
|
|
6364
|
-
throw new ShareReceiveError("capability-invalid", "recipient authorization does not match the signed recipient");
|
|
6365
|
-
} else {
|
|
6366
|
-
if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
|
|
6367
|
-
}
|
|
6368
|
-
}
|
|
6369
|
-
async function verifyV3Envelope(envelope, linkOrigin, options) {
|
|
6370
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
6371
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
5998
|
+
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
6372
5999
|
}
|
|
6373
6000
|
try {
|
|
6374
6001
|
if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
|
|
6375
6002
|
} catch {
|
|
6376
6003
|
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
6377
6004
|
}
|
|
6005
|
+
options.signal?.throwIfAborted();
|
|
6378
6006
|
const expiry = Date.parse(envelope.expiry);
|
|
6379
6007
|
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6380
6008
|
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
|
|
6384
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
6385
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
6386
|
-
}
|
|
6387
|
-
throw new ShareReceiveError("unsupported-target", "share policy target is not supported", { reason: "policy-target" });
|
|
6388
|
-
}
|
|
6389
|
-
async function verifyBearerEnvelope(envelope, linkOrigin, options = {}) {
|
|
6390
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
6391
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
6392
|
-
}
|
|
6393
|
-
if (envelope.authorizationTarget.kind !== "bearerKey") throw new ShareReceiveError("unsupported-target", "this receive path only handles bearer shares", { reason: envelope.authorizationTarget.kind === "policy" ? "policy-target" : "recipient-did-target" });
|
|
6394
|
-
if (envelope.target.resource.kind !== "exact") throw new ShareReceiveError("unsupported-target", "this receive path only handles exact resources", { reason: "prefix-resource" });
|
|
6395
|
-
const expiry = Date.parse(envelope.expiry);
|
|
6396
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6397
|
-
try {
|
|
6398
|
-
if (!await verifyEnvelope(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
6399
|
-
} catch {
|
|
6400
|
-
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
6401
|
-
}
|
|
6402
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6403
|
-
const capability = checkBearerDelegation(envelope, options.now === void 0 ? {} : { now: options.now });
|
|
6404
|
-
if (!capability.ok) throw new ShareReceiveError("capability-invalid", "share delegation does not authorize the target");
|
|
6405
|
-
}
|
|
6406
|
-
async function verifyResolved(envelope, linkOrigin, options) {
|
|
6407
|
-
await verifyBearerEnvelope(envelope, linkOrigin, options);
|
|
6009
|
+
options.onResolvedAddressedEnvelope?.(envelope, parsed.ciphertextCid);
|
|
6010
|
+
return { envelope, origin: url.origin, cid: parsed.ciphertextCid };
|
|
6408
6011
|
}
|
|
6409
6012
|
async function inspectShare(link, options = {}) {
|
|
6410
|
-
const resolved = await
|
|
6411
|
-
|
|
6412
|
-
else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
6413
|
-
else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6414
|
-
else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6415
|
-
if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6416
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
|
|
6013
|
+
const resolved = await resolvePolicyShare(link, options);
|
|
6014
|
+
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
|
|
6417
6015
|
}
|
|
6418
6016
|
async function receiveShare(link, options = {}) {
|
|
6419
|
-
const resolved = await
|
|
6420
|
-
|
|
6421
|
-
await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6422
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6423
|
-
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6424
|
-
}
|
|
6425
|
-
if (resolved.envelope.version === 2) {
|
|
6426
|
-
await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6427
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6428
|
-
const method = resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
|
|
6429
|
-
if (options.authorization === void 0) return { state: "authorization-required", method };
|
|
6430
|
-
const result = options.authorizationResumeToken === void 0 ? await options.authorization.begin({ envelope: resolved.envelope, method }) : await options.authorization.resume({ envelope: resolved.envelope, method, resumeToken: options.authorizationResumeToken, ...options.authorizationProof === void 0 ? {} : { proof: options.authorizationProof } });
|
|
6431
|
-
if (result.state === "authorization-required") return result;
|
|
6432
|
-
if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "share authorization was denied");
|
|
6433
|
-
if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
|
|
6434
|
-
throw new ShareReceiveError("content-integrity-failed", "share authorization returned an unsigned content result");
|
|
6435
|
-
}
|
|
6436
|
-
const authorized = result.value;
|
|
6437
|
-
try {
|
|
6438
|
-
if (!await options.authorization.verifyResult({ envelope: resolved.envelope, value: authorized, proof: authorized.proof })) throw new Error("node proof");
|
|
6439
|
-
} catch {
|
|
6440
|
-
throw new ShareReceiveError("content-integrity-failed", "node authorization proof is invalid");
|
|
6441
|
-
}
|
|
6442
|
-
const bytes4 = authorized.bytes.slice();
|
|
6443
|
-
const maxBytes = options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES;
|
|
6444
|
-
if (bytes4.byteLength > maxBytes) throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
6445
|
-
const digestBytes3 = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes4));
|
|
6446
|
-
const bodyDigest = toBase64Url(digestBytes3);
|
|
6447
|
-
if (authorized.bodyDigest !== bodyDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content digest does not match");
|
|
6448
|
-
if (authorized.contentSourceDigest !== resolved.envelope.contentSourceDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content source does not match");
|
|
6449
|
-
const binding = authorized.binding;
|
|
6450
|
-
if (binding.shareId !== resolved.envelope.shareId || binding.delegationCid !== resolved.envelope.delegationCid || binding.authorityMaterialHandle !== resolved.envelope.authorityMaterialHandle || binding.authorityMaterialDigest !== resolved.envelope.authorityMaterialDigest || binding.resource.kind !== resolved.envelope.resource.kind || binding.resource.path !== resolved.envelope.resource.path || binding.action !== void 0 && !resolved.envelope.actions.some((action) => action === binding.action || `tinycloud.kv/${action}` === binding.action)) throw new ShareReceiveError("content-integrity-failed", "authorized content binding does not match");
|
|
6451
|
-
if (resolved.envelope.metadata.byteLength !== void 0 && resolved.envelope.metadata.byteLength !== bytes4.byteLength) {
|
|
6452
|
-
throw new ShareReceiveError("content-integrity-failed", "authorized content length does not match");
|
|
6453
|
-
}
|
|
6454
|
-
let text2;
|
|
6455
|
-
try {
|
|
6456
|
-
text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes4);
|
|
6457
|
-
} catch {
|
|
6458
|
-
}
|
|
6459
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes4, ...text2 === void 0 ? {} : { text: text2 } };
|
|
6460
|
-
}
|
|
6461
|
-
if (resolved.envelope.authorizationTarget.kind === "policy") {
|
|
6462
|
-
await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
|
|
6463
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6464
|
-
return { state: "authorization-required", method: "email-claim" };
|
|
6465
|
-
}
|
|
6466
|
-
await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
6467
|
-
let bytes3 = new Uint8Array();
|
|
6468
|
-
if (resolved.envelope.content !== void 0) {
|
|
6469
|
-
bytes3 = await registryFetcher(
|
|
6470
|
-
options,
|
|
6471
|
-
(options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES) + CONTENT_SEALED_OVERHEAD,
|
|
6472
|
-
"max-bytes-exceeded",
|
|
6473
|
-
"content"
|
|
6474
|
-
)({ origin: resolved.origin, cid: resolved.envelope.content.cid });
|
|
6475
|
-
if (await computeCid(bytes3) !== resolved.envelope.content.cid) throw new ShareReceiveError("content-integrity-failed", "shared content CID does not match");
|
|
6476
|
-
const key = fromBase64Url(resolved.envelope.content.key);
|
|
6477
|
-
try {
|
|
6478
|
-
bytes3 = await open(bytes3, key);
|
|
6479
|
-
} catch {
|
|
6480
|
-
throw new ShareReceiveError("content-integrity-failed", "shared content could not be opened");
|
|
6481
|
-
} finally {
|
|
6482
|
-
key.fill(0);
|
|
6483
|
-
}
|
|
6484
|
-
if (bytes3.byteLength > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) {
|
|
6485
|
-
throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
6486
|
-
}
|
|
6487
|
-
}
|
|
6488
|
-
let text;
|
|
6489
|
-
if (bytes3.byteLength !== 0) {
|
|
6490
|
-
try {
|
|
6491
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
|
|
6492
|
-
} catch {
|
|
6493
|
-
}
|
|
6494
|
-
}
|
|
6495
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes3, ...text === void 0 ? {} : { text } };
|
|
6017
|
+
const resolved = await resolvePolicyShare(link, options);
|
|
6018
|
+
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6496
6019
|
}
|
|
6497
6020
|
|
|
6498
6021
|
// src/publish.ts
|
|
6499
|
-
var import_ed255194 = require("@noble/curves/ed25519");
|
|
6500
6022
|
var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
|
|
6501
|
-
var SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
6502
6023
|
var SHARE_PUBLISH_RESULT_VERSION = 1;
|
|
6503
6024
|
var DEFAULT_SHARE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6504
6025
|
function redactPublishedShare(result) {
|
|
6505
6026
|
return {
|
|
6506
6027
|
protocol: "tinycloud-share",
|
|
6507
6028
|
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
6508
|
-
link: {
|
|
6029
|
+
link: { ...result.link },
|
|
6509
6030
|
metadata: {
|
|
6510
6031
|
protocol: "tinycloud-share",
|
|
6511
6032
|
version: 1,
|
|
@@ -6515,10 +6036,8 @@ function redactPublishedShare(result) {
|
|
|
6515
6036
|
resource: { ...result.metadata.resource },
|
|
6516
6037
|
actions: [...result.metadata.actions],
|
|
6517
6038
|
expiresAt: result.metadata.expiresAt,
|
|
6518
|
-
display: { ...result.metadata.display }
|
|
6519
|
-
|
|
6520
|
-
},
|
|
6521
|
-
registryDeleteAfter: result.registryDeleteAfter
|
|
6039
|
+
display: { ...result.metadata.display }
|
|
6040
|
+
}
|
|
6522
6041
|
};
|
|
6523
6042
|
}
|
|
6524
6043
|
var SharePublishError = class extends Error {
|
|
@@ -6529,237 +6048,6 @@ var SharePublishError = class extends Error {
|
|
|
6529
6048
|
this.code = code3;
|
|
6530
6049
|
}
|
|
6531
6050
|
};
|
|
6532
|
-
function assertSafeFilename(filename) {
|
|
6533
|
-
if (filename.length === 0 || filename === "." || filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(filename)) {
|
|
6534
|
-
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
6535
|
-
}
|
|
6536
|
-
}
|
|
6537
|
-
function assertOrigin(origin, label) {
|
|
6538
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
6539
|
-
throw new SharePublishError("invalid-argument", `${label} must be a canonical HTTPS origin`);
|
|
6540
|
-
}
|
|
6541
|
-
}
|
|
6542
|
-
async function snapshotSource(source, limit) {
|
|
6543
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
6544
|
-
throw new SharePublishError("invalid-argument", "maxBytes must be a positive safe integer");
|
|
6545
|
-
}
|
|
6546
|
-
if (source instanceof Uint8Array) {
|
|
6547
|
-
if (source.byteLength > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
6548
|
-
return source.slice();
|
|
6549
|
-
}
|
|
6550
|
-
const chunks = [];
|
|
6551
|
-
let total = 0;
|
|
6552
|
-
try {
|
|
6553
|
-
for await (const chunk of source) {
|
|
6554
|
-
if (!(chunk instanceof Uint8Array)) throw new SharePublishError("invalid-argument", "share source yielded invalid bytes");
|
|
6555
|
-
total += chunk.byteLength;
|
|
6556
|
-
if (total > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
6557
|
-
chunks.push(chunk);
|
|
6558
|
-
}
|
|
6559
|
-
} catch (error) {
|
|
6560
|
-
if (error instanceof SharePublishError) throw error;
|
|
6561
|
-
throw new SharePublishError("upload-failed", "share source could not be read");
|
|
6562
|
-
}
|
|
6563
|
-
const bytes3 = new Uint8Array(total);
|
|
6564
|
-
let offset = 0;
|
|
6565
|
-
for (const chunk of chunks) {
|
|
6566
|
-
bytes3.set(chunk, offset);
|
|
6567
|
-
offset += chunk.byteLength;
|
|
6568
|
-
}
|
|
6569
|
-
return bytes3;
|
|
6570
|
-
}
|
|
6571
|
-
function assertTarget(target) {
|
|
6572
|
-
if (target.kind !== "bearer") {
|
|
6573
|
-
throw new SharePublishError("unsupported-target", "this release publishes bearer shares only");
|
|
6574
|
-
}
|
|
6575
|
-
}
|
|
6576
|
-
function assertRegistryUrl(value, allowInsecure) {
|
|
6577
|
-
let url;
|
|
6578
|
-
try {
|
|
6579
|
-
url = new URL(value);
|
|
6580
|
-
} catch {
|
|
6581
|
-
throw new SharePublishError("invalid-argument", "registry URL is invalid");
|
|
6582
|
-
}
|
|
6583
|
-
if (url.username || url.password || url.search || url.hash) throw new SharePublishError("invalid-argument", "registry URL must not contain credentials, query, or fragment");
|
|
6584
|
-
if (!allowInsecure && url.protocol !== "https:") throw new SharePublishError("invalid-argument", "production registry uploads require HTTPS");
|
|
6585
|
-
if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost") throw new SharePublishError("invalid-argument", "insecure registry uploads are limited to localhost");
|
|
6586
|
-
return url;
|
|
6587
|
-
}
|
|
6588
|
-
async function defaultUpload(options, input) {
|
|
6589
|
-
if (options.registryBaseUrl === void 0) throw new SharePublishError("upload-auth-required", "an authenticated share registry is required");
|
|
6590
|
-
const base3 = assertRegistryUrl(options.registryBaseUrl, options.allowInsecureRegistry === true).toString().replace(/\/$/, "");
|
|
6591
|
-
const localInsecure = options.allowInsecureRegistry === true && (new URL(base3).hostname === "127.0.0.1" || new URL(base3).hostname === "localhost");
|
|
6592
|
-
if (options.authorizeUpload === void 0 && !localInsecure) {
|
|
6593
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization is required");
|
|
6594
|
-
}
|
|
6595
|
-
let authorization;
|
|
6596
|
-
if (options.authorizeUpload !== void 0) {
|
|
6597
|
-
try {
|
|
6598
|
-
authorization = await options.authorizeUpload(input);
|
|
6599
|
-
} catch {
|
|
6600
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6601
|
-
}
|
|
6602
|
-
}
|
|
6603
|
-
if (authorization !== void 0 && options.authorizationOrigin !== void 0) {
|
|
6604
|
-
let authorizationUrl;
|
|
6605
|
-
try {
|
|
6606
|
-
authorizationUrl = new URL(options.authorizationOrigin);
|
|
6607
|
-
} catch {
|
|
6608
|
-
throw new SharePublishError("invalid-argument", "upload authorization origin is invalid");
|
|
6609
|
-
}
|
|
6610
|
-
if (authorizationUrl.origin !== new URL(base3).origin) throw new SharePublishError("upload-auth-required", "upload authorization is scoped to another origin");
|
|
6611
|
-
}
|
|
6612
|
-
const headers = new Headers({
|
|
6613
|
-
"content-type": "application/vnd.ipld.raw",
|
|
6614
|
-
"if-none-match": "*",
|
|
6615
|
-
"x-delete-after": input.deleteAfter
|
|
6616
|
-
});
|
|
6617
|
-
if (typeof authorization === "string") {
|
|
6618
|
-
headers.set("x-tinycloud-upload-attestation", authorization);
|
|
6619
|
-
try {
|
|
6620
|
-
const attestation = JSON.parse(authorization);
|
|
6621
|
-
if (attestation.retention === void 0) throw new Error("retention");
|
|
6622
|
-
headers.set("x-tinycloud-retention", canonicalize(attestation.retention));
|
|
6623
|
-
} catch {
|
|
6624
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6625
|
-
}
|
|
6626
|
-
} else if (authorization !== void 0) {
|
|
6627
|
-
new Headers(authorization).forEach((value, key) => headers.set(key, value));
|
|
6628
|
-
}
|
|
6629
|
-
let response;
|
|
6630
|
-
try {
|
|
6631
|
-
response = await (options.fetchFn ?? globalThis.fetch)(`${base3}/blobs`, {
|
|
6632
|
-
method: "POST",
|
|
6633
|
-
headers,
|
|
6634
|
-
body: input.blob,
|
|
6635
|
-
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
6636
|
-
redirect: "error",
|
|
6637
|
-
referrerPolicy: "no-referrer"
|
|
6638
|
-
});
|
|
6639
|
-
} catch {
|
|
6640
|
-
throw new SharePublishError("upload-failed", "share registry is unavailable");
|
|
6641
|
-
}
|
|
6642
|
-
if (response.status === 401 || response.status === 403) throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6643
|
-
if (!response.ok) throw new SharePublishError("upload-failed", "share registry rejected the encrypted blob");
|
|
6644
|
-
let body;
|
|
6645
|
-
try {
|
|
6646
|
-
body = await response.json();
|
|
6647
|
-
} catch {
|
|
6648
|
-
throw new SharePublishError("upload-failed", "share registry returned an invalid response");
|
|
6649
|
-
}
|
|
6650
|
-
const record2 = typeof body === "object" && body !== null ? body : {};
|
|
6651
|
-
if (record2.cid !== input.cid || typeof record2.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share registry returned an unexpected CID or retention value");
|
|
6652
|
-
return { cid: input.cid, deleteAfter: record2.deleteAfter };
|
|
6653
|
-
}
|
|
6654
|
-
async function uploadShareBlob(options, input) {
|
|
6655
|
-
const result = await (options.uploadBlob ?? ((value) => defaultUpload(options, value)))(input);
|
|
6656
|
-
if (result.cid !== input.cid || typeof result.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share uploader returned an invalid result");
|
|
6657
|
-
return result;
|
|
6658
|
-
}
|
|
6659
|
-
async function publishShare(options) {
|
|
6660
|
-
const target = options.target ?? { kind: "bearer" };
|
|
6661
|
-
assertTarget(target);
|
|
6662
|
-
assertSafeFilename(options.filename);
|
|
6663
|
-
assertOrigin(options.origin, "share origin");
|
|
6664
|
-
if (options.mediaType !== void 0 && options.mediaType.length === 0) throw new SharePublishError("invalid-argument", "mediaType must not be empty");
|
|
6665
|
-
const maxBytes = options.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
6666
|
-
if (maxBytes > SHARE_CONTENT_LIMIT) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the maximum 100 MiB limit");
|
|
6667
|
-
const content = await snapshotSource(options.source, maxBytes);
|
|
6668
|
-
if (content.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
6669
|
-
if (options.allowBinary !== true) {
|
|
6670
|
-
try {
|
|
6671
|
-
new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
6672
|
-
} catch {
|
|
6673
|
-
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
6674
|
-
}
|
|
6675
|
-
}
|
|
6676
|
-
const nowMs = options.now?.() ?? Date.now();
|
|
6677
|
-
const expiresAt = options.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
|
|
6678
|
-
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
6679
|
-
const expiry = expiresAt.toISOString();
|
|
6680
|
-
const nodeAudience = options.nodeAudience ?? "did:web:node.tinycloud.xyz";
|
|
6681
|
-
const spaceId = options.spaceId ?? "bearer";
|
|
6682
|
-
const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
|
|
6683
|
-
const path = `shares/${shareId}/${options.filename}`;
|
|
6684
|
-
const sessionPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
|
|
6685
|
-
const senderPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
|
|
6686
|
-
let contentKey;
|
|
6687
|
-
let envelopeKey;
|
|
6688
|
-
try {
|
|
6689
|
-
const sessionPublicKey = import_ed255194.ed25519.getPublicKey(sessionPrivateKey);
|
|
6690
|
-
const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
|
|
6691
|
-
const delegation = mintBearerDelegation({
|
|
6692
|
-
issuerPrivateKey: senderPrivateKey,
|
|
6693
|
-
audienceDid: sessionDid,
|
|
6694
|
-
resourceUri: bearerResourceUri(options.origin, spaceId, path),
|
|
6695
|
-
expiresAtSeconds: Math.ceil(expiresAt.getTime() / 1e3)
|
|
6696
|
-
});
|
|
6697
|
-
contentKey = generateKey();
|
|
6698
|
-
const sealedContent = await seal(content, contentKey);
|
|
6699
|
-
const unsigned = {
|
|
6700
|
-
version: 1,
|
|
6701
|
-
shareId,
|
|
6702
|
-
delegation,
|
|
6703
|
-
authorizationTarget: {
|
|
6704
|
-
kind: "bearerKey",
|
|
6705
|
-
sessionJwk: { kty: "OKP", crv: "Ed25519", x: toBase64Url(sessionPublicKey), d: toBase64Url(sessionPrivateKey) }
|
|
6706
|
-
},
|
|
6707
|
-
target: { origin: options.origin, nodeAudience, spaceId, resource: { kind: "exact", path } },
|
|
6708
|
-
display: { filename: options.filename },
|
|
6709
|
-
expiry,
|
|
6710
|
-
content: { cid: sealedContent.cid, key: toBase64Url(contentKey) }
|
|
6711
|
-
};
|
|
6712
|
-
const envelope = signEnvelope(unsigned, senderPrivateKey);
|
|
6713
|
-
envelopeKey = generateKey();
|
|
6714
|
-
const sealedEnvelope = await seal(new TextEncoder().encode(canonicalize(envelope)), envelopeKey);
|
|
6715
|
-
let inlineUrl;
|
|
6716
|
-
if (options.inline === true) {
|
|
6717
|
-
try {
|
|
6718
|
-
inlineUrl = await encodeInlineShareUrl({ origin: options.origin, ciphertext: sealedEnvelope.blob, key32: envelopeKey });
|
|
6719
|
-
} catch (error) {
|
|
6720
|
-
throw new SharePublishError("inline-too-large", error instanceof Error ? error.message : "inline share is too large");
|
|
6721
|
-
}
|
|
6722
|
-
}
|
|
6723
|
-
const contentUpload = await uploadShareBlob(options, { blob: sealedContent.blob, cid: sealedContent.cid, deleteAfter: expiry, contentLength: sealedContent.blob.byteLength });
|
|
6724
|
-
let url;
|
|
6725
|
-
let kind;
|
|
6726
|
-
let retention = contentUpload.deleteAfter;
|
|
6727
|
-
if (options.inline === true) {
|
|
6728
|
-
if (inlineUrl === void 0) throw new SharePublishError("inline-too-large", "inline share is too large");
|
|
6729
|
-
url = inlineUrl;
|
|
6730
|
-
kind = "inline";
|
|
6731
|
-
} else {
|
|
6732
|
-
const envelopeUpload = await uploadShareBlob(options, { blob: sealedEnvelope.blob, cid: sealedEnvelope.cid, deleteAfter: expiry, contentLength: sealedEnvelope.blob.byteLength });
|
|
6733
|
-
url = encodeShareUrl({ origin: options.origin, ciphertextCid: envelopeUpload.cid, key32: envelopeKey });
|
|
6734
|
-
kind = "compact";
|
|
6735
|
-
retention = envelopeUpload.deleteAfter;
|
|
6736
|
-
}
|
|
6737
|
-
const metadata = {
|
|
6738
|
-
protocol: "tinycloud-share",
|
|
6739
|
-
version: 1,
|
|
6740
|
-
shareId,
|
|
6741
|
-
origin: options.origin,
|
|
6742
|
-
target: { kind: "bearer", origin: options.origin, nodeAudience, spaceId },
|
|
6743
|
-
resource: { kind: "exact", path },
|
|
6744
|
-
actions: ["read"],
|
|
6745
|
-
expiresAt: expiry,
|
|
6746
|
-
display: { filename: options.filename },
|
|
6747
|
-
content: { cid: sealedContent.cid }
|
|
6748
|
-
};
|
|
6749
|
-
const result = { protocol: "tinycloud-share", version: SHARE_PUBLISH_RESULT_VERSION, link: { kind, cid: sealedEnvelope.cid }, metadata, registryDeleteAfter: retention };
|
|
6750
|
-
Object.defineProperty(result, "toJSON", {
|
|
6751
|
-
enumerable: false,
|
|
6752
|
-
value: () => redactPublishedShare(result)
|
|
6753
|
-
});
|
|
6754
|
-
Object.defineProperty(result, "url", { enumerable: false, value: url });
|
|
6755
|
-
return result;
|
|
6756
|
-
} finally {
|
|
6757
|
-
sessionPrivateKey.fill(0);
|
|
6758
|
-
senderPrivateKey.fill(0);
|
|
6759
|
-
contentKey?.fill(0);
|
|
6760
|
-
envelopeKey?.fill(0);
|
|
6761
|
-
}
|
|
6762
|
-
}
|
|
6763
6051
|
|
|
6764
6052
|
// src/addressed.ts
|
|
6765
6053
|
async function parseAddressedEnvelope(value) {
|
|
@@ -6777,10 +6065,31 @@ async function parseAddressedEnvelope(value) {
|
|
|
6777
6065
|
return { envelope, policy: decoded, policyCid: envelope.authorizationTarget.policyCid };
|
|
6778
6066
|
}
|
|
6779
6067
|
|
|
6780
|
-
// src/
|
|
6781
|
-
var import_ed255195 = require("@noble/curves/ed25519");
|
|
6068
|
+
// src/addressed-publish.ts
|
|
6782
6069
|
var import_sha2562 = require("@noble/hashes/sha256");
|
|
6783
6070
|
|
|
6071
|
+
// src/authorization.ts
|
|
6072
|
+
async function authorizeShare(input) {
|
|
6073
|
+
if (input.resumeToken !== void 0) {
|
|
6074
|
+
if (input.resumeToken.length < 16 || input.resumeToken.length > 512) {
|
|
6075
|
+
return { state: "denied", reason: "rejected" };
|
|
6076
|
+
}
|
|
6077
|
+
return input.adapter.resume({
|
|
6078
|
+
envelope: input.envelope,
|
|
6079
|
+
method: input.method,
|
|
6080
|
+
resumeToken: input.resumeToken,
|
|
6081
|
+
...input.proof === void 0 ? {} : { proof: input.proof }
|
|
6082
|
+
});
|
|
6083
|
+
}
|
|
6084
|
+
return input.adapter.begin({ envelope: input.envelope, method: input.method });
|
|
6085
|
+
}
|
|
6086
|
+
function authorizationMethodForTarget(target) {
|
|
6087
|
+
if (target.kind === "recipientDid") return "openkey-device";
|
|
6088
|
+
if (target.kind === "email") return "email-claim";
|
|
6089
|
+
if (target.kind === "emailDomain") return "email-claim";
|
|
6090
|
+
return void 0;
|
|
6091
|
+
}
|
|
6092
|
+
|
|
6784
6093
|
// ../../node_modules/multiformats/dist/src/bytes.js
|
|
6785
6094
|
var empty2 = new Uint8Array(0);
|
|
6786
6095
|
function equals3(aa, bb) {
|
|
@@ -7122,8 +6431,401 @@ function rfc46482({ name, prefix, bitsPerChar, alphabet }) {
|
|
|
7122
6431
|
});
|
|
7123
6432
|
}
|
|
7124
6433
|
|
|
7125
|
-
// ../../node_modules/multiformats/dist/src/bases/
|
|
7126
|
-
var
|
|
6434
|
+
// ../../node_modules/multiformats/dist/src/bases/base58.js
|
|
6435
|
+
var base58btc2 = baseX2({
|
|
6436
|
+
name: "base58btc",
|
|
6437
|
+
prefix: "z",
|
|
6438
|
+
alphabet: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
6439
|
+
});
|
|
6440
|
+
var base58flickr2 = baseX2({
|
|
6441
|
+
name: "base58flickr",
|
|
6442
|
+
prefix: "Z",
|
|
6443
|
+
alphabet: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
6444
|
+
});
|
|
6445
|
+
|
|
6446
|
+
// src/targets.ts
|
|
6447
|
+
function validEmail(value) {
|
|
6448
|
+
return /^[^@\s]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(value);
|
|
6449
|
+
}
|
|
6450
|
+
function validDomain(value) {
|
|
6451
|
+
return /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))+$/i.test(value);
|
|
6452
|
+
}
|
|
6453
|
+
function validRecipientDid(value) {
|
|
6454
|
+
if (value.length === 0 || value.length > 2048 || /[\u0000-\u0020\u007f]/.test(value)) return false;
|
|
6455
|
+
const parts = value.split(":");
|
|
6456
|
+
if (parts.length < 3 || parts[0] !== "did" || !/^[a-z0-9]+$/.test(parts[1] ?? "")) return false;
|
|
6457
|
+
const identifier = parts.slice(2);
|
|
6458
|
+
if (identifier.some((part) => part.length === 0)) return false;
|
|
6459
|
+
if (parts[1] === "web") {
|
|
6460
|
+
const host = identifier[0] ?? "";
|
|
6461
|
+
if (host.length > 253 || host.split(".").some((label) => !label || label.length > 63 || !/^[A-Za-z0-9-]+$/.test(label) || label.startsWith("-") || label.endsWith("-"))) return false;
|
|
6462
|
+
return identifier.slice(1).every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
6463
|
+
}
|
|
6464
|
+
if (parts[1] === "pkh") return identifier.length >= 3 && identifier.every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
6465
|
+
if (parts[1] === "key") {
|
|
6466
|
+
try {
|
|
6467
|
+
const bytes3 = base58btc2.decode(identifier.join(":"));
|
|
6468
|
+
return bytes3.length === 34 && bytes3[0] === 237 && bytes3[1] === 1;
|
|
6469
|
+
} catch {
|
|
6470
|
+
return false;
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
return false;
|
|
6474
|
+
}
|
|
6475
|
+
function normalizeShareTarget(target) {
|
|
6476
|
+
if (target.kind === "bearer") return target;
|
|
6477
|
+
if (target.kind === "recipientDid") {
|
|
6478
|
+
if (!validRecipientDid(target.did)) throw new TypeError("recipient DID is invalid");
|
|
6479
|
+
return { kind: target.kind, did: target.did };
|
|
6480
|
+
}
|
|
6481
|
+
if (target.kind === "email") {
|
|
6482
|
+
if (!validEmail(target.address)) throw new TypeError("recipient email is invalid");
|
|
6483
|
+
const at = target.address.lastIndexOf("@");
|
|
6484
|
+
return { kind: target.kind, address: `${target.address.slice(0, at)}@${target.address.slice(at + 1).toLowerCase()}` };
|
|
6485
|
+
}
|
|
6486
|
+
if (!validDomain(target.domain)) throw new TypeError("recipient email domain is invalid");
|
|
6487
|
+
return { kind: target.kind, domain: target.domain.toLowerCase() };
|
|
6488
|
+
}
|
|
6489
|
+
function targetAuthorizationMethod(target) {
|
|
6490
|
+
return authorizationMethodForTarget(target);
|
|
6491
|
+
}
|
|
6492
|
+
async function publishTargetShare(input) {
|
|
6493
|
+
const target = normalizeShareTarget(input.target);
|
|
6494
|
+
if (input.targetAdapter === void 0) {
|
|
6495
|
+
if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
|
|
6496
|
+
return {
|
|
6497
|
+
state: "authorization-required",
|
|
6498
|
+
method: targetAuthorizationMethod(target)
|
|
6499
|
+
};
|
|
6500
|
+
}
|
|
6501
|
+
const source = input.source instanceof Uint8Array ? input.source.slice() : await (async () => {
|
|
6502
|
+
const chunks = [];
|
|
6503
|
+
let size = 0;
|
|
6504
|
+
for await (const chunk of input.source) {
|
|
6505
|
+
if (!(chunk instanceof Uint8Array)) throw new TypeError("share publication source yielded invalid bytes");
|
|
6506
|
+
size += chunk.byteLength;
|
|
6507
|
+
if (size > (input.maxBytes ?? SHARE_CONTENT_LIMIT)) throw new SharePublishError("max-bytes-exceeded", "share publication exceeds maxBytes");
|
|
6508
|
+
chunks.push(chunk.slice());
|
|
6509
|
+
}
|
|
6510
|
+
const bytes3 = new Uint8Array(size);
|
|
6511
|
+
let offset = 0;
|
|
6512
|
+
for (const chunk of chunks) {
|
|
6513
|
+
bytes3.set(chunk, offset);
|
|
6514
|
+
offset += chunk.byteLength;
|
|
6515
|
+
}
|
|
6516
|
+
return bytes3;
|
|
6517
|
+
})();
|
|
6518
|
+
if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
6519
|
+
if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
|
|
6520
|
+
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
6521
|
+
}
|
|
6522
|
+
if (target.kind === "bearer" && input.allowBinary !== true) {
|
|
6523
|
+
try {
|
|
6524
|
+
new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
6525
|
+
} catch {
|
|
6526
|
+
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
|
|
6530
|
+
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
6531
|
+
let totalBytes = 0;
|
|
6532
|
+
for (const file of files) {
|
|
6533
|
+
totalBytes += file.bytes.byteLength;
|
|
6534
|
+
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
6535
|
+
throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6538
|
+
const nowMs = input.now?.() ?? Date.now();
|
|
6539
|
+
const expiresAt = input.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
|
|
6540
|
+
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
6541
|
+
return input.targetAdapter.publish({
|
|
6542
|
+
source,
|
|
6543
|
+
filename: input.filename,
|
|
6544
|
+
...input.files === void 0 ? {} : { files: input.files },
|
|
6545
|
+
...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
|
|
6546
|
+
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
6547
|
+
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
6548
|
+
target,
|
|
6549
|
+
expiresAt,
|
|
6550
|
+
origin: input.origin,
|
|
6551
|
+
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
6552
|
+
});
|
|
6553
|
+
}
|
|
6554
|
+
|
|
6555
|
+
// src/addressed-publish.ts
|
|
6556
|
+
var POLICY_V1_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
6557
|
+
var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
6558
|
+
var POLICY_CAPABILITY_V1_DOMAIN2 = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
6559
|
+
var CONTENT_SOURCE_V1_DOMAIN2 = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
6560
|
+
var NATIVE_PROJECTION_V1_DOMAIN2 = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
6561
|
+
var ENVELOPE_V3_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
6562
|
+
var textEncoder = new TextEncoder();
|
|
6563
|
+
function targetMatcher(target) {
|
|
6564
|
+
if (target.kind === "recipientDid") return { kind: "recipientDid", value: target.did };
|
|
6565
|
+
if (target.kind === "email") return { kind: "exactEmail", value: target.address };
|
|
6566
|
+
return { kind: "emailDomain", value: target.domain };
|
|
6567
|
+
}
|
|
6568
|
+
function targetKind(target) {
|
|
6569
|
+
return target.kind;
|
|
6570
|
+
}
|
|
6571
|
+
function assertSafeInput(input) {
|
|
6572
|
+
if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
|
|
6573
|
+
if (input.filename.length === 0 || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) throw new TypeError("addressed filename is invalid");
|
|
6574
|
+
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
|
|
6575
|
+
if (input.actions.length === 0 || input.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
6576
|
+
if (!Number.isFinite(input.expiresAt.getTime()) || input.expiresAt.getTime() <= Date.now()) throw new TypeError("addressed share expiry must be in the future");
|
|
6577
|
+
if (input.artifact === "html" && (input.resource.kind !== "prefix" || !input.actions.includes("read") || !input.actions.includes("list"))) throw new TypeError("html artifacts require a readable prefix");
|
|
6578
|
+
if (input.contentSource.shareId !== input.shareId || input.contentSource.selector !== input.resource.kind) throw new TypeError("addressed content source is not bound to the share");
|
|
6579
|
+
}
|
|
6580
|
+
function hex2(bytes3) {
|
|
6581
|
+
return [...bytes3].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
6582
|
+
}
|
|
6583
|
+
function digestHex(value, domain) {
|
|
6584
|
+
return hex2((0, import_sha2562.sha256)(textEncoder.encode(`${domain}${canonicalize(value)}`)));
|
|
6585
|
+
}
|
|
6586
|
+
function sortCanonical(values) {
|
|
6587
|
+
return [...values].sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
|
|
6588
|
+
}
|
|
6589
|
+
function base32Lower2(bytes3) {
|
|
6590
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
6591
|
+
let output = "";
|
|
6592
|
+
let buffer = 0;
|
|
6593
|
+
let bits = 0;
|
|
6594
|
+
for (const byte of bytes3) {
|
|
6595
|
+
buffer = buffer << 8 | byte;
|
|
6596
|
+
bits += 8;
|
|
6597
|
+
while (bits >= 5) {
|
|
6598
|
+
output += alphabet[buffer >>> bits - 5 & 31];
|
|
6599
|
+
bits -= 5;
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
|
|
6603
|
+
return output;
|
|
6604
|
+
}
|
|
6605
|
+
function rfc3339Seconds(value) {
|
|
6606
|
+
return new Date(Math.floor(value.getTime() / 1e3) * 1e3).toISOString().replace(".000Z", "Z");
|
|
6607
|
+
}
|
|
6608
|
+
function nativeProjection(capabilities) {
|
|
6609
|
+
return sortCanonical(capabilities.map((capability) => capability.kind === "encryption" ? { service: "tinycloud.encryption", space: capability.resource, path: capability.resource, actions: [capability.action] } : {
|
|
6610
|
+
service: "tinycloud.kv",
|
|
6611
|
+
space: capability.resource.slice(0, capability.resource.indexOf("/kv/")),
|
|
6612
|
+
path: capability.resource.slice(capability.resource.indexOf("/kv/") + 4),
|
|
6613
|
+
actions: [...capability.actions],
|
|
6614
|
+
caveat: { type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: capability.resource }
|
|
6615
|
+
}));
|
|
6616
|
+
}
|
|
6617
|
+
async function createPolicy(options, capabilities) {
|
|
6618
|
+
const fields = {
|
|
6619
|
+
ownerDid: options.authority.ownerDid,
|
|
6620
|
+
createdAt: rfc3339Seconds(/* @__PURE__ */ new Date()),
|
|
6621
|
+
expiresAt: rfc3339Seconds(options.expiresAt),
|
|
6622
|
+
contentSource: options.contentSource,
|
|
6623
|
+
capabilityCeiling: [...capabilities]
|
|
6624
|
+
};
|
|
6625
|
+
const unsigned = options.credentialRequirement === void 0 ? { schema: "xyz.tinycloud.policy/policy/v1", ...fields } : { schema: "xyz.tinycloud.policy/policy/v2", ...fields, credentialRequirement: options.credentialRequirement };
|
|
6626
|
+
const domain = options.credentialRequirement === void 0 ? POLICY_V1_DOMAIN : POLICY_V2_DOMAIN;
|
|
6627
|
+
const policyDigestHex = digestHex(unsigned, domain);
|
|
6628
|
+
const signature = await options.authority.sign((0, import_sha2562.sha256)(textEncoder.encode(`${domain}${canonicalize(unsigned)}`)));
|
|
6629
|
+
if (signature.byteLength !== 64) throw new TypeError("policy signature must be Ed25519");
|
|
6630
|
+
const policy = {
|
|
6631
|
+
...unsigned,
|
|
6632
|
+
policyId: `pol_${base32Lower2(Uint8Array.from(policyDigestHex.match(/../g), (byte) => Number.parseInt(byte, 16)))}`,
|
|
6633
|
+
signature: { suite: "Ed25519", signerDid: options.authority.ownerDid, value: toBase64Url(signature) }
|
|
6634
|
+
};
|
|
6635
|
+
return { policy, policyCid: await computeCid(textEncoder.encode(canonicalize(policy))), policyDigestHex };
|
|
6636
|
+
}
|
|
6637
|
+
function publicationResult(input) {
|
|
6638
|
+
const result = {
|
|
6639
|
+
protocol: "tinycloud-share",
|
|
6640
|
+
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
6641
|
+
url: input.url,
|
|
6642
|
+
link: { kind: "policy", cid: input.envelopeCid },
|
|
6643
|
+
metadata: {
|
|
6644
|
+
protocol: "tinycloud-share",
|
|
6645
|
+
version: 1,
|
|
6646
|
+
shareId: input.options.shareId,
|
|
6647
|
+
origin: input.options.shareOrigin,
|
|
6648
|
+
target: { kind: targetKind(input.options.target), origin: input.options.nodeOrigin, nodeAudience: input.enforcerDid, spaceId: input.options.spaceId },
|
|
6649
|
+
resource: { ...input.options.resource },
|
|
6650
|
+
actions: [...input.options.actions],
|
|
6651
|
+
expiresAt: input.expiry,
|
|
6652
|
+
display: { filename: input.options.filename },
|
|
6653
|
+
recipientMatcher: { ...input.matcher },
|
|
6654
|
+
policyCid: input.policyCid,
|
|
6655
|
+
ownerDelegationCid: input.policyRootCid,
|
|
6656
|
+
enforcementDelegationCid: input.enforcementRootCid,
|
|
6657
|
+
ownerDid: input.options.authority.ownerDid,
|
|
6658
|
+
enforcerDid: input.enforcerDid,
|
|
6659
|
+
envelopeCid: input.envelopeCid,
|
|
6660
|
+
shareCid: input.envelopeCid
|
|
6661
|
+
}
|
|
6662
|
+
};
|
|
6663
|
+
Object.defineProperty(result, "toJSON", { enumerable: false, value: () => redactPublishedShare(result) });
|
|
6664
|
+
Object.defineProperty(result, "url", { enumerable: false, value: input.url });
|
|
6665
|
+
Object.defineProperty(result, "deliveryMaterial", { enumerable: false, value: input.deliveryMaterial });
|
|
6666
|
+
return result;
|
|
6667
|
+
}
|
|
6668
|
+
async function publishAddressedShare(options) {
|
|
6669
|
+
assertSafeInput(options);
|
|
6670
|
+
const target = normalizeShareTarget(options.target);
|
|
6671
|
+
if (target.kind === "bearer") throw new TypeError("addressed target is required");
|
|
6672
|
+
const expiry = rfc3339Seconds(options.expiresAt);
|
|
6673
|
+
const matcher = targetMatcher(target);
|
|
6674
|
+
const capabilities = sortCanonical([
|
|
6675
|
+
{ kind: "kv", resource: options.contentSource.kvResource, selector: options.resource.kind, actions: [...options.policyActions] },
|
|
6676
|
+
{ kind: "encryption", resource: options.contentSource.encryptionNetwork, action: "tinycloud.encryption/decrypt" }
|
|
6677
|
+
]);
|
|
6678
|
+
const created = await createPolicy(options, capabilities);
|
|
6679
|
+
const contentSourceDigestHex = digestHex(options.contentSource, CONTENT_SOURCE_V1_DOMAIN2);
|
|
6680
|
+
const capabilityCeilingHashHex = digestHex(capabilities, POLICY_CAPABILITY_V1_DOMAIN2);
|
|
6681
|
+
const nativeProjectionHashHex = digestHex(nativeProjection(capabilities), NATIVE_PROJECTION_V1_DOMAIN2);
|
|
6682
|
+
const commonRoot = {
|
|
6683
|
+
ownerDid: options.authority.ownerDid,
|
|
6684
|
+
policyId: created.policy.policyId,
|
|
6685
|
+
policyDigestHex: created.policyDigestHex,
|
|
6686
|
+
policyCid: created.policyCid,
|
|
6687
|
+
contentSourceDigestHex,
|
|
6688
|
+
capabilityCeilingHashHex,
|
|
6689
|
+
nativeProjectionHashHex,
|
|
6690
|
+
notBefore: new Date(created.policy.createdAt),
|
|
6691
|
+
expiresAt: new Date(expiry),
|
|
6692
|
+
nodeAudience: options.nodeAudience,
|
|
6693
|
+
capabilities
|
|
6694
|
+
};
|
|
6695
|
+
const policyRootReceipt = await options.authority.createOwnerRoot({ ...commonRoot, role: "policy-authority", audienceDid: `did:tinycloud:policy:${created.policyDigestHex}` });
|
|
6696
|
+
const enforcementRootReceipt = await options.authority.createOwnerRoot({ ...commonRoot, role: "policy-enforcement", audienceDid: options.enforcerDid });
|
|
6697
|
+
const policyRoot = { cid: policyRootReceipt.cid, authorization: policyRootReceipt.delegationHeader.Authorization.replace(/^Bearer\s+/i, ""), role: "policy-authority" };
|
|
6698
|
+
const enforcementRoot = { cid: enforcementRootReceipt.cid, authorization: enforcementRootReceipt.delegationHeader.Authorization.replace(/^Bearer\s+/i, ""), role: "policy-enforcement" };
|
|
6699
|
+
const registration = await options.authority.registerPolicy({
|
|
6700
|
+
policyCid: created.policyCid,
|
|
6701
|
+
policy: created.policy,
|
|
6702
|
+
policyRoot,
|
|
6703
|
+
enforcementRoot,
|
|
6704
|
+
contentSourceDigestHex,
|
|
6705
|
+
nativeProjectionHashHex,
|
|
6706
|
+
rootExpiresAt: expiry,
|
|
6707
|
+
enforcerDid: options.enforcerDid,
|
|
6708
|
+
expectedNodeAudience: options.nodeAudience
|
|
6709
|
+
});
|
|
6710
|
+
if (registration.policyCid !== created.policyCid || registration.policyRootCid !== policyRoot.cid || registration.enforcementRootCid !== enforcementRoot.cid) throw new Error("Policy/v3 registration receipt is not bound to the published roots");
|
|
6711
|
+
const unsigned = {
|
|
6712
|
+
version: 3,
|
|
6713
|
+
shareId: options.shareId,
|
|
6714
|
+
recipientMatcher: matcher,
|
|
6715
|
+
...options.deliveryEmail === void 0 ? {} : { deliveryEmail: options.deliveryEmail },
|
|
6716
|
+
actions: [...options.actions],
|
|
6717
|
+
resource: { ...options.resource },
|
|
6718
|
+
target: { origin: options.nodeOrigin, nodeAudience: registration.attestedEnforcerBinding.enforcerDid, spaceId: options.spaceId },
|
|
6719
|
+
policy: created.policy,
|
|
6720
|
+
policyCid: created.policyCid,
|
|
6721
|
+
policyRoot,
|
|
6722
|
+
enforcementRoot,
|
|
6723
|
+
attestedEnforcerBinding: registration.attestedEnforcerBinding,
|
|
6724
|
+
contentSource: options.contentSource,
|
|
6725
|
+
contentSourceDigestHex,
|
|
6726
|
+
encryptionNetwork: options.contentSource.encryptionNetwork,
|
|
6727
|
+
expiry,
|
|
6728
|
+
display: { filename: options.filename },
|
|
6729
|
+
encrypted: true,
|
|
6730
|
+
metadata: {
|
|
6731
|
+
mediaType: options.mediaType,
|
|
6732
|
+
byteLength: options.byteLength,
|
|
6733
|
+
filename: options.filename,
|
|
6734
|
+
...options.mediaType.startsWith("text/") ? { encoding: "utf-8" } : {},
|
|
6735
|
+
...options.artifact === void 0 ? {} : { artifact: options.artifact }
|
|
6736
|
+
}
|
|
6737
|
+
};
|
|
6738
|
+
unsignedShareEnvelopeV3Schema.parse(unsigned);
|
|
6739
|
+
const envelopeSignature = await options.authority.sign((0, import_sha2562.sha256)(textEncoder.encode(`${ENVELOPE_V3_DOMAIN}${canonicalize(unsigned)}`)));
|
|
6740
|
+
if (envelopeSignature.byteLength !== 64) throw new TypeError("v3 envelope signature must be Ed25519");
|
|
6741
|
+
const envelope = { ...unsigned, signature: { signerDid: options.authority.ownerDid, algorithm: "Ed25519", value: toBase64Url(envelopeSignature) } };
|
|
6742
|
+
shareEnvelopeV3Schema.parse(envelope);
|
|
6743
|
+
const envelopeBytes = textEncoder.encode(canonicalize(envelope));
|
|
6744
|
+
const envelopeCid = await computeCid(envelopeBytes);
|
|
6745
|
+
const url = await encodePublicInlineShareUrl({ origin: options.shareOrigin, plaintext: envelopeBytes });
|
|
6746
|
+
const deliveryMaterial = { envelope, shareCid: envelopeCid };
|
|
6747
|
+
options.onDeliveryMaterial?.(deliveryMaterial);
|
|
6748
|
+
return publicationResult({
|
|
6749
|
+
options,
|
|
6750
|
+
url,
|
|
6751
|
+
envelopeCid,
|
|
6752
|
+
matcher,
|
|
6753
|
+
policyCid: created.policyCid,
|
|
6754
|
+
policyRootCid: policyRoot.cid,
|
|
6755
|
+
enforcementRootCid: enforcementRoot.cid,
|
|
6756
|
+
enforcerDid: registration.attestedEnforcerBinding.enforcerDid,
|
|
6757
|
+
expiry,
|
|
6758
|
+
deliveryMaterial
|
|
6759
|
+
});
|
|
6760
|
+
}
|
|
6761
|
+
|
|
6762
|
+
// src/cache.ts
|
|
6763
|
+
var import_sha2563 = require("@noble/hashes/sha256");
|
|
6764
|
+
function cacheKey(key) {
|
|
6765
|
+
if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
|
|
6766
|
+
return `${key.cid}\0${key.expiresAt}`;
|
|
6767
|
+
}
|
|
6768
|
+
function digest(bytes3) {
|
|
6769
|
+
return (0, import_sha2563.sha256)(bytes3);
|
|
6770
|
+
}
|
|
6771
|
+
function equalBytes(left, right) {
|
|
6772
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
6773
|
+
return left.every((byte, index) => byte === right[index]);
|
|
6774
|
+
}
|
|
6775
|
+
var MemoryShareCache = class {
|
|
6776
|
+
constructor(now = () => /* @__PURE__ */ new Date()) {
|
|
6777
|
+
this.now = now;
|
|
6778
|
+
}
|
|
6779
|
+
entries = /* @__PURE__ */ new Map();
|
|
6780
|
+
async get(key, now = /* @__PURE__ */ new Date()) {
|
|
6781
|
+
const mapKey = cacheKey(key);
|
|
6782
|
+
const entry = this.entries.get(mapKey);
|
|
6783
|
+
if (entry === void 0) return void 0;
|
|
6784
|
+
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
|
|
6785
|
+
this.entries.delete(mapKey);
|
|
6786
|
+
return void 0;
|
|
6787
|
+
}
|
|
6788
|
+
return entry.bytes.slice();
|
|
6789
|
+
}
|
|
6790
|
+
async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
|
|
6791
|
+
const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
|
|
6792
|
+
for (const entry of entries) {
|
|
6793
|
+
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
|
|
6794
|
+
this.entries.delete(cacheKey(entry.key));
|
|
6795
|
+
continue;
|
|
6796
|
+
}
|
|
6797
|
+
return {
|
|
6798
|
+
key: { ...entry.key },
|
|
6799
|
+
bytes: entry.bytes.slice(),
|
|
6800
|
+
digest: entry.digest.slice(),
|
|
6801
|
+
metadata: { ...entry.metadata }
|
|
6802
|
+
};
|
|
6803
|
+
}
|
|
6804
|
+
return void 0;
|
|
6805
|
+
}
|
|
6806
|
+
async set(key, bytes3, metadata) {
|
|
6807
|
+
if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
|
|
6808
|
+
this.entries.set(cacheKey(key), {
|
|
6809
|
+
key: { cid: key.cid, expiresAt: key.expiresAt },
|
|
6810
|
+
bytes: bytes3.slice(),
|
|
6811
|
+
digest: digest(bytes3),
|
|
6812
|
+
metadata: { ...metadata }
|
|
6813
|
+
});
|
|
6814
|
+
}
|
|
6815
|
+
async delete(key) {
|
|
6816
|
+
this.entries.delete(cacheKey(key));
|
|
6817
|
+
}
|
|
6818
|
+
async clear() {
|
|
6819
|
+
this.entries.clear();
|
|
6820
|
+
}
|
|
6821
|
+
};
|
|
6822
|
+
|
|
6823
|
+
// src/owner-policy.ts
|
|
6824
|
+
var import_ed255194 = require("@noble/curves/ed25519");
|
|
6825
|
+
var import_sha2564 = require("@noble/hashes/sha256");
|
|
6826
|
+
|
|
6827
|
+
// ../../node_modules/multiformats/dist/src/bases/base32.js
|
|
6828
|
+
var base322 = rfc46482({
|
|
7127
6829
|
prefix: "b",
|
|
7128
6830
|
name: "base32",
|
|
7129
6831
|
alphabet: "abcdefghijklmnopqrstuvwxyz234567",
|
|
@@ -7190,18 +6892,6 @@ var base36upper2 = baseX2({
|
|
|
7190
6892
|
alphabet: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
7191
6893
|
});
|
|
7192
6894
|
|
|
7193
|
-
// ../../node_modules/multiformats/dist/src/bases/base58.js
|
|
7194
|
-
var base58btc2 = baseX2({
|
|
7195
|
-
name: "base58btc",
|
|
7196
|
-
prefix: "z",
|
|
7197
|
-
alphabet: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
7198
|
-
});
|
|
7199
|
-
var base58flickr2 = baseX2({
|
|
7200
|
-
name: "base58flickr",
|
|
7201
|
-
prefix: "Z",
|
|
7202
|
-
alphabet: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
7203
|
-
});
|
|
7204
|
-
|
|
7205
6895
|
// ../../node_modules/multiformats/dist/src/vendor/varint.js
|
|
7206
6896
|
var encode_12 = encode4;
|
|
7207
6897
|
var MSB2 = 128;
|
|
@@ -7275,25 +6965,25 @@ function encodingLength2(int) {
|
|
|
7275
6965
|
}
|
|
7276
6966
|
|
|
7277
6967
|
// ../../node_modules/multiformats/dist/src/hashes/digest.js
|
|
7278
|
-
function create2(code3,
|
|
7279
|
-
const size =
|
|
6968
|
+
function create2(code3, digest3) {
|
|
6969
|
+
const size = digest3.byteLength;
|
|
7280
6970
|
const sizeOffset = encodingLength2(code3);
|
|
7281
6971
|
const digestOffset = sizeOffset + encodingLength2(size);
|
|
7282
6972
|
const bytes3 = new Uint8Array(digestOffset + size);
|
|
7283
6973
|
encodeTo2(code3, bytes3, 0);
|
|
7284
6974
|
encodeTo2(size, bytes3, sizeOffset);
|
|
7285
|
-
bytes3.set(
|
|
7286
|
-
return new Digest2(code3, size,
|
|
6975
|
+
bytes3.set(digest3, digestOffset);
|
|
6976
|
+
return new Digest2(code3, size, digest3, bytes3);
|
|
7287
6977
|
}
|
|
7288
6978
|
function decode8(multihash) {
|
|
7289
6979
|
const bytes3 = coerce3(multihash);
|
|
7290
6980
|
const [code3, sizeOffset] = decode7(bytes3);
|
|
7291
6981
|
const [size, digestOffset] = decode7(bytes3.subarray(sizeOffset));
|
|
7292
|
-
const
|
|
7293
|
-
if (
|
|
6982
|
+
const digest3 = bytes3.subarray(sizeOffset + digestOffset);
|
|
6983
|
+
if (digest3.byteLength !== size) {
|
|
7294
6984
|
throw new Error("Incorrect length");
|
|
7295
6985
|
}
|
|
7296
|
-
return new Digest2(code3, size,
|
|
6986
|
+
return new Digest2(code3, size, digest3, bytes3);
|
|
7297
6987
|
}
|
|
7298
6988
|
function equals4(a, b) {
|
|
7299
6989
|
if (a === b) {
|
|
@@ -7311,10 +7001,10 @@ var Digest2 = class {
|
|
|
7311
7001
|
/**
|
|
7312
7002
|
* Creates a multihash digest.
|
|
7313
7003
|
*/
|
|
7314
|
-
constructor(code3, size,
|
|
7004
|
+
constructor(code3, size, digest3, bytes3) {
|
|
7315
7005
|
this.code = code3;
|
|
7316
7006
|
this.size = size;
|
|
7317
|
-
this.digest =
|
|
7007
|
+
this.digest = digest3;
|
|
7318
7008
|
this.bytes = bytes3;
|
|
7319
7009
|
}
|
|
7320
7010
|
};
|
|
@@ -7397,8 +7087,8 @@ var CID2 = class _CID2 {
|
|
|
7397
7087
|
toV1() {
|
|
7398
7088
|
switch (this.version) {
|
|
7399
7089
|
case 0: {
|
|
7400
|
-
const { code: code3, digest:
|
|
7401
|
-
const multihash = create2(code3,
|
|
7090
|
+
const { code: code3, digest: digest3 } = this.multihash;
|
|
7091
|
+
const multihash = create2(code3, digest3);
|
|
7402
7092
|
return _CID2.createV1(this.code, multihash);
|
|
7403
7093
|
}
|
|
7404
7094
|
case 1: {
|
|
@@ -7452,8 +7142,8 @@ var CID2 = class _CID2 {
|
|
|
7452
7142
|
return new _CID2(version, code3, multihash, bytes3 ?? encodeCID2(version, code3, multihash.bytes));
|
|
7453
7143
|
} else if (value[cidSymbol2] === true) {
|
|
7454
7144
|
const { version, multihash, code: code3 } = value;
|
|
7455
|
-
const
|
|
7456
|
-
return _CID2.create(version, code3,
|
|
7145
|
+
const digest3 = decode8(multihash);
|
|
7146
|
+
return _CID2.create(version, code3, digest3);
|
|
7457
7147
|
} else {
|
|
7458
7148
|
return null;
|
|
7459
7149
|
}
|
|
@@ -7463,11 +7153,11 @@ var CID2 = class _CID2 {
|
|
|
7463
7153
|
* @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
|
|
7464
7154
|
* @param digest - (Multi)hash of the of the content.
|
|
7465
7155
|
*/
|
|
7466
|
-
static create(version, code3,
|
|
7156
|
+
static create(version, code3, digest3) {
|
|
7467
7157
|
if (typeof code3 !== "number") {
|
|
7468
7158
|
throw new Error("String codecs are no longer supported");
|
|
7469
7159
|
}
|
|
7470
|
-
if (!(
|
|
7160
|
+
if (!(digest3.bytes instanceof Uint8Array)) {
|
|
7471
7161
|
throw new Error("Invalid digest");
|
|
7472
7162
|
}
|
|
7473
7163
|
switch (version) {
|
|
@@ -7475,12 +7165,12 @@ var CID2 = class _CID2 {
|
|
|
7475
7165
|
if (code3 !== DAG_PB_CODE2) {
|
|
7476
7166
|
throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE2}) block encoding`);
|
|
7477
7167
|
} else {
|
|
7478
|
-
return new _CID2(version, code3,
|
|
7168
|
+
return new _CID2(version, code3, digest3, digest3.bytes);
|
|
7479
7169
|
}
|
|
7480
7170
|
}
|
|
7481
7171
|
case 1: {
|
|
7482
|
-
const bytes3 = encodeCID2(version, code3,
|
|
7483
|
-
return new _CID2(version, code3,
|
|
7172
|
+
const bytes3 = encodeCID2(version, code3, digest3.bytes);
|
|
7173
|
+
return new _CID2(version, code3, digest3, bytes3);
|
|
7484
7174
|
}
|
|
7485
7175
|
default: {
|
|
7486
7176
|
throw new Error("Invalid version");
|
|
@@ -7490,8 +7180,8 @@ var CID2 = class _CID2 {
|
|
|
7490
7180
|
/**
|
|
7491
7181
|
* Simplified version of `create` for CIDv0.
|
|
7492
7182
|
*/
|
|
7493
|
-
static createV0(
|
|
7494
|
-
return _CID2.create(0, DAG_PB_CODE2,
|
|
7183
|
+
static createV0(digest3) {
|
|
7184
|
+
return _CID2.create(0, DAG_PB_CODE2, digest3);
|
|
7495
7185
|
}
|
|
7496
7186
|
/**
|
|
7497
7187
|
* Simplified version of `create` for CIDv1.
|
|
@@ -7499,8 +7189,8 @@ var CID2 = class _CID2 {
|
|
|
7499
7189
|
* @param code - Content encoding format code.
|
|
7500
7190
|
* @param digest - Multihash of the content.
|
|
7501
7191
|
*/
|
|
7502
|
-
static createV1(code3,
|
|
7503
|
-
return _CID2.create(1, code3,
|
|
7192
|
+
static createV1(code3, digest3) {
|
|
7193
|
+
return _CID2.create(1, code3, digest3);
|
|
7504
7194
|
}
|
|
7505
7195
|
/**
|
|
7506
7196
|
* Decoded a CID from its binary representation. The byte array must contain
|
|
@@ -7533,8 +7223,8 @@ var CID2 = class _CID2 {
|
|
|
7533
7223
|
throw new Error("Incorrect length");
|
|
7534
7224
|
}
|
|
7535
7225
|
const digestBytes3 = multihashBytes.subarray(specs.multihashSize - specs.digestSize);
|
|
7536
|
-
const
|
|
7537
|
-
const cid2 = specs.version === 0 ? _CID2.createV0(
|
|
7226
|
+
const digest3 = new Digest2(specs.multihashCode, specs.digestSize, digestBytes3, multihashBytes);
|
|
7227
|
+
const cid2 = specs.version === 0 ? _CID2.createV0(digest3) : _CID2.createV1(specs.codec, digest3);
|
|
7538
7228
|
return [cid2, bytes3.subarray(specs.size)];
|
|
7539
7229
|
}
|
|
7540
7230
|
/**
|
|
@@ -7686,10 +7376,10 @@ function fromB64(value) {
|
|
|
7686
7376
|
throw new Error("base64url decoding is unavailable");
|
|
7687
7377
|
}
|
|
7688
7378
|
function cid(bytesValue) {
|
|
7689
|
-
return CID2.createV1(code2, create2(SHA256_CODE2, (0,
|
|
7379
|
+
return CID2.createV1(code2, create2(SHA256_CODE2, (0, import_sha2564.sha256)(bytesValue))).toString(base322.encoder);
|
|
7690
7380
|
}
|
|
7691
|
-
function
|
|
7692
|
-
return b64((0,
|
|
7381
|
+
function digest2(bytesValue) {
|
|
7382
|
+
return b64((0, import_sha2564.sha256)(bytesValue));
|
|
7693
7383
|
}
|
|
7694
7384
|
function didKeyFromEd25519PublicKey2(publicKey) {
|
|
7695
7385
|
if (publicKey.length !== 32) throw new Error("share key public key must be 32 bytes");
|
|
@@ -7735,821 +7425,149 @@ function dagCborEncode(value) {
|
|
|
7735
7425
|
writeHeader(4, item.length);
|
|
7736
7426
|
item.forEach(write);
|
|
7737
7427
|
return;
|
|
7738
|
-
}
|
|
7739
|
-
if (typeof item === "object" && item !== null) {
|
|
7740
|
-
const entries = Object.entries(item).map(([key, value2]) => {
|
|
7741
|
-
const keyBytes = dagCborEncode(key);
|
|
7742
|
-
return { key, value: value2, keyBytes };
|
|
7743
|
-
}).sort((left, right) => {
|
|
7744
|
-
if (left.keyBytes.length !== right.keyBytes.length) return left.keyBytes.length - right.keyBytes.length;
|
|
7745
|
-
for (let index = 0; index < left.keyBytes.length; index += 1) {
|
|
7746
|
-
const leftByte = left.keyBytes[index] ?? 0;
|
|
7747
|
-
const rightByte = right.keyBytes[index] ?? 0;
|
|
7748
|
-
if (leftByte !== rightByte) return leftByte - rightByte;
|
|
7749
|
-
}
|
|
7750
|
-
return 0;
|
|
7751
|
-
});
|
|
7752
|
-
writeHeader(5, entries.length);
|
|
7753
|
-
entries.forEach(({ key, value: value2 }) => {
|
|
7754
|
-
write(key);
|
|
7755
|
-
write(value2);
|
|
7756
|
-
});
|
|
7757
|
-
return;
|
|
7758
|
-
}
|
|
7759
|
-
throw new Error("Unsupported DAG-CBOR value");
|
|
7760
|
-
};
|
|
7761
|
-
write(value);
|
|
7762
|
-
return Uint8Array.from(output);
|
|
7763
|
-
}
|
|
7764
|
-
function assertObject(value, keys, label) {
|
|
7765
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
7766
|
-
const record2 = value;
|
|
7767
|
-
if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
|
|
7768
|
-
return record2;
|
|
7769
|
-
}
|
|
7770
|
-
async function createDelegatedShareKey(options) {
|
|
7771
|
-
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7772
|
-
const generated = await crypto.subtle.generateKey({ name: "Ed25519" }, options.extractable, ["sign", "verify"]);
|
|
7773
|
-
const privateKey = generated.privateKey;
|
|
7774
|
-
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", generated.publicKey));
|
|
7775
|
-
let cleared = false;
|
|
7776
|
-
const key = {
|
|
7777
|
-
did: didKeyFromEd25519PublicKey2(publicKey),
|
|
7778
|
-
publicKey: bytes(publicKey),
|
|
7779
|
-
extractable: options.extractable,
|
|
7780
|
-
cryptoKey: privateKey,
|
|
7781
|
-
async sign(input) {
|
|
7782
|
-
if (cleared) throw new Error("share key has been cleared");
|
|
7783
|
-
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, privateKey, input));
|
|
7784
|
-
},
|
|
7785
|
-
clear() {
|
|
7786
|
-
cleared = true;
|
|
7787
|
-
}
|
|
7788
|
-
};
|
|
7789
|
-
return key;
|
|
7790
|
-
}
|
|
7791
|
-
function restoreDelegatedShareKey(input) {
|
|
7792
|
-
let cleared = false;
|
|
7793
|
-
return {
|
|
7794
|
-
did: didKeyFromEd25519PublicKey2(input.publicKey),
|
|
7795
|
-
publicKey: bytes(input.publicKey),
|
|
7796
|
-
extractable: input.extractable,
|
|
7797
|
-
cryptoKey: input.privateKey,
|
|
7798
|
-
async sign(signBytes) {
|
|
7799
|
-
if (cleared) throw new Error("share key has been cleared");
|
|
7800
|
-
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7801
|
-
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, input.privateKey, signBytes));
|
|
7802
|
-
},
|
|
7803
|
-
clear() {
|
|
7804
|
-
cleared = true;
|
|
7805
|
-
}
|
|
7806
|
-
};
|
|
7807
|
-
}
|
|
7808
|
-
async function createPolicyEnforcementDelegation(input) {
|
|
7809
|
-
if (input.shareKey.did.length === 0 || input.ownerDelegation.delegationCid.length === 0) throw new Error("owner delegation and share key are required");
|
|
7810
|
-
const facts = {
|
|
7811
|
-
ownerDelegationCid: input.ownerDelegation.delegationCid,
|
|
7812
|
-
policyCid: input.policyCid,
|
|
7813
|
-
shareId: input.shareId,
|
|
7814
|
-
shareKeyDid: input.shareKey.did,
|
|
7815
|
-
enforcerDid: input.enforcerDid,
|
|
7816
|
-
nodeAudience: input.nodeAudience,
|
|
7817
|
-
spaceId: input.spaceId,
|
|
7818
|
-
path: input.path,
|
|
7819
|
-
actions: [...input.actions],
|
|
7820
|
-
contentSourceDigest: input.contentSourceDigest,
|
|
7821
|
-
expiresAt: input.expiresAt
|
|
7822
|
-
};
|
|
7823
|
-
const unsigned = { type: "TinyCloudSharePolicyEnforcement", version: 2, issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts };
|
|
7824
|
-
const dagCborBytes = dagCborEncode({ domain: POLICY_ENFORCEMENT_DOMAIN, unsigned });
|
|
7825
|
-
const signature = b64(await input.shareKey.sign(dagCborBytes));
|
|
7826
|
-
return { cid: cid(dagCborBytes), dagCbor: b64(dagCborBytes), issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts, signature };
|
|
7827
|
-
}
|
|
7828
|
-
async function canonicalOwnerSharePolicy(policy) {
|
|
7829
|
-
const text = canonicalize({ domain: POLICY_DOMAIN, policy });
|
|
7830
|
-
const policyBytes = new TextEncoder().encode(text);
|
|
7831
|
-
return { bytes: policyBytes, cid: cid(policyBytes), digest: digest(policyBytes) };
|
|
7832
|
-
}
|
|
7833
|
-
function computeOwnerShareRegistrationCid(registration) {
|
|
7834
|
-
return cid(new TextEncoder().encode(canonicalize(registration)));
|
|
7835
|
-
}
|
|
7836
|
-
function validateOwnerSharePolicyRegistration(value, expected) {
|
|
7837
|
-
if (cid(expected.policy.bytes) !== expected.policy.cid) throw new Error("submitted owner-share policy bytes do not match its CID");
|
|
7838
|
-
const root = assertObject(value, ["registration", "proof"], "owner-share registration response");
|
|
7839
|
-
const registration = assertObject(root.registration, ["registrationCid", "policyCid", "ownerDelegationCid", "enforcementDelegationCid", "ownerDid", "shareKeyDid", "enforcerDid", "shareId", "recipientMatcher", "target", "resource", "actions", "contentSource", "contentSourceDigest", "registeredAt", "expiresAt"], "owner-share registration");
|
|
7840
|
-
const proof = assertObject(root.proof, ["alg", "kid", "signature"], "owner-share registration proof");
|
|
7841
|
-
if (registration.policyCid !== expected.policy.cid || registration.ownerDelegationCid !== expected.ownerDelegation.delegationCid || registration.enforcementDelegationCid !== expected.enforcementDelegation.cid || registration.contentSourceDigest !== expected.contentSourceDigest) throw new Error("owner-share registration is not bound to the submitted chain");
|
|
7842
|
-
let policyValue;
|
|
7843
|
-
try {
|
|
7844
|
-
policyValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(expected.policy.bytes));
|
|
7845
|
-
} catch {
|
|
7846
|
-
throw new Error("owner-share policy is not valid JSON");
|
|
7847
|
-
}
|
|
7848
|
-
const policy = policyValue.policy;
|
|
7849
|
-
if (policyValue.domain !== "xyz.tinycloud.share/policy/v2\0" || policy === void 0 || Array.isArray(policy)) throw new Error("owner-share policy envelope is invalid");
|
|
7850
|
-
const target = policy.target;
|
|
7851
|
-
const resource = policy.resource;
|
|
7852
|
-
const registrationTarget = registration.target;
|
|
7853
|
-
const registrationResource = registration.resource;
|
|
7854
|
-
const enforcementFacts = expected.enforcementDelegation.facts;
|
|
7855
|
-
if (typeof target?.origin !== "string" || typeof target.nodeAudience !== "string" || typeof target.enforcerDid !== "string" || typeof target.spaceId !== "string" || typeof resource?.kind !== "string" || !["exact", "prefix"].includes(resource.kind) || typeof resource?.path !== "string" || !Array.isArray(policy.actions) || policy.ownerDid !== registration.ownerDid || policy.shareKeyDid !== registration.shareKeyDid || policy.shareId !== registration.shareId || canonicalize(policy.recipientMatcher) !== canonicalize(registration.recipientMatcher) || target.origin !== registrationTarget.origin || target.nodeAudience !== registrationTarget.nodeAudience || target.enforcerDid !== registration.enforcerDid || target.spaceId !== registrationTarget.spaceId || resource.kind !== registrationResource.kind || resource.path !== registrationResource.path || canonicalize(policy.actions) !== canonicalize(registration.actions) || canonicalize(policy.contentSource) !== canonicalize(registration.contentSource) || policy.expiresAt !== registration.expiresAt || policy.contentSourceDigest !== registration.contentSourceDigest || enforcementFacts.ownerDelegationCid !== registration.ownerDelegationCid || enforcementFacts.policyCid !== registration.policyCid || enforcementFacts.shareKeyDid !== registration.shareKeyDid || enforcementFacts.enforcerDid !== registration.enforcerDid || enforcementFacts.nodeAudience !== registrationTarget.nodeAudience || enforcementFacts.spaceId !== registrationTarget.spaceId || enforcementFacts.path !== registrationResource.path || canonicalize(enforcementFacts.actions) !== canonicalize(registration.actions) || enforcementFacts.expiresAt !== registration.expiresAt) throw new Error("owner-share registration is not bound to the canonical policy");
|
|
7856
|
-
if (typeof registration.registrationCid !== "string" || typeof registration.expiresAt !== "string" || typeof registration.registeredAt !== "string") throw new Error("owner-share registration timestamps are invalid");
|
|
7857
|
-
if (new Date(registration.expiresAt).toISOString() !== registration.expiresAt || Date.parse(registration.expiresAt) <= Date.now()) throw new Error("owner-share registration is expired or non-canonical");
|
|
7858
|
-
const { registrationCid: _registrationCid, ...registrationCore } = registration;
|
|
7859
|
-
if (computeOwnerShareRegistrationCid(registrationCore) !== registration.registrationCid) throw new Error("owner-share registration CID does not match its canonical core");
|
|
7860
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("owner-share registration proof is invalid");
|
|
7861
|
-
const proofKey = expected.nodeProof;
|
|
7862
|
-
if (proofKey === void 0) throw new Error("owner-share registration proof trust material is required");
|
|
7863
|
-
if (proof.kid !== proofKey.kid) throw new Error("owner-share registration proof key is not trusted");
|
|
7864
|
-
const encodedKid = proofKey.publicKey;
|
|
7865
|
-
if (encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1) {
|
|
7866
|
-
} else if (encodedKid.length !== 32) throw new Error("owner-share registration proof key is invalid");
|
|
7867
|
-
const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
|
|
7868
|
-
const signatureBytes = fromB64(proof.signature);
|
|
7869
|
-
const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
|
|
7870
|
-
if (signatureBytes.length !== 64 || !import_ed255195.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
|
|
7871
|
-
return { registration, proof };
|
|
7872
|
-
}
|
|
7873
|
-
function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
7874
|
-
let value;
|
|
7875
|
-
try {
|
|
7876
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
7877
|
-
} catch {
|
|
7878
|
-
throw new Error("owner-share registration response is not valid UTF-8 JSON");
|
|
7879
|
-
}
|
|
7880
|
-
return validateOwnerSharePolicyRegistration(value, expected);
|
|
7881
|
-
}
|
|
7882
|
-
|
|
7883
|
-
// src/authorization.ts
|
|
7884
|
-
async function authorizeShare(input) {
|
|
7885
|
-
if (input.resumeToken !== void 0) {
|
|
7886
|
-
if (input.resumeToken.length < 16 || input.resumeToken.length > 512) {
|
|
7887
|
-
return { state: "denied", reason: "rejected" };
|
|
7888
|
-
}
|
|
7889
|
-
return input.adapter.resume({
|
|
7890
|
-
envelope: input.envelope,
|
|
7891
|
-
method: input.method,
|
|
7892
|
-
resumeToken: input.resumeToken,
|
|
7893
|
-
...input.proof === void 0 ? {} : { proof: input.proof }
|
|
7894
|
-
});
|
|
7895
|
-
}
|
|
7896
|
-
return input.adapter.begin({ envelope: input.envelope, method: input.method });
|
|
7897
|
-
}
|
|
7898
|
-
function authorizationMethodForTarget(target) {
|
|
7899
|
-
if (target.kind === "recipientDid") return "openkey-device";
|
|
7900
|
-
if (target.kind === "email") return "email-claim";
|
|
7901
|
-
if (target.kind === "emailDomain") return "email-claim";
|
|
7902
|
-
return void 0;
|
|
7903
|
-
}
|
|
7904
|
-
|
|
7905
|
-
// src/targets.ts
|
|
7906
|
-
function validEmail(value) {
|
|
7907
|
-
return /^[^@\s]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(value);
|
|
7908
|
-
}
|
|
7909
|
-
function validDomain(value) {
|
|
7910
|
-
return /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))+$/i.test(value);
|
|
7911
|
-
}
|
|
7912
|
-
function validRecipientDid(value) {
|
|
7913
|
-
if (value.length === 0 || value.length > 2048 || /[\u0000-\u0020\u007f]/.test(value)) return false;
|
|
7914
|
-
const parts = value.split(":");
|
|
7915
|
-
if (parts.length < 3 || parts[0] !== "did" || !/^[a-z0-9]+$/.test(parts[1] ?? "")) return false;
|
|
7916
|
-
const identifier = parts.slice(2);
|
|
7917
|
-
if (identifier.some((part) => part.length === 0)) return false;
|
|
7918
|
-
if (parts[1] === "web") {
|
|
7919
|
-
const host = identifier[0] ?? "";
|
|
7920
|
-
if (host.length > 253 || host.split(".").some((label) => !label || label.length > 63 || !/^[A-Za-z0-9-]+$/.test(label) || label.startsWith("-") || label.endsWith("-"))) return false;
|
|
7921
|
-
return identifier.slice(1).every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
7922
|
-
}
|
|
7923
|
-
if (parts[1] === "pkh") return identifier.length >= 3 && identifier.every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
7924
|
-
if (parts[1] === "key") {
|
|
7925
|
-
try {
|
|
7926
|
-
const bytes3 = base58btc2.decode(identifier.join(":"));
|
|
7927
|
-
return bytes3.length === 34 && bytes3[0] === 237 && bytes3[1] === 1;
|
|
7928
|
-
} catch {
|
|
7929
|
-
return false;
|
|
7930
|
-
}
|
|
7931
|
-
}
|
|
7932
|
-
return false;
|
|
7933
|
-
}
|
|
7934
|
-
function normalizeShareTarget(target) {
|
|
7935
|
-
if (target.kind === "bearer") return target;
|
|
7936
|
-
if (target.kind === "recipientDid") {
|
|
7937
|
-
if (!validRecipientDid(target.did)) throw new TypeError("recipient DID is invalid");
|
|
7938
|
-
return { kind: target.kind, did: target.did };
|
|
7939
|
-
}
|
|
7940
|
-
if (target.kind === "email") {
|
|
7941
|
-
if (!validEmail(target.address)) throw new TypeError("recipient email is invalid");
|
|
7942
|
-
const at = target.address.lastIndexOf("@");
|
|
7943
|
-
return { kind: target.kind, address: `${target.address.slice(0, at)}@${target.address.slice(at + 1).toLowerCase()}` };
|
|
7944
|
-
}
|
|
7945
|
-
if (!validDomain(target.domain)) throw new TypeError("recipient email domain is invalid");
|
|
7946
|
-
return { kind: target.kind, domain: target.domain.toLowerCase() };
|
|
7947
|
-
}
|
|
7948
|
-
function targetAuthorizationMethod(target) {
|
|
7949
|
-
return authorizationMethodForTarget(target);
|
|
7950
|
-
}
|
|
7951
|
-
async function publishTargetShare(input) {
|
|
7952
|
-
const target = normalizeShareTarget(input.target);
|
|
7953
|
-
if (target.kind === "bearer") return publishShare(input);
|
|
7954
|
-
if (input.targetAdapter === void 0) {
|
|
7955
|
-
return {
|
|
7956
|
-
state: "authorization-required",
|
|
7957
|
-
method: targetAuthorizationMethod(target)
|
|
7958
|
-
};
|
|
7959
|
-
}
|
|
7960
|
-
const source = input.source instanceof Uint8Array ? input.source.slice() : await (async () => {
|
|
7961
|
-
const chunks = [];
|
|
7962
|
-
let size = 0;
|
|
7963
|
-
for await (const chunk of input.source) {
|
|
7964
|
-
if (!(chunk instanceof Uint8Array)) throw new TypeError("addressed publication source yielded invalid bytes");
|
|
7965
|
-
size += chunk.byteLength;
|
|
7966
|
-
if (size > (input.maxBytes ?? 100 * 1024 * 1024)) throw new TypeError("addressed publication exceeds maxBytes");
|
|
7967
|
-
chunks.push(chunk.slice());
|
|
7968
|
-
}
|
|
7969
|
-
const bytes3 = new Uint8Array(size);
|
|
7970
|
-
let offset = 0;
|
|
7971
|
-
for (const chunk of chunks) {
|
|
7972
|
-
bytes3.set(chunk, offset);
|
|
7973
|
-
offset += chunk.byteLength;
|
|
7974
|
-
}
|
|
7975
|
-
return bytes3;
|
|
7976
|
-
})();
|
|
7977
|
-
const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
|
|
7978
|
-
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
7979
|
-
let totalBytes = 0;
|
|
7980
|
-
for (const file of files) {
|
|
7981
|
-
totalBytes += file.bytes.byteLength;
|
|
7982
|
-
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
7983
|
-
throw new SharePublishError("max-bytes-exceeded", "addressed publication exceeds the combined byte limit");
|
|
7984
|
-
}
|
|
7985
|
-
}
|
|
7986
|
-
return input.targetAdapter.publish({
|
|
7987
|
-
source,
|
|
7988
|
-
filename: input.filename,
|
|
7989
|
-
...input.files === void 0 ? {} : { files: input.files },
|
|
7990
|
-
...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
|
|
7991
|
-
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
7992
|
-
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
7993
|
-
target,
|
|
7994
|
-
expiresAt: input.expiresAt ?? new Date((input.now?.() ?? Date.now()) + 7 * 24 * 60 * 60 * 1e3),
|
|
7995
|
-
origin: input.origin,
|
|
7996
|
-
...input.inline === void 0 ? {} : { inline: input.inline },
|
|
7997
|
-
upload: {
|
|
7998
|
-
...input.registryBaseUrl === void 0 ? {} : { registryBaseUrl: input.registryBaseUrl },
|
|
7999
|
-
...input.fetchFn === void 0 ? {} : { fetchFn: input.fetchFn },
|
|
8000
|
-
...input.authorizeUpload === void 0 ? {} : { authorizeUpload: input.authorizeUpload },
|
|
8001
|
-
...input.authorizationOrigin === void 0 ? {} : { authorizationOrigin: input.authorizationOrigin },
|
|
8002
|
-
...input.credentials === void 0 ? {} : { credentials: input.credentials },
|
|
8003
|
-
...input.allowInsecureRegistry === void 0 ? {} : { allowInsecureRegistry: input.allowInsecureRegistry },
|
|
8004
|
-
...input.uploadBlob === void 0 ? {} : { uploadBlob: input.uploadBlob }
|
|
8005
|
-
},
|
|
8006
|
-
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
8007
|
-
});
|
|
8008
|
-
}
|
|
8009
|
-
|
|
8010
|
-
// src/addressed-publish.ts
|
|
8011
|
-
var ENVELOPE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
8012
|
-
function targetMatcher(target) {
|
|
8013
|
-
if (target.kind === "recipientDid") return { kind: "recipientDid", value: target.did };
|
|
8014
|
-
if (target.kind === "email") return { kind: "exactEmail", value: target.address };
|
|
8015
|
-
return { kind: "emailDomain", value: target.domain };
|
|
8016
|
-
}
|
|
8017
|
-
function targetKind(target) {
|
|
8018
|
-
return target.kind;
|
|
8019
|
-
}
|
|
8020
|
-
function assertSafeInput(input) {
|
|
8021
|
-
if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
|
|
8022
|
-
if (input.filename.length === 0 || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) throw new TypeError("addressed filename is invalid");
|
|
8023
|
-
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
|
|
8024
|
-
if (input.actions.length === 0 || input.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
8025
|
-
const expiry = input.expiresAt.getTime();
|
|
8026
|
-
if (!Number.isFinite(expiry) || expiry <= Date.now()) throw new TypeError("addressed share expiry must be in the future");
|
|
8027
|
-
if (input.artifact === "html" && (input.resource.kind !== "prefix" || !input.actions.includes("read") || !input.actions.includes("list"))) throw new TypeError("html artifacts require a readable prefix");
|
|
8028
|
-
if (input.contentSource.kind !== "kv") throw new TypeError("addressed policy publication requires a KV content source");
|
|
8029
|
-
}
|
|
8030
|
-
async function sha2564(value) {
|
|
8031
|
-
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
|
|
8032
|
-
}
|
|
8033
|
-
function publicationResult(input) {
|
|
8034
|
-
const result = {
|
|
8035
|
-
protocol: "tinycloud-share",
|
|
8036
|
-
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
8037
|
-
url: input.url,
|
|
8038
|
-
link: { kind: input.options.inline === true ? "inline" : "compact", cid: input.envelopeCid },
|
|
8039
|
-
metadata: {
|
|
8040
|
-
protocol: "tinycloud-share",
|
|
8041
|
-
version: 1,
|
|
8042
|
-
shareId: input.options.shareId,
|
|
8043
|
-
origin: input.options.shareOrigin,
|
|
8044
|
-
target: {
|
|
8045
|
-
kind: targetKind(input.options.target),
|
|
8046
|
-
origin: input.options.nodeOrigin,
|
|
8047
|
-
nodeAudience: input.options.nodeAudience,
|
|
8048
|
-
spaceId: input.options.spaceId
|
|
8049
|
-
},
|
|
8050
|
-
resource: { ...input.options.resource },
|
|
8051
|
-
actions: [...input.options.actions],
|
|
8052
|
-
expiresAt: input.options.expiresAt.toISOString(),
|
|
8053
|
-
display: { filename: input.options.filename },
|
|
8054
|
-
recipientMatcher: { ...input.matcher },
|
|
8055
|
-
registrationCid: input.registration.registrationCid,
|
|
8056
|
-
policyCid: input.policyCid,
|
|
8057
|
-
ownerDelegationCid: input.registration.ownerDelegationCid,
|
|
8058
|
-
enforcementDelegationCid: input.enforcementDelegationCid,
|
|
8059
|
-
ownerDid: input.registration.ownerDid,
|
|
8060
|
-
shareKeyDid: input.shareKeyDid,
|
|
8061
|
-
enforcerDid: input.registration.enforcerDid,
|
|
8062
|
-
envelopeCid: input.envelopeCid,
|
|
8063
|
-
shareCid: input.shareCid
|
|
8064
|
-
},
|
|
8065
|
-
registryDeleteAfter: input.retention
|
|
8066
|
-
};
|
|
8067
|
-
Object.defineProperty(result, "toJSON", { enumerable: false, value: () => redactPublishedShare(result) });
|
|
8068
|
-
Object.defineProperty(result, "url", { enumerable: false, value: input.url });
|
|
8069
|
-
return result;
|
|
8070
|
-
}
|
|
8071
|
-
async function publishAddressedShare(options) {
|
|
8072
|
-
assertSafeInput(options);
|
|
8073
|
-
const target = normalizeShareTarget(options.target);
|
|
8074
|
-
if (target.kind === "bearer") throw new TypeError("addressed target is required");
|
|
8075
|
-
if (options.contentSource.kind !== "kv") throw new TypeError("addressed policy publication requires a KV content source");
|
|
8076
|
-
const contentSource = options.contentSource;
|
|
8077
|
-
const matcher = targetMatcher(target);
|
|
8078
|
-
const expiresAt = options.expiresAt.toISOString();
|
|
8079
|
-
const contentSourceDigest = options.contentSourceDigest ?? await sha2564(new TextEncoder().encode(canonicalize(contentSource)));
|
|
8080
|
-
const shareKey = await createDelegatedShareKey({ extractable: false });
|
|
8081
|
-
let envelopeKey;
|
|
8082
|
-
try {
|
|
8083
|
-
const permissions = [
|
|
8084
|
-
{
|
|
8085
|
-
service: "tinycloud.kv",
|
|
8086
|
-
path: options.resource.kind === "prefix" ? `${options.resource.path.replace(/\/+$/, "")}/` : options.resource.path,
|
|
8087
|
-
actions: [...options.policyActions]
|
|
8088
|
-
},
|
|
8089
|
-
...options.decryption === void 0 ? [] : [{
|
|
8090
|
-
service: "tinycloud.encryption",
|
|
8091
|
-
path: options.decryption.networkId,
|
|
8092
|
-
actions: [options.decryption.action]
|
|
8093
|
-
}]
|
|
8094
|
-
];
|
|
8095
|
-
const ownerDelegation = await options.authority.createOwnerDelegation({
|
|
8096
|
-
delegateDid: shareKey.did,
|
|
8097
|
-
spaceId: options.spaceId,
|
|
8098
|
-
permissions,
|
|
8099
|
-
expiresAt: options.expiresAt
|
|
8100
|
-
});
|
|
8101
|
-
const policy = {
|
|
8102
|
-
type: "TinyCloudSharePolicy",
|
|
8103
|
-
version: 2,
|
|
8104
|
-
shareId: options.shareId,
|
|
8105
|
-
ownerDid: options.authority.ownerDid,
|
|
8106
|
-
shareKeyDid: shareKey.did,
|
|
8107
|
-
recipientMatcher: matcher,
|
|
8108
|
-
target: {
|
|
8109
|
-
origin: options.nodeOrigin,
|
|
8110
|
-
nodeAudience: options.nodeAudience,
|
|
8111
|
-
enforcerDid: options.enforcerDid,
|
|
8112
|
-
spaceId: options.spaceId
|
|
8113
|
-
},
|
|
8114
|
-
resource: { ...options.resource },
|
|
8115
|
-
actions: [...options.policyActions],
|
|
8116
|
-
...options.decryption === void 0 ? {} : { decryption: options.decryption },
|
|
8117
|
-
contentSource,
|
|
8118
|
-
contentSourceDigest,
|
|
8119
|
-
ownerDelegationCid: ownerDelegation.delegationCid,
|
|
8120
|
-
expiresAt
|
|
8121
|
-
};
|
|
8122
|
-
const canonicalPolicy = await canonicalOwnerSharePolicy(policy);
|
|
8123
|
-
const policyProof = toBase64Url(await shareKey.sign(canonicalPolicy.bytes));
|
|
8124
|
-
const enforcementDelegation = await createPolicyEnforcementDelegation({
|
|
8125
|
-
ownerDelegation,
|
|
8126
|
-
shareKey,
|
|
8127
|
-
enforcerDid: options.enforcerDid,
|
|
8128
|
-
policyCid: canonicalPolicy.cid,
|
|
8129
|
-
shareId: options.shareId,
|
|
8130
|
-
spaceId: options.spaceId,
|
|
8131
|
-
nodeAudience: options.nodeAudience,
|
|
8132
|
-
path: options.resource.path,
|
|
8133
|
-
actions: options.policyActions,
|
|
8134
|
-
contentSourceDigest,
|
|
8135
|
-
expiresAt
|
|
8136
|
-
});
|
|
8137
|
-
const registration = await options.authority.registerOwnerSharePolicy({
|
|
8138
|
-
policy: { bytes: canonicalPolicy.bytes, cid: canonicalPolicy.cid, proof: policyProof },
|
|
8139
|
-
ownerDelegation,
|
|
8140
|
-
enforcementDelegation,
|
|
8141
|
-
contentSourceDigest
|
|
8142
|
-
});
|
|
8143
|
-
const authorityMaterialDigest = await sha2564(fromBase64Url(enforcementDelegation.dagCbor));
|
|
8144
|
-
const authorityTarget = {
|
|
8145
|
-
origin: options.nodeOrigin,
|
|
8146
|
-
nodeAudience: options.nodeAudience,
|
|
8147
|
-
enforcerDid: options.enforcerDid,
|
|
8148
|
-
spaceId: options.spaceId
|
|
8149
|
-
};
|
|
8150
|
-
const envelopeIdentity = {
|
|
8151
|
-
schema: "xyz.tinycloud.share/envelope/v2",
|
|
8152
|
-
version: 2,
|
|
8153
|
-
shareId: options.shareId,
|
|
8154
|
-
delegationCid: ownerDelegation.delegationCid,
|
|
8155
|
-
policyCid: canonicalPolicy.cid,
|
|
8156
|
-
target: authorityTarget,
|
|
8157
|
-
resource: options.resource,
|
|
8158
|
-
actions: options.policyActions,
|
|
8159
|
-
...options.decryption === void 0 ? {} : { decryption: options.decryption },
|
|
8160
|
-
contentSource,
|
|
8161
|
-
contentSourceDigest,
|
|
8162
|
-
expiresAt
|
|
8163
|
-
};
|
|
8164
|
-
const envelopeCid = await computeCid(new TextEncoder().encode(canonicalize(envelopeIdentity)));
|
|
8165
|
-
const shareCid = await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: options.shareId, policyCid: canonicalPolicy.cid, envelopeCid })));
|
|
8166
|
-
const outerUnsigned = { ...envelopeIdentity, envelopeCid, shareCid };
|
|
8167
|
-
const outerSignature = toBase64Url(await shareKey.sign(new TextEncoder().encode(`${ENVELOPE_DOMAIN}${canonicalize(outerUnsigned)}`)));
|
|
8168
|
-
const unsigned = {
|
|
8169
|
-
version: 2,
|
|
8170
|
-
shareId: options.shareId,
|
|
8171
|
-
recipientMatcher: matcher,
|
|
8172
|
-
...options.deliveryEmail === void 0 ? {} : { deliveryEmail: options.deliveryEmail },
|
|
8173
|
-
actions: [...options.actions],
|
|
8174
|
-
resource: { ...options.resource },
|
|
8175
|
-
target: { origin: options.nodeOrigin, nodeAudience: options.nodeAudience, spaceId: options.spaceId },
|
|
8176
|
-
delegationCid: ownerDelegation.delegationCid,
|
|
8177
|
-
authorityMaterialHandle: registration.registration.registrationCid,
|
|
8178
|
-
authorityMaterialDigest,
|
|
8179
|
-
contentSource,
|
|
8180
|
-
contentSourceDigest,
|
|
8181
|
-
authorizationTarget: { kind: "policy", policyCid: canonicalPolicy.cid, policyBytes: toBase64Url(canonicalPolicy.bytes) },
|
|
8182
|
-
display: { filename: options.filename },
|
|
8183
|
-
expiry: expiresAt,
|
|
8184
|
-
encrypted: true,
|
|
8185
|
-
metadata: {
|
|
8186
|
-
mediaType: options.mediaType,
|
|
8187
|
-
byteLength: options.byteLength,
|
|
8188
|
-
filename: options.filename,
|
|
8189
|
-
...options.mediaType.startsWith("text/") ? { encoding: "utf-8" } : {},
|
|
8190
|
-
...options.artifact === void 0 ? {} : { artifact: options.artifact }
|
|
8191
|
-
},
|
|
8192
|
-
ownerAuthority: {
|
|
8193
|
-
registrationCid: registration.registration.registrationCid,
|
|
8194
|
-
shareCid,
|
|
8195
|
-
envelopeCid,
|
|
8196
|
-
enforcementDelegation,
|
|
8197
|
-
registrationReceipt: registration,
|
|
8198
|
-
outerEnvelope: {
|
|
8199
|
-
...outerUnsigned,
|
|
8200
|
-
signature: { signerDid: shareKey.did, algorithm: "Ed25519", value: outerSignature }
|
|
7428
|
+
}
|
|
7429
|
+
if (typeof item === "object" && item !== null) {
|
|
7430
|
+
const entries = Object.entries(item).map(([key, value2]) => {
|
|
7431
|
+
const keyBytes = dagCborEncode(key);
|
|
7432
|
+
return { key, value: value2, keyBytes };
|
|
7433
|
+
}).sort((left, right) => {
|
|
7434
|
+
if (left.keyBytes.length !== right.keyBytes.length) return left.keyBytes.length - right.keyBytes.length;
|
|
7435
|
+
for (let index = 0; index < left.keyBytes.length; index += 1) {
|
|
7436
|
+
const leftByte = left.keyBytes[index] ?? 0;
|
|
7437
|
+
const rightByte = right.keyBytes[index] ?? 0;
|
|
7438
|
+
if (leftByte !== rightByte) return leftByte - rightByte;
|
|
8201
7439
|
}
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
const uploaded = await uploadShareBlob({
|
|
8216
|
-
source: new Uint8Array([1]),
|
|
8217
|
-
filename: options.filename,
|
|
8218
|
-
origin: options.shareOrigin,
|
|
8219
|
-
...options.upload
|
|
8220
|
-
}, { blob: sealed.blob, cid: sealed.cid, deleteAfter: expiresAt, contentLength: sealed.blob.byteLength });
|
|
8221
|
-
retention = uploaded.deleteAfter;
|
|
8222
|
-
url = encodeShareUrl({ origin: options.shareOrigin, ciphertextCid: uploaded.cid, key32: envelopeKey });
|
|
8223
|
-
}
|
|
8224
|
-
return publicationResult({
|
|
8225
|
-
options,
|
|
8226
|
-
url,
|
|
8227
|
-
envelopeCid: sealed.cid,
|
|
8228
|
-
shareCid,
|
|
8229
|
-
matcher,
|
|
8230
|
-
registration: registration.registration,
|
|
8231
|
-
policyCid: canonicalPolicy.cid,
|
|
8232
|
-
enforcementDelegationCid: enforcementDelegation.cid,
|
|
8233
|
-
shareKeyDid: shareKey.did,
|
|
8234
|
-
retention
|
|
8235
|
-
});
|
|
8236
|
-
} finally {
|
|
8237
|
-
shareKey.clear();
|
|
8238
|
-
envelopeKey?.fill(0);
|
|
8239
|
-
}
|
|
7440
|
+
return 0;
|
|
7441
|
+
});
|
|
7442
|
+
writeHeader(5, entries.length);
|
|
7443
|
+
entries.forEach(({ key, value: value2 }) => {
|
|
7444
|
+
write(key);
|
|
7445
|
+
write(value2);
|
|
7446
|
+
});
|
|
7447
|
+
return;
|
|
7448
|
+
}
|
|
7449
|
+
throw new Error("Unsupported DAG-CBOR value");
|
|
7450
|
+
};
|
|
7451
|
+
write(value);
|
|
7452
|
+
return Uint8Array.from(output);
|
|
8240
7453
|
}
|
|
8241
|
-
|
|
8242
|
-
// src/policy-authority.ts
|
|
8243
|
-
var import_ed255196 = require("@noble/curves/ed25519");
|
|
8244
|
-
var ENVELOPE_DOMAIN2 = "xyz.tinycloud.share/envelope/v2\0";
|
|
8245
|
-
function record(value, keys, label) {
|
|
7454
|
+
function assertObject(value, keys, label) {
|
|
8246
7455
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8247
|
-
const
|
|
8248
|
-
if (Object.keys(
|
|
8249
|
-
return
|
|
8250
|
-
}
|
|
8251
|
-
function requiredString(value, label) {
|
|
8252
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${label} is invalid`);
|
|
8253
|
-
return value;
|
|
8254
|
-
}
|
|
8255
|
-
function stringArray(value, label) {
|
|
8256
|
-
if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.length > 0)) throw new Error(`${label} is invalid`);
|
|
8257
|
-
return value;
|
|
7456
|
+
const record = value;
|
|
7457
|
+
if (Object.keys(record).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record, key))) throw new Error(`${label} has unknown or missing fields`);
|
|
7458
|
+
return record;
|
|
8258
7459
|
}
|
|
8259
|
-
function
|
|
8260
|
-
|
|
8261
|
-
const
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
nodeAudience: requiredString(facts.nodeAudience, "node audience"),
|
|
8274
|
-
spaceId: requiredString(facts.spaceId, "space id"),
|
|
8275
|
-
path: requiredString(facts.path, "resource path"),
|
|
8276
|
-
actions: stringArray(facts.actions, "policy actions"),
|
|
8277
|
-
contentSourceDigest: requiredString(facts.contentSourceDigest, "content source digest"),
|
|
8278
|
-
expiresAt: requiredString(facts.expiresAt, "policy expiry")
|
|
7460
|
+
async function createDelegatedShareKey(options) {
|
|
7461
|
+
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7462
|
+
const generated = await crypto.subtle.generateKey({ name: "Ed25519" }, options.extractable, ["sign", "verify"]);
|
|
7463
|
+
const privateKey = generated.privateKey;
|
|
7464
|
+
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", generated.publicKey));
|
|
7465
|
+
let cleared = false;
|
|
7466
|
+
const key = {
|
|
7467
|
+
did: didKeyFromEd25519PublicKey2(publicKey),
|
|
7468
|
+
publicKey: bytes(publicKey),
|
|
7469
|
+
extractable: options.extractable,
|
|
7470
|
+
cryptoKey: privateKey,
|
|
7471
|
+
async sign(input) {
|
|
7472
|
+
if (cleared) throw new Error("share key has been cleared");
|
|
7473
|
+
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, privateKey, input));
|
|
8279
7474
|
},
|
|
8280
|
-
|
|
7475
|
+
clear() {
|
|
7476
|
+
cleared = true;
|
|
7477
|
+
}
|
|
8281
7478
|
};
|
|
7479
|
+
return key;
|
|
8282
7480
|
}
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
const dagCbor = fromBase64Url(delegation.dagCbor);
|
|
8286
|
-
if (await computeCid(dagCbor) !== delegation.cid) return false;
|
|
8287
|
-
const expectedDagCbor = dagCborEncode({
|
|
8288
|
-
domain: POLICY_ENFORCEMENT_DOMAIN,
|
|
8289
|
-
unsigned: {
|
|
8290
|
-
type: "TinyCloudSharePolicyEnforcement",
|
|
8291
|
-
version: 2,
|
|
8292
|
-
issuerDid: delegation.issuerDid,
|
|
8293
|
-
audienceDid: delegation.audienceDid,
|
|
8294
|
-
facts: delegation.facts
|
|
8295
|
-
}
|
|
8296
|
-
});
|
|
8297
|
-
if (expectedDagCbor.length !== dagCbor.length || expectedDagCbor.some((byte, index) => byte !== dagCbor[index])) return false;
|
|
8298
|
-
const signature = fromBase64Url(delegation.signature);
|
|
8299
|
-
return signature.length === 64 && import_ed255196.ed25519.verify(
|
|
8300
|
-
signature,
|
|
8301
|
-
dagCbor,
|
|
8302
|
-
ed25519PublicKeyFromDidKey(delegation.issuerDid),
|
|
8303
|
-
{ zip215: false }
|
|
8304
|
-
);
|
|
8305
|
-
} catch {
|
|
8306
|
-
return false;
|
|
8307
|
-
}
|
|
8308
|
-
}
|
|
8309
|
-
async function verifyOuterEnvelope(value, envelope, policyCid, expectedEnforcerDid, expectedActions) {
|
|
8310
|
-
try {
|
|
8311
|
-
const hasDecryption = typeof value === "object" && value !== null && Object.hasOwn(value, "decryption");
|
|
8312
|
-
const outer = record(value, ["schema", "version", "shareId", "delegationCid", "policyCid", "target", "resource", "actions", ...hasDecryption ? ["decryption"] : [], "contentSource", "contentSourceDigest", "expiresAt", "envelopeCid", "shareCid", "signature"], "outer envelope");
|
|
8313
|
-
const target = record(outer.target, ["origin", "nodeAudience", "enforcerDid", "spaceId"], "outer target");
|
|
8314
|
-
const signature = record(outer.signature, ["signerDid", "algorithm", "value"], "outer envelope signature");
|
|
8315
|
-
const envelopeCid = requiredString(outer.envelopeCid, "outer envelope CID");
|
|
8316
|
-
const shareCid = requiredString(outer.shareCid, "outer share CID");
|
|
8317
|
-
const { signature: _signature, envelopeCid: _envelopeCid, shareCid: _shareCid, ...identity } = outer;
|
|
8318
|
-
if (outer.schema !== "xyz.tinycloud.share/envelope/v2" || outer.version !== 2 || outer.shareId !== envelope.shareId || outer.delegationCid !== envelope.delegationCid || outer.policyCid !== policyCid || target.origin !== envelope.target.origin || target.nodeAudience !== envelope.target.nodeAudience || target.enforcerDid !== expectedEnforcerDid || target.spaceId !== envelope.target.spaceId || canonicalize(outer.resource) !== canonicalize(envelope.resource) || canonicalize(outer.actions) !== canonicalize(expectedActions) || (hasDecryption ? envelope.decryption === void 0 || canonicalize(outer.decryption) !== canonicalize(envelope.decryption) : envelope.decryption !== void 0) || canonicalize(outer.contentSource) !== canonicalize(envelope.contentSource) || outer.contentSourceDigest !== envelope.contentSourceDigest || outer.expiresAt !== envelope.expiry || signature.signerDid !== envelope.signature.signerDid || signature.algorithm !== "Ed25519") return void 0;
|
|
8319
|
-
if (envelopeCid !== await computeCid(new TextEncoder().encode(canonicalize(identity)))) return void 0;
|
|
8320
|
-
if (shareCid !== await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: envelope.shareId, policyCid, envelopeCid })))) return void 0;
|
|
8321
|
-
const unsigned = { ...identity, envelopeCid, shareCid };
|
|
8322
|
-
const signatureBytes = fromBase64Url(requiredString(signature.value, "outer signature"));
|
|
8323
|
-
if (signatureBytes.length !== 64 || !import_ed255196.ed25519.verify(
|
|
8324
|
-
signatureBytes,
|
|
8325
|
-
new TextEncoder().encode(`${ENVELOPE_DOMAIN2}${canonicalize(unsigned)}`),
|
|
8326
|
-
ed25519PublicKeyFromDidKey(envelope.signature.signerDid),
|
|
8327
|
-
{ zip215: false }
|
|
8328
|
-
)) return void 0;
|
|
8329
|
-
return { envelopeCid, shareCid };
|
|
8330
|
-
} catch {
|
|
8331
|
-
return void 0;
|
|
8332
|
-
}
|
|
8333
|
-
}
|
|
8334
|
-
function createRegisteredPolicyAuthority(options) {
|
|
8335
|
-
const proofKey = options.nodeProof.publicKey.slice();
|
|
7481
|
+
function restoreDelegatedShareKey(input) {
|
|
7482
|
+
let cleared = false;
|
|
8336
7483
|
return {
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
delegation: {
|
|
8349
|
-
delegateDID: envelope.signature.signerDid,
|
|
8350
|
-
spaceId: envelope.target.spaceId,
|
|
8351
|
-
path: envelope.resource.path,
|
|
8352
|
-
actions: envelope.actions,
|
|
8353
|
-
expiry: new Date(envelope.expiry)
|
|
8354
|
-
}
|
|
8355
|
-
};
|
|
8356
|
-
const receipt = validateOwnerSharePolicyRegistration(ownerAuthority.registrationReceipt, {
|
|
8357
|
-
policy: { bytes: policyBytes, cid: policyCid, proof: "" },
|
|
8358
|
-
ownerDelegation,
|
|
8359
|
-
enforcementDelegation: enforcement,
|
|
8360
|
-
contentSourceDigest: envelope.contentSourceDigest,
|
|
8361
|
-
nodeProof: { kid: options.nodeProof.kid, publicKey: proofKey }
|
|
8362
|
-
});
|
|
8363
|
-
const registration = receipt.registration;
|
|
8364
|
-
const outer = await verifyOuterEnvelope(ownerAuthority.outerEnvelope, envelope, policyCid, options.expectedTarget.enforcerDid, registration.actions);
|
|
8365
|
-
if (outer === void 0 || ownerAuthority.registrationCid !== registration.registrationCid || ownerAuthority.envelopeCid !== outer.envelopeCid || ownerAuthority.shareCid !== outer.shareCid || envelope.authorityMaterialHandle !== registration.registrationCid || envelope.signature.signerDid !== registration.shareKeyDid || registration.target.origin !== options.expectedTarget.origin || registration.target.nodeAudience !== options.expectedTarget.nodeAudience || registration.enforcerDid !== options.expectedTarget.enforcerDid || enforcement.issuerDid !== registration.shareKeyDid || enforcement.audienceDid !== registration.enforcerDid) return void 0;
|
|
8366
|
-
return {
|
|
8367
|
-
policyCid: registration.policyCid,
|
|
8368
|
-
signerDid: registration.shareKeyDid,
|
|
8369
|
-
registrationCid: registration.registrationCid,
|
|
8370
|
-
shareId: registration.shareId,
|
|
8371
|
-
recipientMatcher: registration.recipientMatcher,
|
|
8372
|
-
target: registration.target,
|
|
8373
|
-
resource: registration.resource,
|
|
8374
|
-
actions: registration.actions,
|
|
8375
|
-
contentSource: registration.contentSource,
|
|
8376
|
-
contentSourceDigest: registration.contentSourceDigest,
|
|
8377
|
-
delegationCid: registration.ownerDelegationCid,
|
|
8378
|
-
authorityMaterialHandle: registration.registrationCid,
|
|
8379
|
-
authorityMaterialDigest: envelope.authorityMaterialDigest,
|
|
8380
|
-
expiresAt: registration.expiresAt
|
|
8381
|
-
};
|
|
8382
|
-
} catch {
|
|
8383
|
-
return void 0;
|
|
8384
|
-
}
|
|
7484
|
+
did: didKeyFromEd25519PublicKey2(input.publicKey),
|
|
7485
|
+
publicKey: bytes(input.publicKey),
|
|
7486
|
+
extractable: input.extractable,
|
|
7487
|
+
cryptoKey: input.privateKey,
|
|
7488
|
+
async sign(signBytes) {
|
|
7489
|
+
if (cleared) throw new Error("share key has been cleared");
|
|
7490
|
+
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7491
|
+
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, input.privateKey, signBytes));
|
|
7492
|
+
},
|
|
7493
|
+
clear() {
|
|
7494
|
+
cleared = true;
|
|
8385
7495
|
}
|
|
8386
7496
|
};
|
|
8387
7497
|
}
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
7498
|
+
async function createPolicyEnforcementDelegation(input) {
|
|
7499
|
+
if (input.shareKey.did.length === 0 || input.ownerDelegation.delegationCid.length === 0) throw new Error("owner delegation and share key are required");
|
|
7500
|
+
const facts = {
|
|
7501
|
+
ownerDelegationCid: input.ownerDelegation.delegationCid,
|
|
7502
|
+
policyCid: input.policyCid,
|
|
7503
|
+
shareId: input.shareId,
|
|
7504
|
+
shareKeyDid: input.shareKey.did,
|
|
7505
|
+
enforcerDid: input.enforcerDid,
|
|
7506
|
+
nodeAudience: input.nodeAudience,
|
|
7507
|
+
spaceId: input.spaceId,
|
|
7508
|
+
path: input.path,
|
|
7509
|
+
actions: [...input.actions],
|
|
7510
|
+
contentSourceDigest: input.contentSourceDigest,
|
|
7511
|
+
expiresAt: input.expiresAt
|
|
7512
|
+
};
|
|
7513
|
+
const unsigned = { type: "TinyCloudSharePolicyEnforcement", version: 2, issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts };
|
|
7514
|
+
const dagCborBytes = dagCborEncode({ domain: POLICY_ENFORCEMENT_DOMAIN, unsigned });
|
|
7515
|
+
const signature = b64(await input.shareKey.sign(dagCborBytes));
|
|
7516
|
+
return { cid: cid(dagCborBytes), dagCbor: b64(dagCborBytes), issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts, signature };
|
|
8394
7517
|
}
|
|
8395
|
-
function
|
|
8396
|
-
|
|
7518
|
+
async function canonicalOwnerSharePolicy(policy) {
|
|
7519
|
+
const text = canonicalize({ domain: POLICY_DOMAIN, policy });
|
|
7520
|
+
const policyBytes = new TextEncoder().encode(text);
|
|
7521
|
+
return { bytes: policyBytes, cid: cid(policyBytes), digest: digest2(policyBytes) };
|
|
8397
7522
|
}
|
|
8398
|
-
function
|
|
8399
|
-
|
|
8400
|
-
return left.every((byte, index) => byte === right[index]);
|
|
7523
|
+
function computeOwnerShareRegistrationCid(registration) {
|
|
7524
|
+
return cid(new TextEncoder().encode(canonicalize(registration)));
|
|
8401
7525
|
}
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
const entry = this.entries.get(mapKey);
|
|
8410
|
-
if (entry === void 0) return void 0;
|
|
8411
|
-
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
|
|
8412
|
-
this.entries.delete(mapKey);
|
|
8413
|
-
return void 0;
|
|
8414
|
-
}
|
|
8415
|
-
return entry.bytes.slice();
|
|
8416
|
-
}
|
|
8417
|
-
async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
|
|
8418
|
-
const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
|
|
8419
|
-
for (const entry of entries) {
|
|
8420
|
-
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
|
|
8421
|
-
this.entries.delete(cacheKey(entry.key));
|
|
8422
|
-
continue;
|
|
8423
|
-
}
|
|
8424
|
-
return {
|
|
8425
|
-
key: { ...entry.key },
|
|
8426
|
-
bytes: entry.bytes.slice(),
|
|
8427
|
-
digest: entry.digest.slice(),
|
|
8428
|
-
metadata: { ...entry.metadata }
|
|
8429
|
-
};
|
|
8430
|
-
}
|
|
8431
|
-
return void 0;
|
|
8432
|
-
}
|
|
8433
|
-
async set(key, bytes3, metadata) {
|
|
8434
|
-
if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
|
|
8435
|
-
this.entries.set(cacheKey(key), {
|
|
8436
|
-
key: { cid: key.cid, expiresAt: key.expiresAt },
|
|
8437
|
-
bytes: bytes3.slice(),
|
|
8438
|
-
digest: digest2(bytes3),
|
|
8439
|
-
metadata: { ...metadata }
|
|
8440
|
-
});
|
|
8441
|
-
}
|
|
8442
|
-
async delete(key) {
|
|
8443
|
-
this.entries.delete(cacheKey(key));
|
|
8444
|
-
}
|
|
8445
|
-
async clear() {
|
|
8446
|
-
this.entries.clear();
|
|
8447
|
-
}
|
|
8448
|
-
};
|
|
8449
|
-
|
|
8450
|
-
// src/delivery.ts
|
|
8451
|
-
var import_ed255197 = require("@noble/curves/ed25519");
|
|
8452
|
-
var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
|
|
8453
|
-
function isCanonicalHttpsOrigin2(value) {
|
|
8454
|
-
let url;
|
|
7526
|
+
function validateOwnerSharePolicyRegistration(value, expected) {
|
|
7527
|
+
if (cid(expected.policy.bytes) !== expected.policy.cid) throw new Error("submitted owner-share policy bytes do not match its CID");
|
|
7528
|
+
const root = assertObject(value, ["registration", "proof"], "owner-share registration response");
|
|
7529
|
+
const registration = assertObject(root.registration, ["registrationCid", "policyCid", "ownerDelegationCid", "enforcementDelegationCid", "ownerDid", "shareKeyDid", "enforcerDid", "shareId", "recipientMatcher", "target", "resource", "actions", "contentSource", "contentSourceDigest", "registeredAt", "expiresAt"], "owner-share registration");
|
|
7530
|
+
const proof = assertObject(root.proof, ["alg", "kid", "signature"], "owner-share registration proof");
|
|
7531
|
+
if (registration.policyCid !== expected.policy.cid || registration.ownerDelegationCid !== expected.ownerDelegation.delegationCid || registration.enforcementDelegationCid !== expected.enforcementDelegation.cid || registration.contentSourceDigest !== expected.contentSourceDigest) throw new Error("owner-share registration is not bound to the submitted chain");
|
|
7532
|
+
let policyValue;
|
|
8455
7533
|
try {
|
|
8456
|
-
|
|
7534
|
+
policyValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(expected.policy.bytes));
|
|
8457
7535
|
} catch {
|
|
8458
|
-
|
|
8459
|
-
}
|
|
8460
|
-
return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
8461
|
-
}
|
|
8462
|
-
var AUTHORIZATION_KEYS = [
|
|
8463
|
-
"type",
|
|
8464
|
-
"version",
|
|
8465
|
-
"jti",
|
|
8466
|
-
"shareCid",
|
|
8467
|
-
"shareId",
|
|
8468
|
-
"registrationCid",
|
|
8469
|
-
"delegationCid",
|
|
8470
|
-
"enforcementDelegationCid",
|
|
8471
|
-
"envelopeCid",
|
|
8472
|
-
"policyCid",
|
|
8473
|
-
"nodeAudience",
|
|
8474
|
-
"targetOrigin",
|
|
8475
|
-
"openCredentialsAudience",
|
|
8476
|
-
"holder",
|
|
8477
|
-
"recipientMatcher",
|
|
8478
|
-
"deliveryEmail",
|
|
8479
|
-
"shareUrl",
|
|
8480
|
-
"returnOrigin",
|
|
8481
|
-
"documentName",
|
|
8482
|
-
"senderDid",
|
|
8483
|
-
"senderTrust",
|
|
8484
|
-
"contentSource",
|
|
8485
|
-
"contentSourceDigest",
|
|
8486
|
-
"shareExpiresAt",
|
|
8487
|
-
"issuedAt",
|
|
8488
|
-
"reportAbuseToken",
|
|
8489
|
-
"actions",
|
|
8490
|
-
"resource",
|
|
8491
|
-
"authorityMaterialHandle",
|
|
8492
|
-
"authorityMaterialDigest",
|
|
8493
|
-
"requestBodyDigest",
|
|
8494
|
-
"idempotencyKey",
|
|
8495
|
-
"expiresAt",
|
|
8496
|
-
"dataAuthority"
|
|
8497
|
-
];
|
|
8498
|
-
function assertObject2(value, keys, label) {
|
|
8499
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8500
|
-
const record2 = value;
|
|
8501
|
-
if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
|
|
8502
|
-
return record2;
|
|
8503
|
-
}
|
|
8504
|
-
function fromB642(value) {
|
|
8505
|
-
if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
|
|
8506
|
-
if (typeof atob === "function") {
|
|
8507
|
-
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
8508
|
-
return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
7536
|
+
throw new Error("owner-share policy is not valid JSON");
|
|
8509
7537
|
}
|
|
8510
|
-
|
|
8511
|
-
throw new Error("
|
|
7538
|
+
const policy = policyValue.policy;
|
|
7539
|
+
if (policyValue.domain !== "xyz.tinycloud.share/policy/v2\0" || policy === void 0 || Array.isArray(policy)) throw new Error("owner-share policy envelope is invalid");
|
|
7540
|
+
const target = policy.target;
|
|
7541
|
+
const resource = policy.resource;
|
|
7542
|
+
const registrationTarget = registration.target;
|
|
7543
|
+
const registrationResource = registration.resource;
|
|
7544
|
+
const enforcementFacts = expected.enforcementDelegation.facts;
|
|
7545
|
+
if (typeof target?.origin !== "string" || typeof target.nodeAudience !== "string" || typeof target.enforcerDid !== "string" || typeof target.spaceId !== "string" || typeof resource?.kind !== "string" || !["exact", "prefix"].includes(resource.kind) || typeof resource?.path !== "string" || !Array.isArray(policy.actions) || policy.ownerDid !== registration.ownerDid || policy.shareKeyDid !== registration.shareKeyDid || policy.shareId !== registration.shareId || canonicalize(policy.recipientMatcher) !== canonicalize(registration.recipientMatcher) || target.origin !== registrationTarget.origin || target.nodeAudience !== registrationTarget.nodeAudience || target.enforcerDid !== registration.enforcerDid || target.spaceId !== registrationTarget.spaceId || resource.kind !== registrationResource.kind || resource.path !== registrationResource.path || canonicalize(policy.actions) !== canonicalize(registration.actions) || canonicalize(policy.contentSource) !== canonicalize(registration.contentSource) || policy.expiresAt !== registration.expiresAt || policy.contentSourceDigest !== registration.contentSourceDigest || enforcementFacts.ownerDelegationCid !== registration.ownerDelegationCid || enforcementFacts.policyCid !== registration.policyCid || enforcementFacts.shareKeyDid !== registration.shareKeyDid || enforcementFacts.enforcerDid !== registration.enforcerDid || enforcementFacts.nodeAudience !== registrationTarget.nodeAudience || enforcementFacts.spaceId !== registrationTarget.spaceId || enforcementFacts.path !== registrationResource.path || canonicalize(enforcementFacts.actions) !== canonicalize(registration.actions) || enforcementFacts.expiresAt !== registration.expiresAt) throw new Error("owner-share registration is not bound to the canonical policy");
|
|
7546
|
+
if (typeof registration.registrationCid !== "string" || typeof registration.expiresAt !== "string" || typeof registration.registeredAt !== "string") throw new Error("owner-share registration timestamps are invalid");
|
|
7547
|
+
if (new Date(registration.expiresAt).toISOString() !== registration.expiresAt || Date.parse(registration.expiresAt) <= Date.now()) throw new Error("owner-share registration is expired or non-canonical");
|
|
7548
|
+
const { registrationCid: _registrationCid, ...registrationCore } = registration;
|
|
7549
|
+
if (computeOwnerShareRegistrationCid(registrationCore) !== registration.registrationCid) throw new Error("owner-share registration CID does not match its canonical core");
|
|
7550
|
+
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("owner-share registration proof is invalid");
|
|
7551
|
+
const proofKey = expected.nodeProof;
|
|
7552
|
+
if (proofKey === void 0) throw new Error("owner-share registration proof trust material is required");
|
|
7553
|
+
if (proof.kid !== proofKey.kid) throw new Error("owner-share registration proof key is not trusted");
|
|
7554
|
+
const encodedKid = proofKey.publicKey;
|
|
7555
|
+
if (encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1) {
|
|
7556
|
+
} else if (encodedKid.length !== 32) throw new Error("owner-share registration proof key is invalid");
|
|
7557
|
+
const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
|
|
7558
|
+
const signatureBytes = fromB64(proof.signature);
|
|
7559
|
+
const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
|
|
7560
|
+
if (signatureBytes.length !== 64 || !import_ed255194.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
|
|
7561
|
+
return { registration, proof };
|
|
8512
7562
|
}
|
|
8513
|
-
function
|
|
7563
|
+
function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
8514
7564
|
let value;
|
|
8515
7565
|
try {
|
|
8516
7566
|
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
8517
7567
|
} catch {
|
|
8518
|
-
throw new Error("share
|
|
8519
|
-
}
|
|
8520
|
-
|
|
8521
|
-
const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
|
|
8522
|
-
const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
|
|
8523
|
-
const request = expected.request;
|
|
8524
|
-
if (authorization.type !== "TinyCloudShareDeliveryAuthorization" || authorization.version !== 2 || authorization.dataAuthority !== false || authorization.jti !== request.jti || authorization.shareCid !== request.shareCid || authorization.shareId !== request.shareId || authorization.registrationCid !== request.registrationCid || authorization.delegationCid !== request.delegationCid || authorization.enforcementDelegationCid !== request.enforcementDelegationCid || authorization.envelopeCid !== request.envelopeCid || authorization.policyCid !== request.policyCid || authorization.deliveryEmail !== request.recipientEmail || authorization.shareUrl !== request.shareUrl || authorization.documentName !== request.documentName || authorization.requestBodyDigest !== request.requestBodyDigest || authorization.expiresAt !== request.expiresAt || authorization.idempotencyKey !== request.idempotencyKey || authorization.reportAbuseToken !== request.jti || authorization.authorityMaterialHandle !== request.registrationCid) {
|
|
8525
|
-
throw new Error("share delivery authorization is not bound to the submitted request");
|
|
8526
|
-
}
|
|
8527
|
-
const now = Date.now();
|
|
8528
|
-
const issuedAt = typeof authorization.issuedAt === "string" ? Date.parse(authorization.issuedAt) : Number.NaN;
|
|
8529
|
-
const expiresAt = typeof authorization.expiresAt === "string" ? Date.parse(authorization.expiresAt) : Number.NaN;
|
|
8530
|
-
if (!Number.isFinite(issuedAt) || issuedAt > now + 6e4 || issuedAt > expiresAt) throw new Error("share delivery authorization issuedAt is invalid");
|
|
8531
|
-
if (!Number.isFinite(expiresAt) || expiresAt <= now) throw new Error("share delivery authorization is expired");
|
|
8532
|
-
if (new Date(expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
|
|
8533
|
-
if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin2(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
|
|
8534
|
-
throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
|
|
8535
|
-
}
|
|
8536
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
|
|
8537
|
-
if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
|
|
8538
|
-
const encodedKid = expected.nodeProof.publicKey;
|
|
8539
|
-
const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
|
|
8540
|
-
if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
|
|
8541
|
-
const signatureBytes = fromB642(proof.signature);
|
|
8542
|
-
const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalize(authorization)}`);
|
|
8543
|
-
if (signatureBytes.length !== 64 || !import_ed255197.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
|
|
8544
|
-
return { authorization, proof };
|
|
8545
|
-
}
|
|
8546
|
-
function shareDeliveryTrustedKid(publicKey) {
|
|
8547
|
-
if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
|
|
8548
|
-
const prefixed = new Uint8Array(34);
|
|
8549
|
-
prefixed.set([237, 1]);
|
|
8550
|
-
prefixed.set(publicKey, 2);
|
|
8551
|
-
const did = `did:key:${base58btc2.encode(prefixed)}`;
|
|
8552
|
-
return `${did}#${did.slice("did:key:".length)}`;
|
|
7568
|
+
throw new Error("owner-share registration response is not valid UTF-8 JSON");
|
|
7569
|
+
}
|
|
7570
|
+
return validateOwnerSharePolicyRegistration(value, expected);
|
|
8553
7571
|
}
|
|
8554
7572
|
|
|
8555
7573
|
// src/history.ts
|
|
@@ -8595,13 +7613,14 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
|
|
|
8595
7613
|
registeredAt: now.toISOString(),
|
|
8596
7614
|
expiresAt: result.metadata.expiresAt,
|
|
8597
7615
|
link: result.url,
|
|
7616
|
+
...result.deliveryMaterial === void 0 ? {} : { deliveryMaterial: result.deliveryMaterial },
|
|
8598
7617
|
...result.metadata.display.filename === void 0 ? {} : { filename: result.metadata.display.filename }
|
|
8599
7618
|
};
|
|
8600
7619
|
}
|
|
8601
7620
|
var MemorySenderShareRecordStorage = class {
|
|
8602
7621
|
records = /* @__PURE__ */ new Map();
|
|
8603
|
-
async put(
|
|
8604
|
-
this.records.set(
|
|
7622
|
+
async put(record) {
|
|
7623
|
+
this.records.set(record.shareId, record);
|
|
8605
7624
|
}
|
|
8606
7625
|
async list() {
|
|
8607
7626
|
return [...this.records.values()];
|
|
@@ -8638,7 +7657,7 @@ var SenderShareStore = class {
|
|
|
8638
7657
|
async save(receipt, key) {
|
|
8639
7658
|
const registration = receipt.registration;
|
|
8640
7659
|
if (registration.shareKeyDid !== key.did) throw new Error("share key does not match the registered receipt");
|
|
8641
|
-
const
|
|
7660
|
+
const record = {
|
|
8642
7661
|
shareId: registration.shareId,
|
|
8643
7662
|
registrationCid: registration.registrationCid,
|
|
8644
7663
|
policyCid: registration.policyCid,
|
|
@@ -8655,15 +7674,15 @@ var SenderShareStore = class {
|
|
|
8655
7674
|
registeredAt: registration.registeredAt,
|
|
8656
7675
|
expiresAt: registration.expiresAt
|
|
8657
7676
|
};
|
|
8658
|
-
await this.records.put(
|
|
8659
|
-
await this.keys.put(
|
|
8660
|
-
return
|
|
7677
|
+
await this.records.put(record);
|
|
7678
|
+
await this.keys.put(record.shareId, { cryptoKey: key.cryptoKey, publicKey: key.publicKey, extractable: key.extractable });
|
|
7679
|
+
return record;
|
|
8661
7680
|
}
|
|
8662
7681
|
/** List shares created in any session, most recent first. */
|
|
8663
7682
|
async list(options) {
|
|
8664
7683
|
const now = this.now().getTime();
|
|
8665
7684
|
const records = await this.records.list();
|
|
8666
|
-
return records.filter((
|
|
7685
|
+
return records.filter((record) => (options?.includeRevoked ?? false) || record.revokedAt === void 0).filter((record) => (options?.includeExpired ?? false) || Date.parse(record.expiresAt) > now).sort((left, right) => Date.parse(right.registeredAt) - Date.parse(left.registeredAt));
|
|
8667
7686
|
}
|
|
8668
7687
|
async get(shareId) {
|
|
8669
7688
|
return this.records.get(shareId);
|
|
@@ -8686,17 +7705,17 @@ var SenderShareStore = class {
|
|
|
8686
7705
|
* returned CID, then call {@link markRevoked} once that succeeds.
|
|
8687
7706
|
*/
|
|
8688
7707
|
async revocationTarget(shareId, scope) {
|
|
8689
|
-
const
|
|
8690
|
-
if (
|
|
8691
|
-
const delegationCid = scope === "direct" ?
|
|
7708
|
+
const record = await this.records.get(shareId);
|
|
7709
|
+
if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7710
|
+
const delegationCid = scope === "direct" ? record.enforcementDelegationCid : record.ownerDelegationCid;
|
|
8692
7711
|
if (delegationCid === void 0) throw new Error("share has no node-enforced delegation");
|
|
8693
7712
|
return delegationCid;
|
|
8694
7713
|
}
|
|
8695
7714
|
/** Mark a share revoked locally after the caller's node-side revoke call succeeds. */
|
|
8696
7715
|
async markRevoked(shareId, revokedAt = this.now().toISOString()) {
|
|
8697
|
-
const
|
|
8698
|
-
if (
|
|
8699
|
-
const revoked = { ...
|
|
7716
|
+
const record = await this.records.get(shareId);
|
|
7717
|
+
if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7718
|
+
const revoked = { ...record, revokedAt };
|
|
8700
7719
|
await this.records.put(revoked);
|
|
8701
7720
|
return revoked;
|
|
8702
7721
|
}
|
|
@@ -8712,9 +7731,9 @@ var EncryptedSenderShareHistory = class {
|
|
|
8712
7731
|
this.key = key;
|
|
8713
7732
|
this.now = now;
|
|
8714
7733
|
}
|
|
8715
|
-
async put(
|
|
7734
|
+
async put(record) {
|
|
8716
7735
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
8717
|
-
const payload = new TextEncoder().encode(JSON.stringify(
|
|
7736
|
+
const payload = new TextEncoder().encode(JSON.stringify(record));
|
|
8718
7737
|
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, this.key, payload));
|
|
8719
7738
|
const encoded = new Uint8Array(iv.length + ciphertext.length);
|
|
8720
7739
|
encoded.set(iv);
|
|
@@ -8747,8 +7766,8 @@ var ShareNotifyError = class extends Error {
|
|
|
8747
7766
|
this.name = "ShareNotifyError";
|
|
8748
7767
|
}
|
|
8749
7768
|
};
|
|
8750
|
-
function recipientMatchesShareRecord(
|
|
8751
|
-
const matcher =
|
|
7769
|
+
function recipientMatchesShareRecord(record, recipient) {
|
|
7770
|
+
const matcher = record.recipientMatcher;
|
|
8752
7771
|
if (matcher.kind === "bearer" || matcher.kind === "recipientDid") return false;
|
|
8753
7772
|
if (matcher.kind === "exactEmail") return matcher.value === recipient;
|
|
8754
7773
|
const at = recipient.lastIndexOf("@");
|
|
@@ -8779,68 +7798,75 @@ async function notifyShare(input) {
|
|
|
8779
7798
|
}
|
|
8780
7799
|
async function defaultIdempotencyKey(shareId, recipient) {
|
|
8781
7800
|
const canonicalRecipient = canonicalize(recipient.trim().toLowerCase());
|
|
8782
|
-
const
|
|
7801
|
+
const digest3 = new Uint8Array(await crypto.subtle.digest(
|
|
8783
7802
|
"SHA-256",
|
|
8784
7803
|
new TextEncoder().encode(canonicalRecipient)
|
|
8785
7804
|
));
|
|
8786
|
-
return `tinycloud-share:${shareId}:${toBase64Url(
|
|
7805
|
+
return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
|
|
8787
7806
|
}
|
|
8788
7807
|
|
|
8789
7808
|
// src/lifecycle.ts
|
|
8790
|
-
function targetKind2(
|
|
8791
|
-
if (
|
|
8792
|
-
return
|
|
7809
|
+
function targetKind2(record) {
|
|
7810
|
+
if (record.targetKind !== void 0) return record.targetKind;
|
|
7811
|
+
return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
|
|
7812
|
+
}
|
|
7813
|
+
function policyNodeAudience(record) {
|
|
7814
|
+
const envelope = record.deliveryMaterial?.envelope;
|
|
7815
|
+
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
|
|
7816
|
+
const binding = envelope.attestedEnforcerBinding;
|
|
7817
|
+
if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
|
|
7818
|
+
const nodeAudience = binding.nodeAudience;
|
|
7819
|
+
return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
|
|
8793
7820
|
}
|
|
8794
7821
|
async function revokeShare(input) {
|
|
8795
7822
|
const target = targetKind2(input.record);
|
|
8796
|
-
if (target === "bearer") return { state: "retention-only", target, reason: "bearer-capability-cannot-be-revoked" };
|
|
8797
7823
|
if (input.adapter === void 0) return { state: "unsupported", target, reason: "node revocation authority is required", code: "unsupported-target" };
|
|
8798
7824
|
const scope = input.scope ?? "direct";
|
|
8799
7825
|
const delegationCid = scope === "ancestor" ? input.record.ownerDelegationCid : input.record.enforcementDelegationCid;
|
|
8800
7826
|
if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
|
|
8801
|
-
|
|
7827
|
+
if (target === "bearer") {
|
|
7828
|
+
if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
|
|
7829
|
+
await input.adapter.revokeDelegation({ delegationCid, scope });
|
|
7830
|
+
} else {
|
|
7831
|
+
if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
|
|
7832
|
+
if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
|
|
7833
|
+
await input.adapter.revokePolicyRoot({
|
|
7834
|
+
rootCid: delegationCid,
|
|
7835
|
+
targetRole: scope === "ancestor" ? "policy-authority" : "policy-enforcement",
|
|
7836
|
+
ownerDid: input.record.ownerDid,
|
|
7837
|
+
nodeOrigin: input.record.target.origin,
|
|
7838
|
+
nodeAudience: policyNodeAudience(input.record)
|
|
7839
|
+
});
|
|
7840
|
+
}
|
|
8802
7841
|
const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
8803
7842
|
if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
|
|
8804
7843
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
8805
7844
|
}
|
|
8806
|
-
function redactRecord(
|
|
8807
|
-
const matcher =
|
|
8808
|
-
const revealedLink = revealLink ? link ??
|
|
7845
|
+
function redactRecord(record, revealLink, link) {
|
|
7846
|
+
const matcher = record.recipientMatcher;
|
|
7847
|
+
const revealedLink = revealLink ? link ?? record.link : void 0;
|
|
8809
7848
|
return {
|
|
8810
|
-
shareId:
|
|
7849
|
+
shareId: record.shareId,
|
|
8811
7850
|
target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
|
|
8812
7851
|
...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
|
|
8813
|
-
expiresAt:
|
|
8814
|
-
revoked:
|
|
7852
|
+
expiresAt: record.expiresAt,
|
|
7853
|
+
revoked: record.revokedAt !== void 0,
|
|
8815
7854
|
...revealedLink === void 0 ? {} : { link: revealedLink }
|
|
8816
7855
|
};
|
|
8817
7856
|
}
|
|
8818
7857
|
async function listShares(storage) {
|
|
8819
7858
|
const records = await storage.list();
|
|
8820
|
-
return records.map((
|
|
7859
|
+
return records.map((record) => redactRecord(record, false));
|
|
8821
7860
|
}
|
|
8822
7861
|
async function showShare(input) {
|
|
8823
|
-
const
|
|
8824
|
-
if (
|
|
8825
|
-
return redactRecord(
|
|
8826
|
-
}
|
|
8827
|
-
|
|
8828
|
-
// src/legacy.ts
|
|
8829
|
-
function isLegacyShareLink(value) {
|
|
8830
|
-
return value.startsWith("tc1:");
|
|
8831
|
-
}
|
|
8832
|
-
async function receiveLegacyShare(link, reader) {
|
|
8833
|
-
if (!isLegacyShareLink(link)) throw new Error("legacy link must use tc1:");
|
|
8834
|
-
return reader.read(link);
|
|
8835
|
-
}
|
|
8836
|
-
async function migrateShare(input) {
|
|
8837
|
-
const value = await receiveLegacyShare(input.link, input.reader);
|
|
8838
|
-
return { protocol: "tinycloud-share", version: 1, legacy: true, value, migrated: await input.publish(value) };
|
|
7862
|
+
const record = await input.storage.get(input.shareId);
|
|
7863
|
+
if (record === void 0) throw new Error("share not found");
|
|
7864
|
+
return redactRecord(record, input.revealLink === true, input.link);
|
|
8839
7865
|
}
|
|
8840
7866
|
|
|
8841
7867
|
// src/recipient.ts
|
|
8842
|
-
var
|
|
8843
|
-
var
|
|
7868
|
+
var import_ed255195 = require("@noble/curves/ed25519");
|
|
7869
|
+
var import_sha2565 = require("@noble/hashes/sha256");
|
|
8844
7870
|
|
|
8845
7871
|
// src/protocol.ts
|
|
8846
7872
|
var SHARE_V2_PROTOCOL = Object.freeze({
|
|
@@ -8931,12 +7957,6 @@ function fromBase64(value, label) {
|
|
|
8931
7957
|
if (toBase64(out) !== value) throw new Error(`${label} is invalid`);
|
|
8932
7958
|
return out;
|
|
8933
7959
|
}
|
|
8934
|
-
async function digest3(value) {
|
|
8935
|
-
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalize(value)))));
|
|
8936
|
-
}
|
|
8937
|
-
async function digestText2(value) {
|
|
8938
|
-
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))));
|
|
8939
|
-
}
|
|
8940
7960
|
async function digestBytes2(value) {
|
|
8941
7961
|
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
|
|
8942
7962
|
}
|
|
@@ -8946,24 +7966,9 @@ function trustedPublicKey(value) {
|
|
|
8946
7966
|
}
|
|
8947
7967
|
return value.invitationPublicKey;
|
|
8948
7968
|
}
|
|
8949
|
-
async function verifyWrapped(value, key, domain, trust) {
|
|
8950
|
-
const wrapper = object2(value, `${key} response`);
|
|
8951
|
-
if (Object.keys(wrapper).length !== 2 || !Object.hasOwn(wrapper, key) || !Object.hasOwn(wrapper, "proof")) throw new Error(`${key} response is invalid`);
|
|
8952
|
-
const artifact = object2(wrapper[key], `${key} artifact`);
|
|
8953
|
-
const proof = object2(wrapper.proof, `${key} proof`);
|
|
8954
|
-
if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error(`${key} proof is invalid`);
|
|
8955
|
-
if (!import_ed255198.ed25519.verify(bytes2(proof.signature, `${key} signature`), new TextEncoder().encode(`${domain}${canonicalize(artifact)}`), trustedPublicKey(trust))) throw new Error(`${key} proof is invalid`);
|
|
8956
|
-
return artifact;
|
|
8957
|
-
}
|
|
8958
7969
|
function nativeAction(action) {
|
|
8959
7970
|
return action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : "tinycloud.kv/get";
|
|
8960
7971
|
}
|
|
8961
|
-
function uiAction(action) {
|
|
8962
|
-
return action === "tinycloud.kv/list" ? "list" : action === "tinycloud.kv/put" ? "edit" : "read";
|
|
8963
|
-
}
|
|
8964
|
-
function selectedAction(envelope) {
|
|
8965
|
-
return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
|
|
8966
|
-
}
|
|
8967
7972
|
function policyAttenuationForV3(envelope) {
|
|
8968
7973
|
const policy = object2(envelope.policy, "v3 policy");
|
|
8969
7974
|
if (!Array.isArray(policy.capabilityCeiling)) throw new Error("v3 policy capability ceiling is invalid");
|
|
@@ -9033,11 +8038,20 @@ function verifyV3PolicyAuthorization(input) {
|
|
|
9033
8038
|
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 - compact.payload.nbf > 60 || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
|
|
9034
8039
|
return compact;
|
|
9035
8040
|
}
|
|
9036
|
-
function
|
|
8041
|
+
function hex3(value) {
|
|
9037
8042
|
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
9038
8043
|
}
|
|
8044
|
+
function matchesInitialCiphertextDigest(bytes3, expected) {
|
|
8045
|
+
if (typeof expected !== "string" || !LOWER_SHA256_HEX.test(expected)) return false;
|
|
8046
|
+
const actual = (0, import_sha2565.sha256)(bytes3);
|
|
8047
|
+
let mismatch = 0;
|
|
8048
|
+
for (let index = 0; index < actual.length; index += 1) {
|
|
8049
|
+
mismatch |= actual[index] ^ Number.parseInt(expected.slice(index * 2, index * 2 + 2), 16);
|
|
8050
|
+
}
|
|
8051
|
+
return mismatch === 0;
|
|
8052
|
+
}
|
|
9039
8053
|
function canonicalHashHex(value) {
|
|
9040
|
-
return
|
|
8054
|
+
return hex3((0, import_sha2565.sha256)(new TextEncoder().encode(canonicalize(value))));
|
|
9041
8055
|
}
|
|
9042
8056
|
async function aesGcmDecrypt(key, blob) {
|
|
9043
8057
|
if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
|
|
@@ -9062,31 +8076,12 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
|
|
|
9062
8076
|
throw new Error("encrypted content envelope is malformed");
|
|
9063
8077
|
}
|
|
9064
8078
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
|
|
9065
|
-
const
|
|
8079
|
+
const record = value;
|
|
9066
8080
|
const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
|
|
9067
|
-
if (Object.keys(
|
|
8081
|
+
if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== 1 || record.networkId !== expected.encryptionNetwork || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== expected.contentSource.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || record.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.metadata !== void 0 && (record.metadata === null || typeof record.metadata !== "object" || Array.isArray(record.metadata) || Object.values(record.metadata).some((entry) => typeof entry !== "string"))) {
|
|
9068
8082
|
throw new Error("encrypted content envelope binding is invalid");
|
|
9069
8083
|
}
|
|
9070
|
-
return
|
|
9071
|
-
}
|
|
9072
|
-
async function verifyDetachedResponse(response, trust) {
|
|
9073
|
-
let value;
|
|
9074
|
-
try {
|
|
9075
|
-
value = await response.clone().json();
|
|
9076
|
-
} catch {
|
|
9077
|
-
throw new Error("share read response is invalid");
|
|
9078
|
-
}
|
|
9079
|
-
const record2 = object2(value, "share read response");
|
|
9080
|
-
const proof = object2(record2.proof, "share read detached proof");
|
|
9081
|
-
if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error("share read detached proof is invalid");
|
|
9082
|
-
const unsigned = { ...record2 };
|
|
9083
|
-
delete unsigned.proof;
|
|
9084
|
-
if (!import_ed255198.ed25519.verify(bytes2(proof.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(trust))) throw new Error("share read detached proof is invalid");
|
|
9085
|
-
}
|
|
9086
|
-
async function post(fetchFn, origin, path, body) {
|
|
9087
|
-
const response = await fetchFn(new URL(path, origin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
9088
|
-
if (!response.ok) throw new Error("share authority rejected the request");
|
|
9089
|
-
return response.json();
|
|
8084
|
+
return record;
|
|
9090
8085
|
}
|
|
9091
8086
|
var ShareRecipientClient = class {
|
|
9092
8087
|
constructor(options) {
|
|
@@ -9118,72 +8113,12 @@ var ShareRecipientClient = class {
|
|
|
9118
8113
|
v3ContentKey;
|
|
9119
8114
|
v3ContentEnvelope;
|
|
9120
8115
|
async beginChallenge(envelope) {
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
9124
|
-
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
9125
|
-
const target = object2(outer.target, "owner authority target");
|
|
9126
|
-
const outerResource = object2(outer.resource, "owner authority resource");
|
|
9127
|
-
const source = object2(outer.contentSource, "owner authority content source");
|
|
9128
|
-
if (!Array.isArray(outer.actions) || outer.actions.length === 0 || outer.actions.some((value) => typeof value !== "string")) throw new Error("owner authority outer envelope actions are invalid");
|
|
9129
|
-
const actions = [...outer.actions];
|
|
9130
|
-
const action = selectedAction(envelope);
|
|
9131
|
-
const challengeBody = { envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), enforcementDelegation: enforcement, outerEnvelope: outer, contentSource: source, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, targetOrigin: String(target.origin), nodeAudience: String(target.nodeAudience), action, actions, resource: String(outerResource.path) };
|
|
9132
|
-
const requestBodyDigest = await digest3(challengeBody);
|
|
9133
|
-
const challenge = await verifyWrapped(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/challenges", { ...challengeBody, requestBodyDigest }), "challenge", DOMAIN, this.options.trustedNode);
|
|
9134
|
-
if (challenge.type !== "TinyCloudSharePolicyChallenge" || challenge.version !== 2 || challenge.challengeId.length < 16 || challenge.nonce.length < 16 || challenge.shareCid !== authority.shareCid || challenge.shareId !== envelope.shareId || challenge.registrationCid !== authority.registrationCid || challenge.envelopeCid !== authority.envelopeCid || challenge.policyCid !== challengeBody.policyCid || challenge.delegationCid !== envelope.delegationCid || challenge.enforcementDelegationCid !== enforcement.cid || canonicalize(challenge.contentSource) !== canonicalize(source) || challenge.contentSourceDigest !== challengeBody.contentSourceDigest || challenge.requestBodyDigest !== requestBodyDigest || challenge.holderDid !== this.options.holderDid || challenge.targetOrigin !== challengeBody.targetOrigin || challenge.nodeAudience !== challengeBody.nodeAudience || challenge.action !== action || canonicalize(challenge.actions) !== canonicalize(actions) || challenge.resource !== challengeBody.resource || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("share authority returned an unbound challenge");
|
|
9135
|
-
return challenge;
|
|
8116
|
+
void envelope;
|
|
8117
|
+
throw new Error("v2 Share recipient transport has been removed; use the embedded policy v3 envelope");
|
|
9136
8118
|
}
|
|
9137
8119
|
async establish(envelope) {
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
9141
|
-
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
9142
|
-
const target = object2(outer.target, "owner authority target");
|
|
9143
|
-
const outerResource = object2(outer.resource, "owner authority resource");
|
|
9144
|
-
const source = object2(outer.contentSource, "owner authority content source");
|
|
9145
|
-
if (!Array.isArray(outer.actions) || outer.actions.length === 0 || outer.actions.some((value) => typeof value !== "string")) throw new Error("owner authority outer envelope actions are invalid");
|
|
9146
|
-
const actions = [...outer.actions];
|
|
9147
|
-
const action = selectedAction(envelope);
|
|
9148
|
-
const challengeBody = { envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), enforcementDelegation: enforcement, outerEnvelope: outer, contentSource: source, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, targetOrigin: String(target.origin), nodeAudience: String(target.nodeAudience), action, actions, resource: String(outerResource.path) };
|
|
9149
|
-
const requestBodyDigest = await digest3(challengeBody);
|
|
9150
|
-
const challenge = await verifyWrapped(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/challenges", { ...challengeBody, requestBodyDigest }), "challenge", DOMAIN, this.options.trustedNode);
|
|
9151
|
-
if (challenge.type !== "TinyCloudSharePolicyChallenge" || challenge.version !== 2 || challenge.challengeId === void 0 || challenge.nonce === void 0 || challenge.shareCid !== authority.shareCid || challenge.shareId !== envelope.shareId || challenge.registrationCid !== authority.registrationCid || challenge.envelopeCid !== authority.envelopeCid || challenge.policyCid !== challengeBody.policyCid || challenge.delegationCid !== envelope.delegationCid || challenge.enforcementDelegationCid !== enforcement.cid || challenge.requestBodyDigest !== requestBodyDigest || canonicalize(challenge.contentSource) !== canonicalize(source) || challenge.contentSourceDigest !== challengeBody.contentSourceDigest || challenge.holderDid !== this.options.holderDid || challenge.targetOrigin !== challengeBody.targetOrigin || challenge.nodeAudience !== challengeBody.nodeAudience || challenge.action !== action || canonicalize(challenge.actions) !== canonicalize(actions) || challenge.resource !== challengeBody.resource || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("share authority returned an unbound challenge");
|
|
9152
|
-
if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
|
|
9153
|
-
const material = await this.options.buildPresentation({ challenge, envelope, policy: {} });
|
|
9154
|
-
this.holderProof = material.proof;
|
|
9155
|
-
this.signer = material.sign;
|
|
9156
|
-
this.nativeSigner = material.sign;
|
|
9157
|
-
if (this.signer === void 0) throw new Error("share holder signer is required");
|
|
9158
|
-
const presentation = {
|
|
9159
|
-
type: "TinyCloudSharePolicyPresentation",
|
|
9160
|
-
version: 2,
|
|
9161
|
-
challengeId: challenge.challengeId,
|
|
9162
|
-
nonce: challenge.nonce,
|
|
9163
|
-
shareCid: authority.shareCid,
|
|
9164
|
-
shareId: envelope.shareId,
|
|
9165
|
-
delegationCid: envelope.delegationCid,
|
|
9166
|
-
policyCid: challengeBody.policyCid,
|
|
9167
|
-
contentSource: source,
|
|
9168
|
-
contentSourceDigest: challengeBody.contentSourceDigest,
|
|
9169
|
-
holderDid: material.holderDid,
|
|
9170
|
-
targetOrigin: challengeBody.targetOrigin,
|
|
9171
|
-
nodeAudience: challengeBody.nodeAudience,
|
|
9172
|
-
...challenge.enforcerDid === void 0 ? {} : { enforcerDid: challenge.enforcerDid },
|
|
9173
|
-
credentialDigest: material.credentialDigest ?? await digestText2(material.credential),
|
|
9174
|
-
action,
|
|
9175
|
-
actions,
|
|
9176
|
-
resource: challengeBody.resource,
|
|
9177
|
-
requestBodyDigest,
|
|
9178
|
-
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9179
|
-
expiresAt: challenge.expiresAt,
|
|
9180
|
-
jti: toBase64Url(crypto.getRandomValues(new Uint8Array(16)))
|
|
9181
|
-
};
|
|
9182
|
-
const presentationProof = { alg: "EdDSA", kid: `${material.holderDid}#${material.holderDid.slice("did:key:".length)}`, signature: toBase64Url(await this.signer(new TextEncoder().encode(`${PRESENTATION_DOMAIN}${canonicalize(presentation)}`))) };
|
|
9183
|
-
const session = await verifyWrapped(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/session", { challengeId: challenge.challengeId, nonce: challenge.nonce, presentation, credential: material.credential, proof: presentationProof, holderBinding: material.holderBinding, readSignerDid: material.holderDid }), "session", SESSION_DOMAIN, this.options.trustedNode);
|
|
9184
|
-
if (session.type !== "TinyCloudSharePolicySession" || session.version !== 2 || typeof session.sessionId !== "string" || session.shareCid !== authority.shareCid || session.shareId !== envelope.shareId || session.registrationCid !== authority.registrationCid || session.envelopeCid !== authority.envelopeCid || session.policyCid !== challengeBody.policyCid || session.delegationCid !== envelope.delegationCid || session.holderDid !== this.options.holderDid || session.targetOrigin !== challengeBody.targetOrigin || session.nodeAudience !== challengeBody.nodeAudience || session.action !== action || canonicalize(session.actions) !== canonicalize(actions) || canonicalize(session.contentSource) !== canonicalize(source) || session.contentSourceDigest !== challengeBody.contentSourceDigest || session.resource !== challengeBody.resource || typeof session.expiresAt !== "string" || !Number.isFinite(Date.parse(session.expiresAt)) || Date.parse(session.expiresAt) <= Date.now()) throw new Error("share authority returned an unbound session");
|
|
9185
|
-
this.session = { sessionId: session.sessionId, expiresAt: session.expiresAt, actions: actions.map(uiAction), resource: { kind: envelope.resource.kind, path: String(session.resource) } };
|
|
9186
|
-
return this.session;
|
|
8120
|
+
void envelope;
|
|
8121
|
+
throw new Error("v2 Share recipient transport has been removed; use the embedded policy v3 envelope");
|
|
9187
8122
|
}
|
|
9188
8123
|
async authorize(envelope) {
|
|
9189
8124
|
if (this.session === void 0) await this.establish(envelope);
|
|
@@ -9211,7 +8146,7 @@ var ShareRecipientClient = class {
|
|
|
9211
8146
|
} catch {
|
|
9212
8147
|
throw new Error("v3 attested Node audience is invalid");
|
|
9213
8148
|
}
|
|
9214
|
-
const challengeResponse = await this.fetchFn(new URL("/
|
|
8149
|
+
const challengeResponse = await this.fetchFn(new URL("/policy/v3/challenges", this.options.nodeOrigin), {
|
|
9215
8150
|
method: "POST",
|
|
9216
8151
|
redirect: "error",
|
|
9217
8152
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
@@ -9232,7 +8167,7 @@ var ShareRecipientClient = class {
|
|
|
9232
8167
|
throw new Error(accountless ? "v4 ceremony requires a verified credential and requirement" : "v3 ceremony requires a claim");
|
|
9233
8168
|
}
|
|
9234
8169
|
this.options.onStage?.("policy-admission");
|
|
9235
|
-
const delegationResponse = await this.fetchFn(new URL("/
|
|
8170
|
+
const delegationResponse = await this.fetchFn(new URL("/policy/v3/delegations", this.options.nodeOrigin), {
|
|
9236
8171
|
method: "POST",
|
|
9237
8172
|
redirect: "error",
|
|
9238
8173
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
@@ -9258,12 +8193,13 @@ var ShareRecipientClient = class {
|
|
|
9258
8193
|
const envelope = this.options.envelope;
|
|
9259
8194
|
const signer = this.nativeSigner ?? this.signer;
|
|
9260
8195
|
if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || this.v3NodeAudience === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
|
|
8196
|
+
if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
|
|
9261
8197
|
const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
|
|
9262
8198
|
const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
|
|
9263
|
-
const receiverPublicKey = toBase64(
|
|
8199
|
+
const receiverPublicKey = toBase64(import_ed255195.x25519.getPublicKey(receiverPrivateKey));
|
|
9264
8200
|
const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
|
|
9265
8201
|
const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: this.v3NodeAudience, networkId: encrypted.networkId, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
|
|
9266
|
-
const bodyHash =
|
|
8202
|
+
const bodyHash = hex3((0, import_sha2565.sha256)(new TextEncoder().encode(canonicalize(body))));
|
|
9267
8203
|
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
9268
8204
|
const now = Math.floor(Date.now() / 1e3);
|
|
9269
8205
|
const invocation = await signCompactUcanAuthorization({
|
|
@@ -9283,14 +8219,14 @@ var ShareRecipientClient = class {
|
|
|
9283
8219
|
if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
|
|
9284
8220
|
const value = object2(await response.json(), "v3 decrypt response");
|
|
9285
8221
|
const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
|
|
9286
|
-
if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !==
|
|
8222
|
+
if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex3((0, import_sha2565.sha256)(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
|
|
9287
8223
|
const unsigned = { ...value };
|
|
9288
8224
|
delete unsigned.nodeSignature;
|
|
9289
8225
|
const signature = fromBase64(value.nodeSignature, "v3 decrypt response signature");
|
|
9290
|
-
if (signature.length !== 64 || !
|
|
8226
|
+
if (signature.length !== 64 || !import_ed255195.ed25519.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
|
|
9291
8227
|
const wrapped = fromBase64(value.wrappedKey, "v3 wrapped content key");
|
|
9292
8228
|
if (wrapped.length < 61 || wrapped[32] !== 1) throw new Error("v3 wrapped content key is malformed");
|
|
9293
|
-
const shared =
|
|
8229
|
+
const shared = import_ed255195.x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
|
|
9294
8230
|
const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(33));
|
|
9295
8231
|
shared.fill(0);
|
|
9296
8232
|
if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
|
|
@@ -9309,47 +8245,16 @@ var ShareRecipientClient = class {
|
|
|
9309
8245
|
return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
|
|
9310
8246
|
}
|
|
9311
8247
|
async resumeWithProof(envelope, resumeToken, proof) {
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
const credential = material.credential;
|
|
9317
|
-
const holderDid = material.holderDid;
|
|
9318
|
-
const holderBinding = material.holderBinding;
|
|
9319
|
-
if (typeof nonce !== "string" || typeof credential !== "string" || typeof holderDid !== "string" || holderDid !== this.options.holderDid || typeof holderBinding !== "object" || holderBinding === null || presentationProof.alg !== "EdDSA" || typeof presentationProof.signature !== "string") throw new Error("share authorization proof is incomplete");
|
|
9320
|
-
const value = object2(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/session", { challengeId: resumeToken, nonce, presentation, credential, proof: presentationProof, holderBinding, readSignerDid: holderDid }), "share policy session");
|
|
9321
|
-
const session = await verifyWrapped(value, "session", SESSION_DOMAIN, this.options.trustedNode);
|
|
9322
|
-
const authority = envelope.ownerAuthority;
|
|
9323
|
-
if (authority === void 0 || session.type !== "TinyCloudSharePolicySession" || session.version !== 2 || typeof session.sessionId !== "string" || session.shareCid !== authority.shareCid || session.shareId !== envelope.shareId || session.registrationCid !== authority.registrationCid || session.envelopeCid !== authority.envelopeCid || session.delegationCid !== envelope.delegationCid || typeof session.resource !== "string" || typeof session.expiresAt !== "string") throw new Error("share authority returned an unbound session");
|
|
9324
|
-
const policyCid = envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "";
|
|
9325
|
-
const actions = [...new Set(envelope.actions.map(nativeAction))].sort();
|
|
9326
|
-
if (session.policyCid !== policyCid || session.holderDid !== this.options.holderDid || session.targetOrigin !== envelope.target.origin || session.nodeAudience !== envelope.target.nodeAudience || session.action !== selectedAction(envelope) || canonicalize(session.actions) !== canonicalize(actions) || canonicalize(session.contentSource) !== canonicalize(envelope.contentSource) || session.contentSourceDigest !== envelope.contentSourceDigest || !Number.isFinite(Date.parse(session.expiresAt)) || Date.parse(session.expiresAt) <= Date.now()) throw new Error("share authority returned an unbound session");
|
|
9327
|
-
this.session = { sessionId: session.sessionId, expiresAt: session.expiresAt, actions: actions.map(uiAction), resource: { kind: envelope.resource.kind, path: String(session.resource) } };
|
|
9328
|
-
this.holderProof = presentationProof;
|
|
9329
|
-
this.signer ??= this.options.sign;
|
|
9330
|
-
return this.authorize(envelope);
|
|
8248
|
+
void envelope;
|
|
8249
|
+
void resumeToken;
|
|
8250
|
+
void proof;
|
|
8251
|
+
throw new Error("v2 Share recipient transport has been removed; use the embedded policy v3 envelope");
|
|
9331
8252
|
}
|
|
9332
8253
|
async nativeInvoke(request) {
|
|
9333
8254
|
if (this.session === void 0) throw new Error("share policy session is required");
|
|
9334
8255
|
const envelope = this.options.envelope;
|
|
9335
8256
|
if (envelope.version === 3) return this.nativeInvokeV3(request, envelope);
|
|
9336
|
-
|
|
9337
|
-
const authority = envelope.ownerAuthority;
|
|
9338
|
-
const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
|
|
9339
|
-
const resource = typeof request.resource?.path === "string" ? request.resource.path : this.session.resource.path;
|
|
9340
|
-
const actions = [...new Set(this.session.actions.map(nativeAction).concat(action === "tinycloud.kv/metadata" ? [action] : []))].sort();
|
|
9341
|
-
const bodyBytes = request.body === void 0 ? void 0 : Uint8Array.from(request.body);
|
|
9342
|
-
const bodyDigest = bodyBytes === void 0 ? void 0 : toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", bodyBytes)));
|
|
9343
|
-
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
9344
|
-
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
9345
|
-
const invocationBase = { type: "TinyCloudShareReadInvocation", version: 2, sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, targetOrigin: String(object2(outer.target, "owner authority target").origin), nodeAudience: String(object2(outer.target, "owner authority target").nodeAudience), action, actions, resource, ...action === "tinycloud.kv/list" ? { limit: 100 } : {}, ...bodyDigest === void 0 ? {} : { bodyDigest, ifMatch: request.ifMatch, contentType: request.contentType }, issuedAt: (/* @__PURE__ */ new Date()).toISOString(), expiresAt: new Date(Math.min(Date.now() + 6e4, Date.parse(this.session.expiresAt))).toISOString(), jti: toBase64Url(crypto.getRandomValues(new Uint8Array(16))) };
|
|
9346
|
-
const requestBodyDigest = await digest3(invocationBase);
|
|
9347
|
-
const invocation = { ...invocationBase, requestBodyDigest };
|
|
9348
|
-
const proof = { ...this.holderProof, signature: toBase64Url(await this.signer(new TextEncoder().encode(`${INVOCATION_DOMAIN}${canonicalize(invocation)}`))) };
|
|
9349
|
-
const signedRequest = { sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, nodeAudience: String(object2(outer.target, "owner authority target").nodeAudience), action, actions, resource, requestBodyDigest, invocation, proof };
|
|
9350
|
-
const response = await this.fetchFn(new URL("/share/v2/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/vnd.tinycloud.share+json", "content-type": "application/vnd.tinycloud.share+json" }, body: JSON.stringify({ request: signedRequest, ...action === "tinycloud.kv/list" ? { limit: 100 } : {}, ...bodyBytes === void 0 ? {} : { body: toBase64Url(bodyBytes), bodyDigest, ifMatch: request.ifMatch, contentType: request.contentType } }) });
|
|
9351
|
-
if (response.ok) await verifyDetachedResponse(response, this.options.trustedNode);
|
|
9352
|
-
return response;
|
|
8257
|
+
throw new Error("v2 Share invocation transport has been removed; use the embedded policy v3 envelope");
|
|
9353
8258
|
}
|
|
9354
8259
|
async nativeInvokeV3(request, envelope) {
|
|
9355
8260
|
const signer = this.nativeSigner ?? this.signer;
|
|
@@ -9405,10 +8310,10 @@ function createAddressedAuthorization(input) {
|
|
|
9405
8310
|
const envelopeAction = nativeAction("read");
|
|
9406
8311
|
if (response.type !== "TinyCloudShareInvokeResponse" || response.version !== 2 || response.action !== envelopeAction || response.resource !== envelope.resource.path || typeof response.bodyDigest !== "string" || response.bodyDigest !== value.bodyDigest || response.bodyDigest !== await digestBytes2(value.bytes)) return false;
|
|
9407
8312
|
const detached = object2(wrapper.detached, "share read detached proof");
|
|
9408
|
-
if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
|
|
8313
|
+
if (input.trustedNode === void 0 || detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
|
|
9409
8314
|
const unsigned = { ...response };
|
|
9410
8315
|
delete unsigned.proof;
|
|
9411
|
-
return
|
|
8316
|
+
return import_ed255195.ed25519.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
|
|
9412
8317
|
} catch {
|
|
9413
8318
|
return false;
|
|
9414
8319
|
}
|
|
@@ -9440,6 +8345,37 @@ async function claimShare(input) {
|
|
|
9440
8345
|
async function resumeShareAuthorization(input) {
|
|
9441
8346
|
return authorizeShare(input);
|
|
9442
8347
|
}
|
|
8348
|
+
|
|
8349
|
+
// src/native.ts
|
|
8350
|
+
var NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
|
|
8351
|
+
function canonicalViewerUrl(viewerOrigin) {
|
|
8352
|
+
const url = new URL(viewerOrigin);
|
|
8353
|
+
if (url.protocol !== "https:" || url.origin !== viewerOrigin || url.pathname !== "/" || url.search || url.hash) throw new TypeError("viewer origin must be a canonical HTTPS origin");
|
|
8354
|
+
url.pathname = "/viewer";
|
|
8355
|
+
return url;
|
|
8356
|
+
}
|
|
8357
|
+
async function createNativeShare(sharing, input) {
|
|
8358
|
+
if (!input.path || input.path.startsWith("/") || input.path.endsWith("/") || input.path.includes("..") || input.path.includes("//")) throw new TypeError("native share path must be one canonical TinyCloud KV key");
|
|
8359
|
+
const generated = await sharing.generate({ path: input.path, actions: ["tinycloud.kv/get"], expiry: input.expiresAt });
|
|
8360
|
+
if (generated.ok !== true) throw new Error(typeof generated.error?.message === "string" ? generated.error.message : "TinyCloud sharing service rejected delegation generation");
|
|
8361
|
+
if (typeof generated.data?.token !== "string" || typeof generated.data.delegation?.cid !== "string" || !(generated.data.expiresAt instanceof Date)) throw new Error("TinyCloud sharing service returned incomplete delegation metadata");
|
|
8362
|
+
const delegation = sharing.decodeLink(generated.data.token);
|
|
8363
|
+
if (typeof delegation.spaceId !== "string" || delegation.spaceId.length === 0 || typeof delegation.path !== "string" || delegation.path.length === 0) throw new Error("TinyCloud sharing service returned a non-canonical delegation authority");
|
|
8364
|
+
const url = canonicalViewerUrl(input.viewerOrigin);
|
|
8365
|
+
url.hash = `${NATIVE_SHARE_FRAGMENT_PARAMETER}=${encodeURIComponent(generated.data.token)}`;
|
|
8366
|
+
return { url: url.toString(), delegationCid: generated.data.delegation.cid, expiresAt: generated.data.expiresAt, spaceId: delegation.spaceId };
|
|
8367
|
+
}
|
|
8368
|
+
function parseNativeShareUrl(value) {
|
|
8369
|
+
const url = new URL(value);
|
|
8370
|
+
if (url.search || url.pathname !== "/viewer") throw new TypeError("native shares must carry tc1 only in the /viewer URL fragment");
|
|
8371
|
+
const fragment = new URLSearchParams(url.hash.slice(1));
|
|
8372
|
+
const token = fragment.get(NATIVE_SHARE_FRAGMENT_PARAMETER);
|
|
8373
|
+
if (!token || fragment.size !== 1) throw new TypeError("missing native share fragment");
|
|
8374
|
+
return token;
|
|
8375
|
+
}
|
|
8376
|
+
async function openNativeShare(sharing, link) {
|
|
8377
|
+
return sharing.receive(parseNativeShareUrl(link), { autoSubdelegate: false, useSessionKey: false });
|
|
8378
|
+
}
|
|
9443
8379
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9444
8380
|
0 && (module.exports = {
|
|
9445
8381
|
DEFAULT_MAX_CONTENT_BLOB_BYTES,
|
|
@@ -9451,12 +8387,11 @@ async function resumeShareAuthorization(input) {
|
|
|
9451
8387
|
MemorySenderShareKeyStorage,
|
|
9452
8388
|
MemorySenderShareRecordStorage,
|
|
9453
8389
|
MemoryShareCache,
|
|
8390
|
+
NATIVE_SHARE_FRAGMENT_PARAMETER,
|
|
9454
8391
|
OWNER_SHARE_REGISTRATION_DOMAIN,
|
|
9455
8392
|
SHARE_CONTENT_LIMIT,
|
|
9456
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
9457
8393
|
SHARE_PUBLISH_RESULT_VERSION,
|
|
9458
8394
|
SHARE_RESULT_VERSION,
|
|
9459
|
-
SHARE_SEALED_OVERHEAD,
|
|
9460
8395
|
SHARE_V2_PROTOCOL,
|
|
9461
8396
|
SenderShareStore,
|
|
9462
8397
|
ShareNotifyError,
|
|
@@ -9472,23 +8407,21 @@ async function resumeShareAuthorization(input) {
|
|
|
9472
8407
|
computeOwnerShareRegistrationCid,
|
|
9473
8408
|
createAddressedAuthorization,
|
|
9474
8409
|
createDelegatedShareKey,
|
|
8410
|
+
createNativeShare,
|
|
9475
8411
|
createPolicyEnforcementDelegation,
|
|
9476
|
-
createRegisteredPolicyAuthority,
|
|
9477
8412
|
createShareV2HolderBindingArtifact,
|
|
9478
8413
|
fromBase64Url,
|
|
9479
8414
|
historyRecordForPublishedShare,
|
|
9480
8415
|
inspectShare,
|
|
9481
|
-
isLegacyShareLink,
|
|
9482
8416
|
listShares,
|
|
9483
|
-
migrateShare,
|
|
9484
8417
|
normalizeShareTarget,
|
|
9485
8418
|
notifyShare,
|
|
8419
|
+
openNativeShare,
|
|
9486
8420
|
parseAddressedEnvelope,
|
|
8421
|
+
parseNativeShareUrl,
|
|
9487
8422
|
publishAddressedShare,
|
|
9488
8423
|
publishPolicyShare,
|
|
9489
|
-
publishShare,
|
|
9490
8424
|
publishTargetShare,
|
|
9491
|
-
receiveLegacyShare,
|
|
9492
8425
|
receiveShare,
|
|
9493
8426
|
recipientMatchesShareRecord,
|
|
9494
8427
|
redactPublishedShare,
|
|
@@ -9497,14 +8430,11 @@ async function resumeShareAuthorization(input) {
|
|
|
9497
8430
|
restoreDelegatedShareKey,
|
|
9498
8431
|
resumeShareAuthorization,
|
|
9499
8432
|
revokeShare,
|
|
9500
|
-
shareDeliveryTrustedKid,
|
|
9501
8433
|
showShare,
|
|
9502
8434
|
targetAuthorizationMethod,
|
|
9503
8435
|
toBase64Url,
|
|
9504
8436
|
toShareErrorInfo,
|
|
9505
8437
|
validateOwnerSharePolicyRegistration,
|
|
9506
|
-
validateOwnerSharePolicyRegistrationBytes
|
|
9507
|
-
validateShareDeliveryAuthorizationBytes,
|
|
9508
|
-
verifyBearerEnvelope
|
|
8438
|
+
validateOwnerSharePolicyRegistrationBytes
|
|
9509
8439
|
});
|
|
9510
8440
|
//# sourceMappingURL=index.cjs.map
|