@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.js
CHANGED
|
@@ -4531,25 +4531,25 @@ function encodeTo(int, target, offset = 0) {
|
|
|
4531
4531
|
function encodingLength(int) {
|
|
4532
4532
|
return varint_default.encodingLength(int);
|
|
4533
4533
|
}
|
|
4534
|
-
function create(code22,
|
|
4535
|
-
const size =
|
|
4534
|
+
function create(code22, digest3) {
|
|
4535
|
+
const size = digest3.byteLength;
|
|
4536
4536
|
const sizeOffset = encodingLength(code22);
|
|
4537
4537
|
const digestOffset = sizeOffset + encodingLength(size);
|
|
4538
4538
|
const bytes3 = new Uint8Array(digestOffset + size);
|
|
4539
4539
|
encodeTo(code22, bytes3, 0);
|
|
4540
4540
|
encodeTo(size, bytes3, sizeOffset);
|
|
4541
|
-
bytes3.set(
|
|
4542
|
-
return new Digest(code22, size,
|
|
4541
|
+
bytes3.set(digest3, digestOffset);
|
|
4542
|
+
return new Digest(code22, size, digest3, bytes3);
|
|
4543
4543
|
}
|
|
4544
4544
|
function decode4(multihash) {
|
|
4545
4545
|
const bytes3 = coerce2(multihash);
|
|
4546
4546
|
const [code22, sizeOffset] = decode3(bytes3);
|
|
4547
4547
|
const [size, digestOffset] = decode3(bytes3.subarray(sizeOffset));
|
|
4548
|
-
const
|
|
4549
|
-
if (
|
|
4548
|
+
const digest3 = bytes3.subarray(sizeOffset + digestOffset);
|
|
4549
|
+
if (digest3.byteLength !== size) {
|
|
4550
4550
|
throw new Error("Incorrect length");
|
|
4551
4551
|
}
|
|
4552
|
-
return new Digest(code22, size,
|
|
4552
|
+
return new Digest(code22, size, digest3, bytes3);
|
|
4553
4553
|
}
|
|
4554
4554
|
function equals2(a, b) {
|
|
4555
4555
|
if (a === b) {
|
|
@@ -4567,10 +4567,10 @@ var Digest = class {
|
|
|
4567
4567
|
/**
|
|
4568
4568
|
* Creates a multihash digest.
|
|
4569
4569
|
*/
|
|
4570
|
-
constructor(code22, size,
|
|
4570
|
+
constructor(code22, size, digest3, bytes3) {
|
|
4571
4571
|
this.code = code22;
|
|
4572
4572
|
this.size = size;
|
|
4573
|
-
this.digest =
|
|
4573
|
+
this.digest = digest3;
|
|
4574
4574
|
this.bytes = bytes3;
|
|
4575
4575
|
}
|
|
4576
4576
|
};
|
|
@@ -4651,8 +4651,8 @@ var CID = class _CID {
|
|
|
4651
4651
|
toV1() {
|
|
4652
4652
|
switch (this.version) {
|
|
4653
4653
|
case 0: {
|
|
4654
|
-
const { code: code22, digest:
|
|
4655
|
-
const multihash = create(code22,
|
|
4654
|
+
const { code: code22, digest: digest3 } = this.multihash;
|
|
4655
|
+
const multihash = create(code22, digest3);
|
|
4656
4656
|
return _CID.createV1(this.code, multihash);
|
|
4657
4657
|
}
|
|
4658
4658
|
case 1: {
|
|
@@ -4706,8 +4706,8 @@ var CID = class _CID {
|
|
|
4706
4706
|
return new _CID(version, code22, multihash, bytes3 ?? encodeCID(version, code22, multihash.bytes));
|
|
4707
4707
|
} else if (value[cidSymbol] === true) {
|
|
4708
4708
|
const { version, multihash, code: code22 } = value;
|
|
4709
|
-
const
|
|
4710
|
-
return _CID.create(version, code22,
|
|
4709
|
+
const digest3 = decode4(multihash);
|
|
4710
|
+
return _CID.create(version, code22, digest3);
|
|
4711
4711
|
} else {
|
|
4712
4712
|
return null;
|
|
4713
4713
|
}
|
|
@@ -4717,11 +4717,11 @@ var CID = class _CID {
|
|
|
4717
4717
|
* @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
|
|
4718
4718
|
* @param digest - (Multi)hash of the of the content.
|
|
4719
4719
|
*/
|
|
4720
|
-
static create(version, code22,
|
|
4720
|
+
static create(version, code22, digest3) {
|
|
4721
4721
|
if (typeof code22 !== "number") {
|
|
4722
4722
|
throw new Error("String codecs are no longer supported");
|
|
4723
4723
|
}
|
|
4724
|
-
if (!(
|
|
4724
|
+
if (!(digest3.bytes instanceof Uint8Array)) {
|
|
4725
4725
|
throw new Error("Invalid digest");
|
|
4726
4726
|
}
|
|
4727
4727
|
switch (version) {
|
|
@@ -4729,12 +4729,12 @@ var CID = class _CID {
|
|
|
4729
4729
|
if (code22 !== DAG_PB_CODE) {
|
|
4730
4730
|
throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE}) block encoding`);
|
|
4731
4731
|
} else {
|
|
4732
|
-
return new _CID(version, code22,
|
|
4732
|
+
return new _CID(version, code22, digest3, digest3.bytes);
|
|
4733
4733
|
}
|
|
4734
4734
|
}
|
|
4735
4735
|
case 1: {
|
|
4736
|
-
const bytes3 = encodeCID(version, code22,
|
|
4737
|
-
return new _CID(version, code22,
|
|
4736
|
+
const bytes3 = encodeCID(version, code22, digest3.bytes);
|
|
4737
|
+
return new _CID(version, code22, digest3, bytes3);
|
|
4738
4738
|
}
|
|
4739
4739
|
default: {
|
|
4740
4740
|
throw new Error("Invalid version");
|
|
@@ -4744,8 +4744,8 @@ var CID = class _CID {
|
|
|
4744
4744
|
/**
|
|
4745
4745
|
* Simplified version of `create` for CIDv0.
|
|
4746
4746
|
*/
|
|
4747
|
-
static createV0(
|
|
4748
|
-
return _CID.create(0, DAG_PB_CODE,
|
|
4747
|
+
static createV0(digest3) {
|
|
4748
|
+
return _CID.create(0, DAG_PB_CODE, digest3);
|
|
4749
4749
|
}
|
|
4750
4750
|
/**
|
|
4751
4751
|
* Simplified version of `create` for CIDv1.
|
|
@@ -4753,8 +4753,8 @@ var CID = class _CID {
|
|
|
4753
4753
|
* @param code - Content encoding format code.
|
|
4754
4754
|
* @param digest - Multihash of the content.
|
|
4755
4755
|
*/
|
|
4756
|
-
static createV1(code22,
|
|
4757
|
-
return _CID.create(1, code22,
|
|
4756
|
+
static createV1(code22, digest3) {
|
|
4757
|
+
return _CID.create(1, code22, digest3);
|
|
4758
4758
|
}
|
|
4759
4759
|
/**
|
|
4760
4760
|
* Decoded a CID from its binary representation. The byte array must contain
|
|
@@ -4787,8 +4787,8 @@ var CID = class _CID {
|
|
|
4787
4787
|
throw new Error("Incorrect length");
|
|
4788
4788
|
}
|
|
4789
4789
|
const digestBytes3 = multihashBytes.subarray(specs.multihashSize - specs.digestSize);
|
|
4790
|
-
const
|
|
4791
|
-
const cid2 = specs.version === 0 ? _CID.createV0(
|
|
4790
|
+
const digest3 = new Digest(specs.multihashCode, specs.digestSize, digestBytes3, multihashBytes);
|
|
4791
|
+
const cid2 = specs.version === 0 ? _CID.createV0(digest3) : _CID.createV1(specs.codec, digest3);
|
|
4792
4792
|
return [cid2, bytes3.subarray(specs.size)];
|
|
4793
4793
|
}
|
|
4794
4794
|
/**
|
|
@@ -4911,8 +4911,8 @@ var cidSymbol = /* @__PURE__ */ Symbol.for("@ipld/js-cid/CID");
|
|
|
4911
4911
|
var code = 85;
|
|
4912
4912
|
var SHA256_CODE = 18;
|
|
4913
4913
|
async function computeCid(bytes3) {
|
|
4914
|
-
const
|
|
4915
|
-
return CID.create(1, code,
|
|
4914
|
+
const digest3 = create(SHA256_CODE, sha256(new Uint8Array(bytes3)));
|
|
4915
|
+
return CID.create(1, code, digest3).toString();
|
|
4916
4916
|
}
|
|
4917
4917
|
function isCanonicalRawCid(cidString) {
|
|
4918
4918
|
let cid2;
|
|
@@ -5359,7 +5359,7 @@ function validateV3Invariants(value, ctx) {
|
|
|
5359
5359
|
if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
|
|
5360
5360
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
|
|
5361
5361
|
}
|
|
5362
|
-
if (value.attestedEnforcerBinding.
|
|
5362
|
+
if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
5363
5363
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
5364
5364
|
}
|
|
5365
5365
|
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) {
|
|
@@ -5554,105 +5554,8 @@ function checkBearerDelegation(envelope, options = {}) {
|
|
|
5554
5554
|
}
|
|
5555
5555
|
return { ok: true, delegateeDid: audience };
|
|
5556
5556
|
}
|
|
5557
|
-
function mintBearerDelegation(options) {
|
|
5558
|
-
const { issuerPrivateKey, audienceDid, resourceUri, expiresAtSeconds } = options;
|
|
5559
|
-
if (!audienceDid.startsWith("did:")) {
|
|
5560
|
-
throw new TypeError(`audienceDid must be a DID, got ${audienceDid}`);
|
|
5561
|
-
}
|
|
5562
|
-
if (resourceUri.length === 0) {
|
|
5563
|
-
throw new TypeError("resourceUri must be non-empty");
|
|
5564
|
-
}
|
|
5565
|
-
if (!Number.isSafeInteger(expiresAtSeconds) || expiresAtSeconds <= 0) {
|
|
5566
|
-
throw new TypeError(
|
|
5567
|
-
`expiresAtSeconds must be a positive integer (epoch seconds), got ${expiresAtSeconds}`
|
|
5568
|
-
);
|
|
5569
|
-
}
|
|
5570
|
-
const issuerDid = didKeyFromEd25519PublicKey(
|
|
5571
|
-
ed25519.getPublicKey(issuerPrivateKey)
|
|
5572
|
-
);
|
|
5573
|
-
const header = { alg: "EdDSA", typ: "JWT", ucv: "0.9.1" };
|
|
5574
|
-
const payload = {
|
|
5575
|
-
iss: issuerDid,
|
|
5576
|
-
aud: audienceDid,
|
|
5577
|
-
att: [{ with: resourceUri, can: BEARER_READ_ABILITY }],
|
|
5578
|
-
prf: [],
|
|
5579
|
-
exp: expiresAtSeconds
|
|
5580
|
-
};
|
|
5581
|
-
const signingInput = `${toBase64Url(utf8Bytes(JSON.stringify(header)))}.${toBase64Url(
|
|
5582
|
-
utf8Bytes(JSON.stringify(payload))
|
|
5583
|
-
)}`;
|
|
5584
|
-
const signature = ed25519.sign(utf8Bytes(signingInput), issuerPrivateKey);
|
|
5585
|
-
return `${signingInput}.${toBase64Url(signature)}`;
|
|
5586
|
-
}
|
|
5587
5557
|
var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
|
|
5588
|
-
var SEALED_BLOB_VERSION = 1;
|
|
5589
5558
|
var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
|
|
5590
|
-
var KEY_LENGTH = 32;
|
|
5591
|
-
var NONCE_LENGTH = 12;
|
|
5592
|
-
var TAG_LENGTH = 16;
|
|
5593
|
-
var HEADER_LENGTH = 1;
|
|
5594
|
-
function assertKey(key32) {
|
|
5595
|
-
if (key32.length !== KEY_LENGTH) {
|
|
5596
|
-
throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
|
|
5597
|
-
}
|
|
5598
|
-
}
|
|
5599
|
-
async function importAesKey(key32, usage) {
|
|
5600
|
-
assertKey(key32);
|
|
5601
|
-
return globalThis.crypto.subtle.importKey(
|
|
5602
|
-
"raw",
|
|
5603
|
-
key32,
|
|
5604
|
-
"AES-GCM",
|
|
5605
|
-
false,
|
|
5606
|
-
[usage]
|
|
5607
|
-
);
|
|
5608
|
-
}
|
|
5609
|
-
function generateKey() {
|
|
5610
|
-
return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
|
5611
|
-
}
|
|
5612
|
-
async function seal(plaintextBytes, key32) {
|
|
5613
|
-
const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
|
|
5614
|
-
const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
|
|
5615
|
-
blob[0] = SEALED_BLOB_VERSION;
|
|
5616
|
-
blob.set(nonce, HEADER_LENGTH);
|
|
5617
|
-
blob.set(ciphertext, HEADER_LENGTH + nonce.length);
|
|
5618
|
-
return { blob, cid: await computeCid(blob) };
|
|
5619
|
-
}
|
|
5620
|
-
async function open(blob, key32) {
|
|
5621
|
-
if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
|
|
5622
|
-
throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
|
|
5623
|
-
}
|
|
5624
|
-
if (blob[0] !== SEALED_BLOB_VERSION) {
|
|
5625
|
-
throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
|
|
5626
|
-
}
|
|
5627
|
-
const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
|
|
5628
|
-
const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
|
|
5629
|
-
return decryptEnvelope(nonce, ciphertext, key32);
|
|
5630
|
-
}
|
|
5631
|
-
async function encryptEnvelope(plaintextBytes, key32) {
|
|
5632
|
-
const key = await importAesKey(key32, "encrypt");
|
|
5633
|
-
const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
|
5634
|
-
const ciphertext = new Uint8Array(
|
|
5635
|
-
await globalThis.crypto.subtle.encrypt(
|
|
5636
|
-
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
5637
|
-
key,
|
|
5638
|
-
plaintextBytes
|
|
5639
|
-
)
|
|
5640
|
-
);
|
|
5641
|
-
return { nonce, ciphertext };
|
|
5642
|
-
}
|
|
5643
|
-
async function decryptEnvelope(nonce, ciphertext, key32) {
|
|
5644
|
-
if (nonce.length !== NONCE_LENGTH) {
|
|
5645
|
-
throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
|
|
5646
|
-
}
|
|
5647
|
-
const key = await importAesKey(key32, "decrypt");
|
|
5648
|
-
return new Uint8Array(
|
|
5649
|
-
await globalThis.crypto.subtle.decrypt(
|
|
5650
|
-
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
5651
|
-
key,
|
|
5652
|
-
ciphertext
|
|
5653
|
-
)
|
|
5654
|
-
);
|
|
5655
|
-
}
|
|
5656
5559
|
function isPlainRecord(value) {
|
|
5657
5560
|
const proto = Object.getPrototypeOf(value);
|
|
5658
5561
|
return proto === Object.prototype || proto === null;
|
|
@@ -5783,8 +5686,6 @@ function equal(left, right) {
|
|
|
5783
5686
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
5784
5687
|
}
|
|
5785
5688
|
var ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
5786
|
-
var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
|
|
5787
|
-
var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
5788
5689
|
var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
5789
5690
|
var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
5790
5691
|
var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
@@ -5792,22 +5693,6 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
|
5792
5693
|
var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
5793
5694
|
var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
5794
5695
|
var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
5795
|
-
function signingBytes(unsigned) {
|
|
5796
|
-
const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
|
|
5797
|
-
const body = utf8Bytes(canonicalize(unsigned));
|
|
5798
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5799
|
-
bytes3.set(domain);
|
|
5800
|
-
bytes3.set(body, domain.length);
|
|
5801
|
-
return bytes3;
|
|
5802
|
-
}
|
|
5803
|
-
function signingBytesV2(unsigned) {
|
|
5804
|
-
const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
|
|
5805
|
-
const body = utf8Bytes(canonicalize(unsigned));
|
|
5806
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5807
|
-
bytes3.set(domain);
|
|
5808
|
-
bytes3.set(body, domain.length);
|
|
5809
|
-
return bytes3;
|
|
5810
|
-
}
|
|
5811
5696
|
function signingBytesV3(unsigned) {
|
|
5812
5697
|
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
5813
5698
|
const body = utf8Bytes(canonicalize(unsigned));
|
|
@@ -5816,53 +5701,6 @@ function signingBytesV3(unsigned) {
|
|
|
5816
5701
|
bytes3.set(body, domain.length);
|
|
5817
5702
|
return bytes3;
|
|
5818
5703
|
}
|
|
5819
|
-
function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
|
|
5820
|
-
const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
|
|
5821
|
-
const publicKey = ed255193.getPublicKey(ed25519PrivKey);
|
|
5822
|
-
const signature = ed255193.sign(signingBytes(unsigned), ed25519PrivKey);
|
|
5823
|
-
return {
|
|
5824
|
-
...unsigned,
|
|
5825
|
-
signature: {
|
|
5826
|
-
signerDid: didKeyFromEd25519PublicKey(publicKey),
|
|
5827
|
-
algorithm: "Ed25519",
|
|
5828
|
-
value: toBase64Url(signature)
|
|
5829
|
-
}
|
|
5830
|
-
};
|
|
5831
|
-
}
|
|
5832
|
-
function verifyEnvelopeSignatureOnly(envelope) {
|
|
5833
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
5834
|
-
const { signature, ...unsigned } = parsed;
|
|
5835
|
-
const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
|
|
5836
|
-
return ed255193.verify(
|
|
5837
|
-
fromBase64Url(signature.value),
|
|
5838
|
-
signingBytes(unsigned),
|
|
5839
|
-
publicKey,
|
|
5840
|
-
ED25519_VERIFY_OPTS2
|
|
5841
|
-
);
|
|
5842
|
-
}
|
|
5843
|
-
async function verifyEnvelope(envelope, options) {
|
|
5844
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
5845
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid) return false;
|
|
5846
|
-
if (!verifyEnvelopeSignatureOnly(parsed)) return false;
|
|
5847
|
-
if (parsed.authorizationTarget.kind === "policy") {
|
|
5848
|
-
const policyBytes = fromBase64Url(parsed.authorizationTarget.policyBytes);
|
|
5849
|
-
if (await computeCid(policyBytes) !== parsed.authorizationTarget.policyCid) {
|
|
5850
|
-
return false;
|
|
5851
|
-
}
|
|
5852
|
-
}
|
|
5853
|
-
return true;
|
|
5854
|
-
}
|
|
5855
|
-
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
5856
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5857
|
-
const { signature, ...unsigned } = parsed;
|
|
5858
|
-
return ed255193.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
5859
|
-
}
|
|
5860
|
-
async function verifyEnvelopeV2(envelope, options) {
|
|
5861
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5862
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
|
|
5863
|
-
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
5864
|
-
return true;
|
|
5865
|
-
}
|
|
5866
5704
|
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
5867
5705
|
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
5868
5706
|
const { signature, ...unsigned } = parsed;
|
|
@@ -5896,8 +5734,8 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
5896
5734
|
if (hex(sourceDigest) !== parsed.contentSourceDigestHex) return false;
|
|
5897
5735
|
const sortedCapabilities = [...policy.capabilityCeiling].sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
|
|
5898
5736
|
const capabilityCeilingHashHex = hex(sha2562(new TextEncoder().encode(`${POLICY_CAPABILITY_V1_DOMAIN}${canonicalize(sortedCapabilities)}`)));
|
|
5899
|
-
const
|
|
5900
|
-
const nativeProjectionHashHex = hex(sha2562(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize(
|
|
5737
|
+
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)));
|
|
5738
|
+
const nativeProjectionHashHex = hex(sha2562(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize(nativeProjection2)}`)));
|
|
5901
5739
|
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 }]]))]));
|
|
5902
5740
|
const kv = policy.capabilityCeiling.find((capability) => capability.kind === "kv");
|
|
5903
5741
|
const expectedKvActions = parsed.actions.flatMap((action) => action === "read" ? ["tinycloud.kv/get", "tinycloud.kv/metadata"] : action === "list" ? ["tinycloud.kv/list"] : ["tinycloud.kv/put"]);
|
|
@@ -5908,10 +5746,10 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
5908
5746
|
const binding = parsed.attestedEnforcerBinding;
|
|
5909
5747
|
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
5910
5748
|
const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
5911
|
-
if (binding.
|
|
5749
|
+
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;
|
|
5912
5750
|
try {
|
|
5913
|
-
const
|
|
5914
|
-
if (!ed255193.verify(fromBase64Url(bindingSignature.value),
|
|
5751
|
+
const digest3 = sha2562(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize(unsignedBinding)}`));
|
|
5752
|
+
if (!ed255193.verify(fromBase64Url(bindingSignature.value), digest3, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
|
|
5915
5753
|
} catch {
|
|
5916
5754
|
return false;
|
|
5917
5755
|
}
|
|
@@ -5950,7 +5788,8 @@ function base32Lower(bytes3) {
|
|
|
5950
5788
|
return output;
|
|
5951
5789
|
}
|
|
5952
5790
|
var KEY_LENGTH2 = 32;
|
|
5953
|
-
var INLINE_PREFIX = "#
|
|
5791
|
+
var INLINE_PREFIX = "#tc2=";
|
|
5792
|
+
var PUBLIC_INLINE_PARAMETER = "tc2";
|
|
5954
5793
|
var MAX_INLINE_BYTES = 256 * 1024;
|
|
5955
5794
|
function assertCanonicalCid(cidString) {
|
|
5956
5795
|
const cid2 = CID.parse(cidString);
|
|
@@ -5959,77 +5798,30 @@ function assertCanonicalCid(cidString) {
|
|
|
5959
5798
|
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
5960
5799
|
}
|
|
5961
5800
|
}
|
|
5962
|
-
function
|
|
5963
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
5964
|
-
throw new TypeError(`key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
5965
|
-
}
|
|
5966
|
-
assertCanonicalCid(ciphertextCid);
|
|
5967
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
5968
|
-
throw new TypeError(`origin must be a canonical https origin, got ${origin}`);
|
|
5969
|
-
}
|
|
5970
|
-
return `${origin}/s/${ciphertextCid}#k=${toBase64Url(key32)}`;
|
|
5971
|
-
}
|
|
5972
|
-
function parseShareUrl(url, options = {}) {
|
|
5973
|
-
const parsed = new URL(url);
|
|
5974
|
-
if (parsed.protocol !== "https:") {
|
|
5975
|
-
throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
|
|
5976
|
-
}
|
|
5977
|
-
if (parsed.username !== "" || parsed.password !== "") {
|
|
5978
|
-
throw new TypeError("share URL must not carry userinfo");
|
|
5979
|
-
}
|
|
5980
|
-
if (parsed.search !== "") {
|
|
5981
|
-
throw new TypeError("share URL must not have a query string");
|
|
5982
|
-
}
|
|
5983
|
-
if (options.expectedOrigin !== void 0) {
|
|
5984
|
-
if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
|
|
5985
|
-
throw new TypeError(
|
|
5986
|
-
`expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
|
|
5987
|
-
);
|
|
5988
|
-
}
|
|
5989
|
-
if (parsed.origin !== options.expectedOrigin) {
|
|
5990
|
-
throw new TypeError(
|
|
5991
|
-
`share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
|
|
5992
|
-
);
|
|
5993
|
-
}
|
|
5994
|
-
}
|
|
5995
|
-
const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
|
|
5996
|
-
if (!match || match[1] === void 0) {
|
|
5997
|
-
throw new TypeError(`not a share URL path: ${parsed.pathname}`);
|
|
5998
|
-
}
|
|
5999
|
-
const ciphertextCid = match[1];
|
|
6000
|
-
assertCanonicalCid(ciphertextCid);
|
|
6001
|
-
if (!parsed.hash.startsWith("#k=")) {
|
|
6002
|
-
throw new TypeError("share URL is missing the #k= key fragment");
|
|
6003
|
-
}
|
|
6004
|
-
const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
|
|
6005
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
6006
|
-
throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
6007
|
-
}
|
|
6008
|
-
return { ciphertextCid, key32 };
|
|
6009
|
-
}
|
|
6010
|
-
async function encodeInlineShareUrl(parts) {
|
|
5801
|
+
async function encodePublicInlineShareUrl(parts) {
|
|
6011
5802
|
if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
|
|
6012
|
-
if (parts.
|
|
6013
|
-
|
|
6014
|
-
const
|
|
6015
|
-
const payload = canonicalize({
|
|
5803
|
+
if (parts.plaintext.byteLength === 0 || parts.plaintext.byteLength > MAX_INLINE_BYTES) throw new RangeError("public inline envelope is outside the allowed size");
|
|
5804
|
+
const ciphertextCid = await computeCid(parts.plaintext);
|
|
5805
|
+
const payloadBytes = new TextEncoder().encode(canonicalize({
|
|
6016
5806
|
v: 2,
|
|
6017
|
-
c: toBase64Url(parts.
|
|
6018
|
-
cid: ciphertextCid
|
|
6019
|
-
|
|
6020
|
-
});
|
|
6021
|
-
const payloadBytes = new TextEncoder().encode(payload);
|
|
5807
|
+
c: toBase64Url(parts.plaintext),
|
|
5808
|
+
cid: ciphertextCid
|
|
5809
|
+
}));
|
|
6022
5810
|
if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
|
|
6023
|
-
return `${parts.origin}/
|
|
5811
|
+
return `${parts.origin}/viewer?${PUBLIC_INLINE_PARAMETER}=${toBase64Url(payloadBytes)}`;
|
|
6024
5812
|
}
|
|
6025
5813
|
function parseInlineShareUrl(url, options = {}) {
|
|
6026
5814
|
const parsed = new URL(url);
|
|
6027
|
-
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== ""
|
|
5815
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("inline share URL must be canonical HTTPS without userinfo");
|
|
6028
5816
|
if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
|
|
6029
|
-
if (parsed.pathname !== "/
|
|
5817
|
+
if (parsed.pathname !== "/viewer") throw new TypeError("not a TinyCloud policy share URL");
|
|
5818
|
+
const secretPayload = parsed.search === "" && parsed.hash.startsWith(INLINE_PREFIX) ? parsed.hash.slice(INLINE_PREFIX.length) : void 0;
|
|
5819
|
+
const publicPayload = parsed.hash === "" && parsed.searchParams.size === 1 ? parsed.searchParams.get(PUBLIC_INLINE_PARAMETER) ?? void 0 : void 0;
|
|
5820
|
+
if (secretPayload === void 0 && publicPayload === void 0) throw new TypeError("not a canonical TinyCloud policy share URL");
|
|
5821
|
+
if (publicPayload !== void 0 && parsed.search !== `?${PUBLIC_INLINE_PARAMETER}=${publicPayload}`) throw new TypeError("public inline URL is not canonical");
|
|
6030
5822
|
let payloadBytes;
|
|
6031
5823
|
try {
|
|
6032
|
-
payloadBytes = fromBase64Url(
|
|
5824
|
+
payloadBytes = fromBase64Url(secretPayload ?? publicPayload);
|
|
6033
5825
|
} catch {
|
|
6034
5826
|
throw new TypeError("inline payload is not canonical base64url");
|
|
6035
5827
|
}
|
|
@@ -6042,37 +5834,32 @@ function parseInlineShareUrl(url, options = {}) {
|
|
|
6042
5834
|
}
|
|
6043
5835
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload must be an object");
|
|
6044
5836
|
if (canonicalize(value) !== new TextDecoder("utf-8").decode(payloadBytes)) throw new TypeError("inline payload is not canonical JSON");
|
|
6045
|
-
const
|
|
6046
|
-
const keys = Object.keys(
|
|
6047
|
-
if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") ||
|
|
5837
|
+
const record = value;
|
|
5838
|
+
const keys = Object.keys(record);
|
|
5839
|
+
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");
|
|
6048
5840
|
let ciphertext;
|
|
6049
5841
|
try {
|
|
6050
|
-
ciphertext = fromBase64Url(
|
|
5842
|
+
ciphertext = fromBase64Url(record.c);
|
|
6051
5843
|
} catch {
|
|
6052
5844
|
throw new TypeError("inline ciphertext is not canonical base64url");
|
|
6053
5845
|
}
|
|
6054
5846
|
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
|
|
6055
|
-
assertCanonicalCid(
|
|
6056
|
-
if (
|
|
5847
|
+
assertCanonicalCid(record.cid);
|
|
5848
|
+
if (record.k !== void 0 && typeof record.k !== "string") throw new TypeError("inline key must be base64url");
|
|
6057
5849
|
let key32;
|
|
6058
5850
|
try {
|
|
6059
|
-
key32 =
|
|
5851
|
+
key32 = record.k === void 0 ? void 0 : fromBase64Url(record.k);
|
|
6060
5852
|
} catch {
|
|
6061
5853
|
throw new TypeError("inline key is not canonical base64url");
|
|
6062
5854
|
}
|
|
6063
5855
|
if (key32 !== void 0 && key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
|
|
6064
|
-
return { kind: "inline", ciphertextCid:
|
|
6065
|
-
}
|
|
6066
|
-
function parseCompactOrInlineShareUrl(url, options = {}) {
|
|
6067
|
-
if (new URL(url).pathname === "/s/inline") return parseInlineShareUrl(url, options);
|
|
6068
|
-
return { kind: "compact", ...parseShareUrl(url, options) };
|
|
5856
|
+
return { kind: "inline", ciphertextCid: record.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
|
|
6069
5857
|
}
|
|
6070
5858
|
|
|
6071
5859
|
// src/receive.ts
|
|
6072
5860
|
var SHARE_RESULT_VERSION = 1;
|
|
6073
5861
|
var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
|
|
6074
5862
|
var DEFAULT_MAX_CONTENT_BLOB_BYTES = DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
6075
|
-
var CONTENT_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
6076
5863
|
var ShareReceiveError = class extends Error {
|
|
6077
5864
|
code;
|
|
6078
5865
|
details;
|
|
@@ -6082,7 +5869,6 @@ var ShareReceiveError = class extends Error {
|
|
|
6082
5869
|
this.code = code3;
|
|
6083
5870
|
this.details = details;
|
|
6084
5871
|
}
|
|
6085
|
-
/** Machine output is deliberately code-only; diagnostics belong on stderr. */
|
|
6086
5872
|
toJSON() {
|
|
6087
5873
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: this.code } };
|
|
6088
5874
|
}
|
|
@@ -6091,338 +5877,79 @@ function toShareErrorInfo(error) {
|
|
|
6091
5877
|
if (error instanceof ShareReceiveError) return error.toJSON();
|
|
6092
5878
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: "fetch-failed" } };
|
|
6093
5879
|
}
|
|
6094
|
-
function boundedBytes(value, limit, code3) {
|
|
6095
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("fetch-failed", "share byte limit is invalid");
|
|
6096
|
-
if (!(value instanceof Uint8Array)) throw new ShareReceiveError("fetch-failed", "registry returned invalid bytes");
|
|
6097
|
-
if (value.byteLength > limit) throw new ShareReceiveError(code3, "share blob exceeds the configured byte limit");
|
|
6098
|
-
return value;
|
|
6099
|
-
}
|
|
6100
|
-
async function readResponseBytes(response, limit, tooLargeCode) {
|
|
6101
|
-
const contentLength = response.headers.get("content-length");
|
|
6102
|
-
if (contentLength !== null && Number.isSafeInteger(Number(contentLength)) && Number(contentLength) > limit) {
|
|
6103
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
6104
|
-
}
|
|
6105
|
-
if (response.body === null) {
|
|
6106
|
-
return boundedBytes(new Uint8Array(await response.arrayBuffer()), limit, tooLargeCode);
|
|
6107
|
-
}
|
|
6108
|
-
const reader = response.body.getReader();
|
|
6109
|
-
const chunks = [];
|
|
6110
|
-
let total = 0;
|
|
6111
|
-
try {
|
|
6112
|
-
for (; ; ) {
|
|
6113
|
-
const part = await reader.read();
|
|
6114
|
-
if (part.done) break;
|
|
6115
|
-
total += part.value.byteLength;
|
|
6116
|
-
if (total > limit) {
|
|
6117
|
-
await reader.cancel();
|
|
6118
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
6119
|
-
}
|
|
6120
|
-
chunks.push(part.value);
|
|
6121
|
-
}
|
|
6122
|
-
} finally {
|
|
6123
|
-
reader.releaseLock();
|
|
6124
|
-
}
|
|
6125
|
-
const bytes3 = new Uint8Array(total);
|
|
6126
|
-
let offset = 0;
|
|
6127
|
-
for (const chunk of chunks) {
|
|
6128
|
-
bytes3.set(chunk, offset);
|
|
6129
|
-
offset += chunk.byteLength;
|
|
6130
|
-
}
|
|
6131
|
-
return bytes3;
|
|
6132
|
-
}
|
|
6133
|
-
function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
|
|
6134
|
-
if (options.fetchBlob !== void 0) return async (input) => {
|
|
6135
|
-
try {
|
|
6136
|
-
options.signal?.throwIfAborted();
|
|
6137
|
-
const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
6138
|
-
options.signal?.throwIfAborted();
|
|
6139
|
-
return boundedBytes(bytes3, limit, tooLargeCode);
|
|
6140
|
-
} catch (error) {
|
|
6141
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6142
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
6143
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6144
|
-
}
|
|
6145
|
-
};
|
|
6146
|
-
if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "a registry fetch adapter is required");
|
|
6147
|
-
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
6148
|
-
const base3 = options.registryBaseUrl.replace(/\/+$/, "");
|
|
6149
|
-
return async ({ cid: cid2 }) => {
|
|
6150
|
-
try {
|
|
6151
|
-
options.signal?.throwIfAborted();
|
|
6152
|
-
const response = await fetchFn(`${base3}/ipfs/${cid2}?format=raw`, {
|
|
6153
|
-
headers: { accept: "application/vnd.ipld.raw" },
|
|
6154
|
-
redirect: "error",
|
|
6155
|
-
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
6156
|
-
});
|
|
6157
|
-
options.signal?.throwIfAborted();
|
|
6158
|
-
if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
|
|
6159
|
-
const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
|
|
6160
|
-
options.signal?.throwIfAborted();
|
|
6161
|
-
return bytes3;
|
|
6162
|
-
} catch (error) {
|
|
6163
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6164
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
6165
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6166
|
-
}
|
|
6167
|
-
};
|
|
6168
|
-
}
|
|
6169
5880
|
function metadataFor(envelope, origin) {
|
|
6170
|
-
|
|
6171
|
-
const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
6172
|
-
return {
|
|
6173
|
-
protocol: "tinycloud-share",
|
|
6174
|
-
version: 1,
|
|
6175
|
-
shareId: envelope.shareId,
|
|
6176
|
-
origin,
|
|
6177
|
-
target: { ...envelope.target, kind: targetKind3 },
|
|
6178
|
-
resource: envelope.resource,
|
|
6179
|
-
actions: envelope.actions,
|
|
6180
|
-
expiresAt: envelope.expiry,
|
|
6181
|
-
display: {
|
|
6182
|
-
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
6183
|
-
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
6184
|
-
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6185
|
-
},
|
|
6186
|
-
...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
|
|
6187
|
-
};
|
|
6188
|
-
}
|
|
5881
|
+
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
|
|
6189
5882
|
return {
|
|
6190
5883
|
protocol: "tinycloud-share",
|
|
6191
5884
|
version: 1,
|
|
6192
5885
|
shareId: envelope.shareId,
|
|
6193
5886
|
origin,
|
|
6194
|
-
target: { ...envelope.target, kind
|
|
6195
|
-
resource: envelope.
|
|
6196
|
-
actions: [
|
|
5887
|
+
target: { ...envelope.target, kind },
|
|
5888
|
+
resource: { ...envelope.resource },
|
|
5889
|
+
actions: [...envelope.actions],
|
|
6197
5890
|
expiresAt: envelope.expiry,
|
|
6198
5891
|
display: {
|
|
6199
5892
|
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
6200
5893
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
6201
5894
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6202
|
-
}
|
|
6203
|
-
...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
|
|
5895
|
+
}
|
|
6204
5896
|
};
|
|
6205
5897
|
}
|
|
6206
|
-
function
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
const value = JSON.parse(text);
|
|
6210
|
-
if (canonicalize(value) !== text) throw new Error("share envelope is not canonical JSON");
|
|
6211
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
6212
|
-
const version = value.version;
|
|
6213
|
-
if (version === 3) return shareEnvelopeV3Schema.parse(value);
|
|
6214
|
-
if (version === 2) return shareEnvelopeV2Schema.parse(value);
|
|
6215
|
-
}
|
|
6216
|
-
return shareEnvelopeSchema.parse(value);
|
|
6217
|
-
} catch {
|
|
6218
|
-
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
6219
|
-
}
|
|
6220
|
-
}
|
|
6221
|
-
async function resolveShareEnvelope(link, options = {}) {
|
|
5898
|
+
async function resolvePolicyShare(link, options) {
|
|
5899
|
+
options.signal?.throwIfAborted();
|
|
5900
|
+
let url;
|
|
6222
5901
|
let parsed;
|
|
6223
5902
|
try {
|
|
6224
|
-
|
|
5903
|
+
url = new URL(link);
|
|
5904
|
+
if (url.pathname !== "/viewer" || url.hash !== "" || url.searchParams.size !== 1 || !url.searchParams.has("tc2")) throw new Error("not public Policy/v3");
|
|
5905
|
+
parsed = parseInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
|
|
5906
|
+
if (parsed.key32 !== void 0) throw new Error("secret inline transport is retired");
|
|
6225
5907
|
} catch {
|
|
6226
5908
|
throw new ShareReceiveError("invalid-link", "share link format is invalid");
|
|
6227
5909
|
}
|
|
5910
|
+
const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
5911
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "public policy envelope exceeds the configured byte limit");
|
|
5912
|
+
if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "public envelope bytes do not match the link CID");
|
|
5913
|
+
options.signal?.throwIfAborted();
|
|
5914
|
+
let envelope;
|
|
6228
5915
|
try {
|
|
6229
|
-
|
|
6230
|
-
const
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
let plaintext;
|
|
6234
|
-
try {
|
|
6235
|
-
plaintext = parsed.key32 === void 0 ? sealed : await open(sealed, parsed.key32);
|
|
6236
|
-
} catch {
|
|
6237
|
-
throw new ShareReceiveError("decrypt-failed", "share envelope could not be opened");
|
|
6238
|
-
}
|
|
6239
|
-
return { envelope: parseEnvelope(plaintext), origin: url.origin, cid: parsed.ciphertextCid, kind: parsed.kind };
|
|
6240
|
-
} finally {
|
|
6241
|
-
parsed.key32?.fill(0);
|
|
6242
|
-
}
|
|
6243
|
-
}
|
|
6244
|
-
function actionName(value) {
|
|
6245
|
-
return value === "read" ? "tinycloud.kv/get" : value === "list" ? "tinycloud.kv/list" : value === "edit" ? "tinycloud.kv/put" : value;
|
|
6246
|
-
}
|
|
6247
|
-
function resourceContains(outer, inner) {
|
|
6248
|
-
if (outer.kind === "exact") return inner.kind === "exact" && outer.path === inner.path;
|
|
6249
|
-
const prefix = outer.path.endsWith("/") ? outer.path : `${outer.path}/`;
|
|
6250
|
-
return inner.path === outer.path || inner.path.startsWith(prefix);
|
|
6251
|
-
}
|
|
6252
|
-
function policyEvidenceMatches(envelope, evidence) {
|
|
6253
|
-
const registeredActions = new Set(evidence.actions.map(actionName));
|
|
6254
|
-
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);
|
|
6255
|
-
}
|
|
6256
|
-
async function verifyV2Envelope(envelope, linkOrigin, options) {
|
|
6257
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
6258
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
6259
|
-
}
|
|
6260
|
-
try {
|
|
6261
|
-
if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
5916
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(parsed.ciphertext);
|
|
5917
|
+
const value = JSON.parse(encoded);
|
|
5918
|
+
if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
|
|
5919
|
+
envelope = shareEnvelopeV3Schema.parse(value);
|
|
6262
5920
|
} catch {
|
|
6263
|
-
throw new ShareReceiveError("
|
|
6264
|
-
}
|
|
6265
|
-
const expiry = Date.parse(envelope.expiry);
|
|
6266
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6267
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6268
|
-
if (envelope.authorizationTarget.kind === "policy") {
|
|
6269
|
-
if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "an external policy authority is required");
|
|
6270
|
-
let evidence;
|
|
6271
|
-
try {
|
|
6272
|
-
evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
|
|
6273
|
-
} catch {
|
|
6274
|
-
evidence = void 0;
|
|
6275
|
-
}
|
|
6276
|
-
if (evidence === void 0 || !policyEvidenceMatches(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "share policy authority evidence is not bound to the envelope");
|
|
6277
|
-
if (options.trustedSignerDid !== void 0 && (evidence.signerDid !== options.trustedSignerDid || envelope.signature.signerDid !== options.trustedSignerDid)) {
|
|
6278
|
-
throw new ShareReceiveError("signature-invalid", "share signer is not trusted");
|
|
6279
|
-
}
|
|
6280
|
-
} else if (envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
|
|
6281
|
-
throw new ShareReceiveError("capability-invalid", "recipient authorization does not match the signed recipient");
|
|
6282
|
-
} else {
|
|
6283
|
-
if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
|
|
6284
|
-
}
|
|
6285
|
-
}
|
|
6286
|
-
async function verifyV3Envelope(envelope, linkOrigin, options) {
|
|
6287
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
6288
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
5921
|
+
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
6289
5922
|
}
|
|
6290
5923
|
try {
|
|
6291
5924
|
if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
|
|
6292
5925
|
} catch {
|
|
6293
5926
|
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
6294
5927
|
}
|
|
5928
|
+
options.signal?.throwIfAborted();
|
|
6295
5929
|
const expiry = Date.parse(envelope.expiry);
|
|
6296
5930
|
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6297
5931
|
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
|
|
6301
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
6302
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
6303
|
-
}
|
|
6304
|
-
throw new ShareReceiveError("unsupported-target", "share policy target is not supported", { reason: "policy-target" });
|
|
6305
|
-
}
|
|
6306
|
-
async function verifyBearerEnvelope(envelope, linkOrigin, options = {}) {
|
|
6307
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
6308
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
6309
|
-
}
|
|
6310
|
-
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" });
|
|
6311
|
-
if (envelope.target.resource.kind !== "exact") throw new ShareReceiveError("unsupported-target", "this receive path only handles exact resources", { reason: "prefix-resource" });
|
|
6312
|
-
const expiry = Date.parse(envelope.expiry);
|
|
6313
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6314
|
-
try {
|
|
6315
|
-
if (!await verifyEnvelope(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
6316
|
-
} catch {
|
|
6317
|
-
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
6318
|
-
}
|
|
6319
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6320
|
-
const capability = checkBearerDelegation(envelope, options.now === void 0 ? {} : { now: options.now });
|
|
6321
|
-
if (!capability.ok) throw new ShareReceiveError("capability-invalid", "share delegation does not authorize the target");
|
|
6322
|
-
}
|
|
6323
|
-
async function verifyResolved(envelope, linkOrigin, options) {
|
|
6324
|
-
await verifyBearerEnvelope(envelope, linkOrigin, options);
|
|
5932
|
+
options.onResolvedAddressedEnvelope?.(envelope, parsed.ciphertextCid);
|
|
5933
|
+
return { envelope, origin: url.origin, cid: parsed.ciphertextCid };
|
|
6325
5934
|
}
|
|
6326
5935
|
async function inspectShare(link, options = {}) {
|
|
6327
|
-
const resolved = await
|
|
6328
|
-
|
|
6329
|
-
else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
6330
|
-
else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6331
|
-
else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6332
|
-
if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6333
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
|
|
5936
|
+
const resolved = await resolvePolicyShare(link, options);
|
|
5937
|
+
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
|
|
6334
5938
|
}
|
|
6335
5939
|
async function receiveShare(link, options = {}) {
|
|
6336
|
-
const resolved = await
|
|
6337
|
-
|
|
6338
|
-
await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6339
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6340
|
-
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6341
|
-
}
|
|
6342
|
-
if (resolved.envelope.version === 2) {
|
|
6343
|
-
await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6344
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6345
|
-
const method = resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
|
|
6346
|
-
if (options.authorization === void 0) return { state: "authorization-required", method };
|
|
6347
|
-
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 } });
|
|
6348
|
-
if (result.state === "authorization-required") return result;
|
|
6349
|
-
if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "share authorization was denied");
|
|
6350
|
-
if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
|
|
6351
|
-
throw new ShareReceiveError("content-integrity-failed", "share authorization returned an unsigned content result");
|
|
6352
|
-
}
|
|
6353
|
-
const authorized = result.value;
|
|
6354
|
-
try {
|
|
6355
|
-
if (!await options.authorization.verifyResult({ envelope: resolved.envelope, value: authorized, proof: authorized.proof })) throw new Error("node proof");
|
|
6356
|
-
} catch {
|
|
6357
|
-
throw new ShareReceiveError("content-integrity-failed", "node authorization proof is invalid");
|
|
6358
|
-
}
|
|
6359
|
-
const bytes4 = authorized.bytes.slice();
|
|
6360
|
-
const maxBytes = options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES;
|
|
6361
|
-
if (bytes4.byteLength > maxBytes) throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
6362
|
-
const digestBytes3 = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes4));
|
|
6363
|
-
const bodyDigest = toBase64Url(digestBytes3);
|
|
6364
|
-
if (authorized.bodyDigest !== bodyDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content digest does not match");
|
|
6365
|
-
if (authorized.contentSourceDigest !== resolved.envelope.contentSourceDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content source does not match");
|
|
6366
|
-
const binding = authorized.binding;
|
|
6367
|
-
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");
|
|
6368
|
-
if (resolved.envelope.metadata.byteLength !== void 0 && resolved.envelope.metadata.byteLength !== bytes4.byteLength) {
|
|
6369
|
-
throw new ShareReceiveError("content-integrity-failed", "authorized content length does not match");
|
|
6370
|
-
}
|
|
6371
|
-
let text2;
|
|
6372
|
-
try {
|
|
6373
|
-
text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes4);
|
|
6374
|
-
} catch {
|
|
6375
|
-
}
|
|
6376
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes4, ...text2 === void 0 ? {} : { text: text2 } };
|
|
6377
|
-
}
|
|
6378
|
-
if (resolved.envelope.authorizationTarget.kind === "policy") {
|
|
6379
|
-
await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
|
|
6380
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6381
|
-
return { state: "authorization-required", method: "email-claim" };
|
|
6382
|
-
}
|
|
6383
|
-
await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
6384
|
-
let bytes3 = new Uint8Array();
|
|
6385
|
-
if (resolved.envelope.content !== void 0) {
|
|
6386
|
-
bytes3 = await registryFetcher(
|
|
6387
|
-
options,
|
|
6388
|
-
(options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES) + CONTENT_SEALED_OVERHEAD,
|
|
6389
|
-
"max-bytes-exceeded",
|
|
6390
|
-
"content"
|
|
6391
|
-
)({ origin: resolved.origin, cid: resolved.envelope.content.cid });
|
|
6392
|
-
if (await computeCid(bytes3) !== resolved.envelope.content.cid) throw new ShareReceiveError("content-integrity-failed", "shared content CID does not match");
|
|
6393
|
-
const key = fromBase64Url(resolved.envelope.content.key);
|
|
6394
|
-
try {
|
|
6395
|
-
bytes3 = await open(bytes3, key);
|
|
6396
|
-
} catch {
|
|
6397
|
-
throw new ShareReceiveError("content-integrity-failed", "shared content could not be opened");
|
|
6398
|
-
} finally {
|
|
6399
|
-
key.fill(0);
|
|
6400
|
-
}
|
|
6401
|
-
if (bytes3.byteLength > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) {
|
|
6402
|
-
throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
6403
|
-
}
|
|
6404
|
-
}
|
|
6405
|
-
let text;
|
|
6406
|
-
if (bytes3.byteLength !== 0) {
|
|
6407
|
-
try {
|
|
6408
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
|
|
6409
|
-
} catch {
|
|
6410
|
-
}
|
|
6411
|
-
}
|
|
6412
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes3, ...text === void 0 ? {} : { text } };
|
|
5940
|
+
const resolved = await resolvePolicyShare(link, options);
|
|
5941
|
+
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6413
5942
|
}
|
|
6414
5943
|
|
|
6415
5944
|
// src/publish.ts
|
|
6416
|
-
import { ed25519 as ed255194 } from "@noble/curves/ed25519";
|
|
6417
5945
|
var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
|
|
6418
|
-
var SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
6419
5946
|
var SHARE_PUBLISH_RESULT_VERSION = 1;
|
|
6420
5947
|
var DEFAULT_SHARE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6421
5948
|
function redactPublishedShare(result) {
|
|
6422
5949
|
return {
|
|
6423
5950
|
protocol: "tinycloud-share",
|
|
6424
5951
|
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
6425
|
-
link: {
|
|
5952
|
+
link: { ...result.link },
|
|
6426
5953
|
metadata: {
|
|
6427
5954
|
protocol: "tinycloud-share",
|
|
6428
5955
|
version: 1,
|
|
@@ -6432,10 +5959,8 @@ function redactPublishedShare(result) {
|
|
|
6432
5959
|
resource: { ...result.metadata.resource },
|
|
6433
5960
|
actions: [...result.metadata.actions],
|
|
6434
5961
|
expiresAt: result.metadata.expiresAt,
|
|
6435
|
-
display: { ...result.metadata.display }
|
|
6436
|
-
|
|
6437
|
-
},
|
|
6438
|
-
registryDeleteAfter: result.registryDeleteAfter
|
|
5962
|
+
display: { ...result.metadata.display }
|
|
5963
|
+
}
|
|
6439
5964
|
};
|
|
6440
5965
|
}
|
|
6441
5966
|
var SharePublishError = class extends Error {
|
|
@@ -6446,237 +5971,6 @@ var SharePublishError = class extends Error {
|
|
|
6446
5971
|
this.code = code3;
|
|
6447
5972
|
}
|
|
6448
5973
|
};
|
|
6449
|
-
function assertSafeFilename(filename) {
|
|
6450
|
-
if (filename.length === 0 || filename === "." || filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(filename)) {
|
|
6451
|
-
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
6452
|
-
}
|
|
6453
|
-
}
|
|
6454
|
-
function assertOrigin(origin, label) {
|
|
6455
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
6456
|
-
throw new SharePublishError("invalid-argument", `${label} must be a canonical HTTPS origin`);
|
|
6457
|
-
}
|
|
6458
|
-
}
|
|
6459
|
-
async function snapshotSource(source, limit) {
|
|
6460
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
6461
|
-
throw new SharePublishError("invalid-argument", "maxBytes must be a positive safe integer");
|
|
6462
|
-
}
|
|
6463
|
-
if (source instanceof Uint8Array) {
|
|
6464
|
-
if (source.byteLength > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
6465
|
-
return source.slice();
|
|
6466
|
-
}
|
|
6467
|
-
const chunks = [];
|
|
6468
|
-
let total = 0;
|
|
6469
|
-
try {
|
|
6470
|
-
for await (const chunk of source) {
|
|
6471
|
-
if (!(chunk instanceof Uint8Array)) throw new SharePublishError("invalid-argument", "share source yielded invalid bytes");
|
|
6472
|
-
total += chunk.byteLength;
|
|
6473
|
-
if (total > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
6474
|
-
chunks.push(chunk);
|
|
6475
|
-
}
|
|
6476
|
-
} catch (error) {
|
|
6477
|
-
if (error instanceof SharePublishError) throw error;
|
|
6478
|
-
throw new SharePublishError("upload-failed", "share source could not be read");
|
|
6479
|
-
}
|
|
6480
|
-
const bytes3 = new Uint8Array(total);
|
|
6481
|
-
let offset = 0;
|
|
6482
|
-
for (const chunk of chunks) {
|
|
6483
|
-
bytes3.set(chunk, offset);
|
|
6484
|
-
offset += chunk.byteLength;
|
|
6485
|
-
}
|
|
6486
|
-
return bytes3;
|
|
6487
|
-
}
|
|
6488
|
-
function assertTarget(target) {
|
|
6489
|
-
if (target.kind !== "bearer") {
|
|
6490
|
-
throw new SharePublishError("unsupported-target", "this release publishes bearer shares only");
|
|
6491
|
-
}
|
|
6492
|
-
}
|
|
6493
|
-
function assertRegistryUrl(value, allowInsecure) {
|
|
6494
|
-
let url;
|
|
6495
|
-
try {
|
|
6496
|
-
url = new URL(value);
|
|
6497
|
-
} catch {
|
|
6498
|
-
throw new SharePublishError("invalid-argument", "registry URL is invalid");
|
|
6499
|
-
}
|
|
6500
|
-
if (url.username || url.password || url.search || url.hash) throw new SharePublishError("invalid-argument", "registry URL must not contain credentials, query, or fragment");
|
|
6501
|
-
if (!allowInsecure && url.protocol !== "https:") throw new SharePublishError("invalid-argument", "production registry uploads require HTTPS");
|
|
6502
|
-
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");
|
|
6503
|
-
return url;
|
|
6504
|
-
}
|
|
6505
|
-
async function defaultUpload(options, input) {
|
|
6506
|
-
if (options.registryBaseUrl === void 0) throw new SharePublishError("upload-auth-required", "an authenticated share registry is required");
|
|
6507
|
-
const base3 = assertRegistryUrl(options.registryBaseUrl, options.allowInsecureRegistry === true).toString().replace(/\/$/, "");
|
|
6508
|
-
const localInsecure = options.allowInsecureRegistry === true && (new URL(base3).hostname === "127.0.0.1" || new URL(base3).hostname === "localhost");
|
|
6509
|
-
if (options.authorizeUpload === void 0 && !localInsecure) {
|
|
6510
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization is required");
|
|
6511
|
-
}
|
|
6512
|
-
let authorization;
|
|
6513
|
-
if (options.authorizeUpload !== void 0) {
|
|
6514
|
-
try {
|
|
6515
|
-
authorization = await options.authorizeUpload(input);
|
|
6516
|
-
} catch {
|
|
6517
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6518
|
-
}
|
|
6519
|
-
}
|
|
6520
|
-
if (authorization !== void 0 && options.authorizationOrigin !== void 0) {
|
|
6521
|
-
let authorizationUrl;
|
|
6522
|
-
try {
|
|
6523
|
-
authorizationUrl = new URL(options.authorizationOrigin);
|
|
6524
|
-
} catch {
|
|
6525
|
-
throw new SharePublishError("invalid-argument", "upload authorization origin is invalid");
|
|
6526
|
-
}
|
|
6527
|
-
if (authorizationUrl.origin !== new URL(base3).origin) throw new SharePublishError("upload-auth-required", "upload authorization is scoped to another origin");
|
|
6528
|
-
}
|
|
6529
|
-
const headers = new Headers({
|
|
6530
|
-
"content-type": "application/vnd.ipld.raw",
|
|
6531
|
-
"if-none-match": "*",
|
|
6532
|
-
"x-delete-after": input.deleteAfter
|
|
6533
|
-
});
|
|
6534
|
-
if (typeof authorization === "string") {
|
|
6535
|
-
headers.set("x-tinycloud-upload-attestation", authorization);
|
|
6536
|
-
try {
|
|
6537
|
-
const attestation = JSON.parse(authorization);
|
|
6538
|
-
if (attestation.retention === void 0) throw new Error("retention");
|
|
6539
|
-
headers.set("x-tinycloud-retention", canonicalize(attestation.retention));
|
|
6540
|
-
} catch {
|
|
6541
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6542
|
-
}
|
|
6543
|
-
} else if (authorization !== void 0) {
|
|
6544
|
-
new Headers(authorization).forEach((value, key) => headers.set(key, value));
|
|
6545
|
-
}
|
|
6546
|
-
let response;
|
|
6547
|
-
try {
|
|
6548
|
-
response = await (options.fetchFn ?? globalThis.fetch)(`${base3}/blobs`, {
|
|
6549
|
-
method: "POST",
|
|
6550
|
-
headers,
|
|
6551
|
-
body: input.blob,
|
|
6552
|
-
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
6553
|
-
redirect: "error",
|
|
6554
|
-
referrerPolicy: "no-referrer"
|
|
6555
|
-
});
|
|
6556
|
-
} catch {
|
|
6557
|
-
throw new SharePublishError("upload-failed", "share registry is unavailable");
|
|
6558
|
-
}
|
|
6559
|
-
if (response.status === 401 || response.status === 403) throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6560
|
-
if (!response.ok) throw new SharePublishError("upload-failed", "share registry rejected the encrypted blob");
|
|
6561
|
-
let body;
|
|
6562
|
-
try {
|
|
6563
|
-
body = await response.json();
|
|
6564
|
-
} catch {
|
|
6565
|
-
throw new SharePublishError("upload-failed", "share registry returned an invalid response");
|
|
6566
|
-
}
|
|
6567
|
-
const record2 = typeof body === "object" && body !== null ? body : {};
|
|
6568
|
-
if (record2.cid !== input.cid || typeof record2.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share registry returned an unexpected CID or retention value");
|
|
6569
|
-
return { cid: input.cid, deleteAfter: record2.deleteAfter };
|
|
6570
|
-
}
|
|
6571
|
-
async function uploadShareBlob(options, input) {
|
|
6572
|
-
const result = await (options.uploadBlob ?? ((value) => defaultUpload(options, value)))(input);
|
|
6573
|
-
if (result.cid !== input.cid || typeof result.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share uploader returned an invalid result");
|
|
6574
|
-
return result;
|
|
6575
|
-
}
|
|
6576
|
-
async function publishShare(options) {
|
|
6577
|
-
const target = options.target ?? { kind: "bearer" };
|
|
6578
|
-
assertTarget(target);
|
|
6579
|
-
assertSafeFilename(options.filename);
|
|
6580
|
-
assertOrigin(options.origin, "share origin");
|
|
6581
|
-
if (options.mediaType !== void 0 && options.mediaType.length === 0) throw new SharePublishError("invalid-argument", "mediaType must not be empty");
|
|
6582
|
-
const maxBytes = options.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
6583
|
-
if (maxBytes > SHARE_CONTENT_LIMIT) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the maximum 100 MiB limit");
|
|
6584
|
-
const content = await snapshotSource(options.source, maxBytes);
|
|
6585
|
-
if (content.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
6586
|
-
if (options.allowBinary !== true) {
|
|
6587
|
-
try {
|
|
6588
|
-
new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
6589
|
-
} catch {
|
|
6590
|
-
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
6591
|
-
}
|
|
6592
|
-
}
|
|
6593
|
-
const nowMs = options.now?.() ?? Date.now();
|
|
6594
|
-
const expiresAt = options.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
|
|
6595
|
-
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
6596
|
-
const expiry = expiresAt.toISOString();
|
|
6597
|
-
const nodeAudience = options.nodeAudience ?? "did:web:node.tinycloud.xyz";
|
|
6598
|
-
const spaceId = options.spaceId ?? "bearer";
|
|
6599
|
-
const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
|
|
6600
|
-
const path = `shares/${shareId}/${options.filename}`;
|
|
6601
|
-
const sessionPrivateKey = ed255194.utils.randomPrivateKey();
|
|
6602
|
-
const senderPrivateKey = ed255194.utils.randomPrivateKey();
|
|
6603
|
-
let contentKey;
|
|
6604
|
-
let envelopeKey;
|
|
6605
|
-
try {
|
|
6606
|
-
const sessionPublicKey = ed255194.getPublicKey(sessionPrivateKey);
|
|
6607
|
-
const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
|
|
6608
|
-
const delegation = mintBearerDelegation({
|
|
6609
|
-
issuerPrivateKey: senderPrivateKey,
|
|
6610
|
-
audienceDid: sessionDid,
|
|
6611
|
-
resourceUri: bearerResourceUri(options.origin, spaceId, path),
|
|
6612
|
-
expiresAtSeconds: Math.ceil(expiresAt.getTime() / 1e3)
|
|
6613
|
-
});
|
|
6614
|
-
contentKey = generateKey();
|
|
6615
|
-
const sealedContent = await seal(content, contentKey);
|
|
6616
|
-
const unsigned = {
|
|
6617
|
-
version: 1,
|
|
6618
|
-
shareId,
|
|
6619
|
-
delegation,
|
|
6620
|
-
authorizationTarget: {
|
|
6621
|
-
kind: "bearerKey",
|
|
6622
|
-
sessionJwk: { kty: "OKP", crv: "Ed25519", x: toBase64Url(sessionPublicKey), d: toBase64Url(sessionPrivateKey) }
|
|
6623
|
-
},
|
|
6624
|
-
target: { origin: options.origin, nodeAudience, spaceId, resource: { kind: "exact", path } },
|
|
6625
|
-
display: { filename: options.filename },
|
|
6626
|
-
expiry,
|
|
6627
|
-
content: { cid: sealedContent.cid, key: toBase64Url(contentKey) }
|
|
6628
|
-
};
|
|
6629
|
-
const envelope = signEnvelope(unsigned, senderPrivateKey);
|
|
6630
|
-
envelopeKey = generateKey();
|
|
6631
|
-
const sealedEnvelope = await seal(new TextEncoder().encode(canonicalize(envelope)), envelopeKey);
|
|
6632
|
-
let inlineUrl;
|
|
6633
|
-
if (options.inline === true) {
|
|
6634
|
-
try {
|
|
6635
|
-
inlineUrl = await encodeInlineShareUrl({ origin: options.origin, ciphertext: sealedEnvelope.blob, key32: envelopeKey });
|
|
6636
|
-
} catch (error) {
|
|
6637
|
-
throw new SharePublishError("inline-too-large", error instanceof Error ? error.message : "inline share is too large");
|
|
6638
|
-
}
|
|
6639
|
-
}
|
|
6640
|
-
const contentUpload = await uploadShareBlob(options, { blob: sealedContent.blob, cid: sealedContent.cid, deleteAfter: expiry, contentLength: sealedContent.blob.byteLength });
|
|
6641
|
-
let url;
|
|
6642
|
-
let kind;
|
|
6643
|
-
let retention = contentUpload.deleteAfter;
|
|
6644
|
-
if (options.inline === true) {
|
|
6645
|
-
if (inlineUrl === void 0) throw new SharePublishError("inline-too-large", "inline share is too large");
|
|
6646
|
-
url = inlineUrl;
|
|
6647
|
-
kind = "inline";
|
|
6648
|
-
} else {
|
|
6649
|
-
const envelopeUpload = await uploadShareBlob(options, { blob: sealedEnvelope.blob, cid: sealedEnvelope.cid, deleteAfter: expiry, contentLength: sealedEnvelope.blob.byteLength });
|
|
6650
|
-
url = encodeShareUrl({ origin: options.origin, ciphertextCid: envelopeUpload.cid, key32: envelopeKey });
|
|
6651
|
-
kind = "compact";
|
|
6652
|
-
retention = envelopeUpload.deleteAfter;
|
|
6653
|
-
}
|
|
6654
|
-
const metadata = {
|
|
6655
|
-
protocol: "tinycloud-share",
|
|
6656
|
-
version: 1,
|
|
6657
|
-
shareId,
|
|
6658
|
-
origin: options.origin,
|
|
6659
|
-
target: { kind: "bearer", origin: options.origin, nodeAudience, spaceId },
|
|
6660
|
-
resource: { kind: "exact", path },
|
|
6661
|
-
actions: ["read"],
|
|
6662
|
-
expiresAt: expiry,
|
|
6663
|
-
display: { filename: options.filename },
|
|
6664
|
-
content: { cid: sealedContent.cid }
|
|
6665
|
-
};
|
|
6666
|
-
const result = { protocol: "tinycloud-share", version: SHARE_PUBLISH_RESULT_VERSION, link: { kind, cid: sealedEnvelope.cid }, metadata, registryDeleteAfter: retention };
|
|
6667
|
-
Object.defineProperty(result, "toJSON", {
|
|
6668
|
-
enumerable: false,
|
|
6669
|
-
value: () => redactPublishedShare(result)
|
|
6670
|
-
});
|
|
6671
|
-
Object.defineProperty(result, "url", { enumerable: false, value: url });
|
|
6672
|
-
return result;
|
|
6673
|
-
} finally {
|
|
6674
|
-
sessionPrivateKey.fill(0);
|
|
6675
|
-
senderPrivateKey.fill(0);
|
|
6676
|
-
contentKey?.fill(0);
|
|
6677
|
-
envelopeKey?.fill(0);
|
|
6678
|
-
}
|
|
6679
|
-
}
|
|
6680
5974
|
|
|
6681
5975
|
// src/addressed.ts
|
|
6682
5976
|
async function parseAddressedEnvelope(value) {
|
|
@@ -6694,10 +5988,31 @@ async function parseAddressedEnvelope(value) {
|
|
|
6694
5988
|
return { envelope, policy: decoded, policyCid: envelope.authorizationTarget.policyCid };
|
|
6695
5989
|
}
|
|
6696
5990
|
|
|
6697
|
-
// src/
|
|
6698
|
-
import { ed25519 as ed255195 } from "@noble/curves/ed25519";
|
|
5991
|
+
// src/addressed-publish.ts
|
|
6699
5992
|
import { sha256 as sha2563 } from "@noble/hashes/sha256";
|
|
6700
5993
|
|
|
5994
|
+
// src/authorization.ts
|
|
5995
|
+
async function authorizeShare(input) {
|
|
5996
|
+
if (input.resumeToken !== void 0) {
|
|
5997
|
+
if (input.resumeToken.length < 16 || input.resumeToken.length > 512) {
|
|
5998
|
+
return { state: "denied", reason: "rejected" };
|
|
5999
|
+
}
|
|
6000
|
+
return input.adapter.resume({
|
|
6001
|
+
envelope: input.envelope,
|
|
6002
|
+
method: input.method,
|
|
6003
|
+
resumeToken: input.resumeToken,
|
|
6004
|
+
...input.proof === void 0 ? {} : { proof: input.proof }
|
|
6005
|
+
});
|
|
6006
|
+
}
|
|
6007
|
+
return input.adapter.begin({ envelope: input.envelope, method: input.method });
|
|
6008
|
+
}
|
|
6009
|
+
function authorizationMethodForTarget(target) {
|
|
6010
|
+
if (target.kind === "recipientDid") return "openkey-device";
|
|
6011
|
+
if (target.kind === "email") return "email-claim";
|
|
6012
|
+
if (target.kind === "emailDomain") return "email-claim";
|
|
6013
|
+
return void 0;
|
|
6014
|
+
}
|
|
6015
|
+
|
|
6701
6016
|
// ../../node_modules/multiformats/dist/src/bytes.js
|
|
6702
6017
|
var empty2 = new Uint8Array(0);
|
|
6703
6018
|
function equals3(aa, bb) {
|
|
@@ -7039,9 +6354,402 @@ function rfc46482({ name, prefix, bitsPerChar, alphabet }) {
|
|
|
7039
6354
|
});
|
|
7040
6355
|
}
|
|
7041
6356
|
|
|
7042
|
-
// ../../node_modules/multiformats/dist/src/bases/
|
|
7043
|
-
var
|
|
7044
|
-
|
|
6357
|
+
// ../../node_modules/multiformats/dist/src/bases/base58.js
|
|
6358
|
+
var base58btc2 = baseX2({
|
|
6359
|
+
name: "base58btc",
|
|
6360
|
+
prefix: "z",
|
|
6361
|
+
alphabet: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
6362
|
+
});
|
|
6363
|
+
var base58flickr2 = baseX2({
|
|
6364
|
+
name: "base58flickr",
|
|
6365
|
+
prefix: "Z",
|
|
6366
|
+
alphabet: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
6367
|
+
});
|
|
6368
|
+
|
|
6369
|
+
// src/targets.ts
|
|
6370
|
+
function validEmail(value) {
|
|
6371
|
+
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);
|
|
6372
|
+
}
|
|
6373
|
+
function validDomain(value) {
|
|
6374
|
+
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);
|
|
6375
|
+
}
|
|
6376
|
+
function validRecipientDid(value) {
|
|
6377
|
+
if (value.length === 0 || value.length > 2048 || /[\u0000-\u0020\u007f]/.test(value)) return false;
|
|
6378
|
+
const parts = value.split(":");
|
|
6379
|
+
if (parts.length < 3 || parts[0] !== "did" || !/^[a-z0-9]+$/.test(parts[1] ?? "")) return false;
|
|
6380
|
+
const identifier = parts.slice(2);
|
|
6381
|
+
if (identifier.some((part) => part.length === 0)) return false;
|
|
6382
|
+
if (parts[1] === "web") {
|
|
6383
|
+
const host = identifier[0] ?? "";
|
|
6384
|
+
if (host.length > 253 || host.split(".").some((label) => !label || label.length > 63 || !/^[A-Za-z0-9-]+$/.test(label) || label.startsWith("-") || label.endsWith("-"))) return false;
|
|
6385
|
+
return identifier.slice(1).every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
6386
|
+
}
|
|
6387
|
+
if (parts[1] === "pkh") return identifier.length >= 3 && identifier.every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
6388
|
+
if (parts[1] === "key") {
|
|
6389
|
+
try {
|
|
6390
|
+
const bytes3 = base58btc2.decode(identifier.join(":"));
|
|
6391
|
+
return bytes3.length === 34 && bytes3[0] === 237 && bytes3[1] === 1;
|
|
6392
|
+
} catch {
|
|
6393
|
+
return false;
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
return false;
|
|
6397
|
+
}
|
|
6398
|
+
function normalizeShareTarget(target) {
|
|
6399
|
+
if (target.kind === "bearer") return target;
|
|
6400
|
+
if (target.kind === "recipientDid") {
|
|
6401
|
+
if (!validRecipientDid(target.did)) throw new TypeError("recipient DID is invalid");
|
|
6402
|
+
return { kind: target.kind, did: target.did };
|
|
6403
|
+
}
|
|
6404
|
+
if (target.kind === "email") {
|
|
6405
|
+
if (!validEmail(target.address)) throw new TypeError("recipient email is invalid");
|
|
6406
|
+
const at = target.address.lastIndexOf("@");
|
|
6407
|
+
return { kind: target.kind, address: `${target.address.slice(0, at)}@${target.address.slice(at + 1).toLowerCase()}` };
|
|
6408
|
+
}
|
|
6409
|
+
if (!validDomain(target.domain)) throw new TypeError("recipient email domain is invalid");
|
|
6410
|
+
return { kind: target.kind, domain: target.domain.toLowerCase() };
|
|
6411
|
+
}
|
|
6412
|
+
function targetAuthorizationMethod(target) {
|
|
6413
|
+
return authorizationMethodForTarget(target);
|
|
6414
|
+
}
|
|
6415
|
+
async function publishTargetShare(input) {
|
|
6416
|
+
const target = normalizeShareTarget(input.target);
|
|
6417
|
+
if (input.targetAdapter === void 0) {
|
|
6418
|
+
if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
|
|
6419
|
+
return {
|
|
6420
|
+
state: "authorization-required",
|
|
6421
|
+
method: targetAuthorizationMethod(target)
|
|
6422
|
+
};
|
|
6423
|
+
}
|
|
6424
|
+
const source = input.source instanceof Uint8Array ? input.source.slice() : await (async () => {
|
|
6425
|
+
const chunks = [];
|
|
6426
|
+
let size = 0;
|
|
6427
|
+
for await (const chunk of input.source) {
|
|
6428
|
+
if (!(chunk instanceof Uint8Array)) throw new TypeError("share publication source yielded invalid bytes");
|
|
6429
|
+
size += chunk.byteLength;
|
|
6430
|
+
if (size > (input.maxBytes ?? SHARE_CONTENT_LIMIT)) throw new SharePublishError("max-bytes-exceeded", "share publication exceeds maxBytes");
|
|
6431
|
+
chunks.push(chunk.slice());
|
|
6432
|
+
}
|
|
6433
|
+
const bytes3 = new Uint8Array(size);
|
|
6434
|
+
let offset = 0;
|
|
6435
|
+
for (const chunk of chunks) {
|
|
6436
|
+
bytes3.set(chunk, offset);
|
|
6437
|
+
offset += chunk.byteLength;
|
|
6438
|
+
}
|
|
6439
|
+
return bytes3;
|
|
6440
|
+
})();
|
|
6441
|
+
if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
6442
|
+
if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
|
|
6443
|
+
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
6444
|
+
}
|
|
6445
|
+
if (target.kind === "bearer" && input.allowBinary !== true) {
|
|
6446
|
+
try {
|
|
6447
|
+
new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
6448
|
+
} catch {
|
|
6449
|
+
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
|
|
6453
|
+
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
6454
|
+
let totalBytes = 0;
|
|
6455
|
+
for (const file of files) {
|
|
6456
|
+
totalBytes += file.bytes.byteLength;
|
|
6457
|
+
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
6458
|
+
throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
const nowMs = input.now?.() ?? Date.now();
|
|
6462
|
+
const expiresAt = input.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
|
|
6463
|
+
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
6464
|
+
return input.targetAdapter.publish({
|
|
6465
|
+
source,
|
|
6466
|
+
filename: input.filename,
|
|
6467
|
+
...input.files === void 0 ? {} : { files: input.files },
|
|
6468
|
+
...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
|
|
6469
|
+
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
6470
|
+
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
6471
|
+
target,
|
|
6472
|
+
expiresAt,
|
|
6473
|
+
origin: input.origin,
|
|
6474
|
+
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
6475
|
+
});
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6478
|
+
// src/addressed-publish.ts
|
|
6479
|
+
var POLICY_V1_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
6480
|
+
var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
6481
|
+
var POLICY_CAPABILITY_V1_DOMAIN2 = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
6482
|
+
var CONTENT_SOURCE_V1_DOMAIN2 = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
6483
|
+
var NATIVE_PROJECTION_V1_DOMAIN2 = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
6484
|
+
var ENVELOPE_V3_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
6485
|
+
var textEncoder = new TextEncoder();
|
|
6486
|
+
function targetMatcher(target) {
|
|
6487
|
+
if (target.kind === "recipientDid") return { kind: "recipientDid", value: target.did };
|
|
6488
|
+
if (target.kind === "email") return { kind: "exactEmail", value: target.address };
|
|
6489
|
+
return { kind: "emailDomain", value: target.domain };
|
|
6490
|
+
}
|
|
6491
|
+
function targetKind(target) {
|
|
6492
|
+
return target.kind;
|
|
6493
|
+
}
|
|
6494
|
+
function assertSafeInput(input) {
|
|
6495
|
+
if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
|
|
6496
|
+
if (input.filename.length === 0 || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) throw new TypeError("addressed filename is invalid");
|
|
6497
|
+
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
|
|
6498
|
+
if (input.actions.length === 0 || input.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
6499
|
+
if (!Number.isFinite(input.expiresAt.getTime()) || input.expiresAt.getTime() <= Date.now()) throw new TypeError("addressed share expiry must be in the future");
|
|
6500
|
+
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");
|
|
6501
|
+
if (input.contentSource.shareId !== input.shareId || input.contentSource.selector !== input.resource.kind) throw new TypeError("addressed content source is not bound to the share");
|
|
6502
|
+
}
|
|
6503
|
+
function hex2(bytes3) {
|
|
6504
|
+
return [...bytes3].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
6505
|
+
}
|
|
6506
|
+
function digestHex(value, domain) {
|
|
6507
|
+
return hex2(sha2563(textEncoder.encode(`${domain}${canonicalize(value)}`)));
|
|
6508
|
+
}
|
|
6509
|
+
function sortCanonical(values) {
|
|
6510
|
+
return [...values].sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
|
|
6511
|
+
}
|
|
6512
|
+
function base32Lower2(bytes3) {
|
|
6513
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
6514
|
+
let output = "";
|
|
6515
|
+
let buffer = 0;
|
|
6516
|
+
let bits = 0;
|
|
6517
|
+
for (const byte of bytes3) {
|
|
6518
|
+
buffer = buffer << 8 | byte;
|
|
6519
|
+
bits += 8;
|
|
6520
|
+
while (bits >= 5) {
|
|
6521
|
+
output += alphabet[buffer >>> bits - 5 & 31];
|
|
6522
|
+
bits -= 5;
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
|
|
6526
|
+
return output;
|
|
6527
|
+
}
|
|
6528
|
+
function rfc3339Seconds(value) {
|
|
6529
|
+
return new Date(Math.floor(value.getTime() / 1e3) * 1e3).toISOString().replace(".000Z", "Z");
|
|
6530
|
+
}
|
|
6531
|
+
function nativeProjection(capabilities) {
|
|
6532
|
+
return sortCanonical(capabilities.map((capability) => capability.kind === "encryption" ? { service: "tinycloud.encryption", space: capability.resource, path: capability.resource, actions: [capability.action] } : {
|
|
6533
|
+
service: "tinycloud.kv",
|
|
6534
|
+
space: capability.resource.slice(0, capability.resource.indexOf("/kv/")),
|
|
6535
|
+
path: capability.resource.slice(capability.resource.indexOf("/kv/") + 4),
|
|
6536
|
+
actions: [...capability.actions],
|
|
6537
|
+
caveat: { type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: capability.resource }
|
|
6538
|
+
}));
|
|
6539
|
+
}
|
|
6540
|
+
async function createPolicy(options, capabilities) {
|
|
6541
|
+
const fields = {
|
|
6542
|
+
ownerDid: options.authority.ownerDid,
|
|
6543
|
+
createdAt: rfc3339Seconds(/* @__PURE__ */ new Date()),
|
|
6544
|
+
expiresAt: rfc3339Seconds(options.expiresAt),
|
|
6545
|
+
contentSource: options.contentSource,
|
|
6546
|
+
capabilityCeiling: [...capabilities]
|
|
6547
|
+
};
|
|
6548
|
+
const unsigned = options.credentialRequirement === void 0 ? { schema: "xyz.tinycloud.policy/policy/v1", ...fields } : { schema: "xyz.tinycloud.policy/policy/v2", ...fields, credentialRequirement: options.credentialRequirement };
|
|
6549
|
+
const domain = options.credentialRequirement === void 0 ? POLICY_V1_DOMAIN : POLICY_V2_DOMAIN;
|
|
6550
|
+
const policyDigestHex = digestHex(unsigned, domain);
|
|
6551
|
+
const signature = await options.authority.sign(sha2563(textEncoder.encode(`${domain}${canonicalize(unsigned)}`)));
|
|
6552
|
+
if (signature.byteLength !== 64) throw new TypeError("policy signature must be Ed25519");
|
|
6553
|
+
const policy = {
|
|
6554
|
+
...unsigned,
|
|
6555
|
+
policyId: `pol_${base32Lower2(Uint8Array.from(policyDigestHex.match(/../g), (byte) => Number.parseInt(byte, 16)))}`,
|
|
6556
|
+
signature: { suite: "Ed25519", signerDid: options.authority.ownerDid, value: toBase64Url(signature) }
|
|
6557
|
+
};
|
|
6558
|
+
return { policy, policyCid: await computeCid(textEncoder.encode(canonicalize(policy))), policyDigestHex };
|
|
6559
|
+
}
|
|
6560
|
+
function publicationResult(input) {
|
|
6561
|
+
const result = {
|
|
6562
|
+
protocol: "tinycloud-share",
|
|
6563
|
+
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
6564
|
+
url: input.url,
|
|
6565
|
+
link: { kind: "policy", cid: input.envelopeCid },
|
|
6566
|
+
metadata: {
|
|
6567
|
+
protocol: "tinycloud-share",
|
|
6568
|
+
version: 1,
|
|
6569
|
+
shareId: input.options.shareId,
|
|
6570
|
+
origin: input.options.shareOrigin,
|
|
6571
|
+
target: { kind: targetKind(input.options.target), origin: input.options.nodeOrigin, nodeAudience: input.enforcerDid, spaceId: input.options.spaceId },
|
|
6572
|
+
resource: { ...input.options.resource },
|
|
6573
|
+
actions: [...input.options.actions],
|
|
6574
|
+
expiresAt: input.expiry,
|
|
6575
|
+
display: { filename: input.options.filename },
|
|
6576
|
+
recipientMatcher: { ...input.matcher },
|
|
6577
|
+
policyCid: input.policyCid,
|
|
6578
|
+
ownerDelegationCid: input.policyRootCid,
|
|
6579
|
+
enforcementDelegationCid: input.enforcementRootCid,
|
|
6580
|
+
ownerDid: input.options.authority.ownerDid,
|
|
6581
|
+
enforcerDid: input.enforcerDid,
|
|
6582
|
+
envelopeCid: input.envelopeCid,
|
|
6583
|
+
shareCid: input.envelopeCid
|
|
6584
|
+
}
|
|
6585
|
+
};
|
|
6586
|
+
Object.defineProperty(result, "toJSON", { enumerable: false, value: () => redactPublishedShare(result) });
|
|
6587
|
+
Object.defineProperty(result, "url", { enumerable: false, value: input.url });
|
|
6588
|
+
Object.defineProperty(result, "deliveryMaterial", { enumerable: false, value: input.deliveryMaterial });
|
|
6589
|
+
return result;
|
|
6590
|
+
}
|
|
6591
|
+
async function publishAddressedShare(options) {
|
|
6592
|
+
assertSafeInput(options);
|
|
6593
|
+
const target = normalizeShareTarget(options.target);
|
|
6594
|
+
if (target.kind === "bearer") throw new TypeError("addressed target is required");
|
|
6595
|
+
const expiry = rfc3339Seconds(options.expiresAt);
|
|
6596
|
+
const matcher = targetMatcher(target);
|
|
6597
|
+
const capabilities = sortCanonical([
|
|
6598
|
+
{ kind: "kv", resource: options.contentSource.kvResource, selector: options.resource.kind, actions: [...options.policyActions] },
|
|
6599
|
+
{ kind: "encryption", resource: options.contentSource.encryptionNetwork, action: "tinycloud.encryption/decrypt" }
|
|
6600
|
+
]);
|
|
6601
|
+
const created = await createPolicy(options, capabilities);
|
|
6602
|
+
const contentSourceDigestHex = digestHex(options.contentSource, CONTENT_SOURCE_V1_DOMAIN2);
|
|
6603
|
+
const capabilityCeilingHashHex = digestHex(capabilities, POLICY_CAPABILITY_V1_DOMAIN2);
|
|
6604
|
+
const nativeProjectionHashHex = digestHex(nativeProjection(capabilities), NATIVE_PROJECTION_V1_DOMAIN2);
|
|
6605
|
+
const commonRoot = {
|
|
6606
|
+
ownerDid: options.authority.ownerDid,
|
|
6607
|
+
policyId: created.policy.policyId,
|
|
6608
|
+
policyDigestHex: created.policyDigestHex,
|
|
6609
|
+
policyCid: created.policyCid,
|
|
6610
|
+
contentSourceDigestHex,
|
|
6611
|
+
capabilityCeilingHashHex,
|
|
6612
|
+
nativeProjectionHashHex,
|
|
6613
|
+
notBefore: new Date(created.policy.createdAt),
|
|
6614
|
+
expiresAt: new Date(expiry),
|
|
6615
|
+
nodeAudience: options.nodeAudience,
|
|
6616
|
+
capabilities
|
|
6617
|
+
};
|
|
6618
|
+
const policyRootReceipt = await options.authority.createOwnerRoot({ ...commonRoot, role: "policy-authority", audienceDid: `did:tinycloud:policy:${created.policyDigestHex}` });
|
|
6619
|
+
const enforcementRootReceipt = await options.authority.createOwnerRoot({ ...commonRoot, role: "policy-enforcement", audienceDid: options.enforcerDid });
|
|
6620
|
+
const policyRoot = { cid: policyRootReceipt.cid, authorization: policyRootReceipt.delegationHeader.Authorization.replace(/^Bearer\s+/i, ""), role: "policy-authority" };
|
|
6621
|
+
const enforcementRoot = { cid: enforcementRootReceipt.cid, authorization: enforcementRootReceipt.delegationHeader.Authorization.replace(/^Bearer\s+/i, ""), role: "policy-enforcement" };
|
|
6622
|
+
const registration = await options.authority.registerPolicy({
|
|
6623
|
+
policyCid: created.policyCid,
|
|
6624
|
+
policy: created.policy,
|
|
6625
|
+
policyRoot,
|
|
6626
|
+
enforcementRoot,
|
|
6627
|
+
contentSourceDigestHex,
|
|
6628
|
+
nativeProjectionHashHex,
|
|
6629
|
+
rootExpiresAt: expiry,
|
|
6630
|
+
enforcerDid: options.enforcerDid,
|
|
6631
|
+
expectedNodeAudience: options.nodeAudience
|
|
6632
|
+
});
|
|
6633
|
+
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");
|
|
6634
|
+
const unsigned = {
|
|
6635
|
+
version: 3,
|
|
6636
|
+
shareId: options.shareId,
|
|
6637
|
+
recipientMatcher: matcher,
|
|
6638
|
+
...options.deliveryEmail === void 0 ? {} : { deliveryEmail: options.deliveryEmail },
|
|
6639
|
+
actions: [...options.actions],
|
|
6640
|
+
resource: { ...options.resource },
|
|
6641
|
+
target: { origin: options.nodeOrigin, nodeAudience: registration.attestedEnforcerBinding.enforcerDid, spaceId: options.spaceId },
|
|
6642
|
+
policy: created.policy,
|
|
6643
|
+
policyCid: created.policyCid,
|
|
6644
|
+
policyRoot,
|
|
6645
|
+
enforcementRoot,
|
|
6646
|
+
attestedEnforcerBinding: registration.attestedEnforcerBinding,
|
|
6647
|
+
contentSource: options.contentSource,
|
|
6648
|
+
contentSourceDigestHex,
|
|
6649
|
+
encryptionNetwork: options.contentSource.encryptionNetwork,
|
|
6650
|
+
expiry,
|
|
6651
|
+
display: { filename: options.filename },
|
|
6652
|
+
encrypted: true,
|
|
6653
|
+
metadata: {
|
|
6654
|
+
mediaType: options.mediaType,
|
|
6655
|
+
byteLength: options.byteLength,
|
|
6656
|
+
filename: options.filename,
|
|
6657
|
+
...options.mediaType.startsWith("text/") ? { encoding: "utf-8" } : {},
|
|
6658
|
+
...options.artifact === void 0 ? {} : { artifact: options.artifact }
|
|
6659
|
+
}
|
|
6660
|
+
};
|
|
6661
|
+
unsignedShareEnvelopeV3Schema.parse(unsigned);
|
|
6662
|
+
const envelopeSignature = await options.authority.sign(sha2563(textEncoder.encode(`${ENVELOPE_V3_DOMAIN}${canonicalize(unsigned)}`)));
|
|
6663
|
+
if (envelopeSignature.byteLength !== 64) throw new TypeError("v3 envelope signature must be Ed25519");
|
|
6664
|
+
const envelope = { ...unsigned, signature: { signerDid: options.authority.ownerDid, algorithm: "Ed25519", value: toBase64Url(envelopeSignature) } };
|
|
6665
|
+
shareEnvelopeV3Schema.parse(envelope);
|
|
6666
|
+
const envelopeBytes = textEncoder.encode(canonicalize(envelope));
|
|
6667
|
+
const envelopeCid = await computeCid(envelopeBytes);
|
|
6668
|
+
const url = await encodePublicInlineShareUrl({ origin: options.shareOrigin, plaintext: envelopeBytes });
|
|
6669
|
+
const deliveryMaterial = { envelope, shareCid: envelopeCid };
|
|
6670
|
+
options.onDeliveryMaterial?.(deliveryMaterial);
|
|
6671
|
+
return publicationResult({
|
|
6672
|
+
options,
|
|
6673
|
+
url,
|
|
6674
|
+
envelopeCid,
|
|
6675
|
+
matcher,
|
|
6676
|
+
policyCid: created.policyCid,
|
|
6677
|
+
policyRootCid: policyRoot.cid,
|
|
6678
|
+
enforcementRootCid: enforcementRoot.cid,
|
|
6679
|
+
enforcerDid: registration.attestedEnforcerBinding.enforcerDid,
|
|
6680
|
+
expiry,
|
|
6681
|
+
deliveryMaterial
|
|
6682
|
+
});
|
|
6683
|
+
}
|
|
6684
|
+
|
|
6685
|
+
// src/cache.ts
|
|
6686
|
+
import { sha256 as sha2564 } from "@noble/hashes/sha256";
|
|
6687
|
+
function cacheKey(key) {
|
|
6688
|
+
if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
|
|
6689
|
+
return `${key.cid}\0${key.expiresAt}`;
|
|
6690
|
+
}
|
|
6691
|
+
function digest(bytes3) {
|
|
6692
|
+
return sha2564(bytes3);
|
|
6693
|
+
}
|
|
6694
|
+
function equalBytes(left, right) {
|
|
6695
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
6696
|
+
return left.every((byte, index) => byte === right[index]);
|
|
6697
|
+
}
|
|
6698
|
+
var MemoryShareCache = class {
|
|
6699
|
+
constructor(now = () => /* @__PURE__ */ new Date()) {
|
|
6700
|
+
this.now = now;
|
|
6701
|
+
}
|
|
6702
|
+
entries = /* @__PURE__ */ new Map();
|
|
6703
|
+
async get(key, now = /* @__PURE__ */ new Date()) {
|
|
6704
|
+
const mapKey = cacheKey(key);
|
|
6705
|
+
const entry = this.entries.get(mapKey);
|
|
6706
|
+
if (entry === void 0) return void 0;
|
|
6707
|
+
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
|
|
6708
|
+
this.entries.delete(mapKey);
|
|
6709
|
+
return void 0;
|
|
6710
|
+
}
|
|
6711
|
+
return entry.bytes.slice();
|
|
6712
|
+
}
|
|
6713
|
+
async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
|
|
6714
|
+
const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
|
|
6715
|
+
for (const entry of entries) {
|
|
6716
|
+
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
|
|
6717
|
+
this.entries.delete(cacheKey(entry.key));
|
|
6718
|
+
continue;
|
|
6719
|
+
}
|
|
6720
|
+
return {
|
|
6721
|
+
key: { ...entry.key },
|
|
6722
|
+
bytes: entry.bytes.slice(),
|
|
6723
|
+
digest: entry.digest.slice(),
|
|
6724
|
+
metadata: { ...entry.metadata }
|
|
6725
|
+
};
|
|
6726
|
+
}
|
|
6727
|
+
return void 0;
|
|
6728
|
+
}
|
|
6729
|
+
async set(key, bytes3, metadata) {
|
|
6730
|
+
if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
|
|
6731
|
+
this.entries.set(cacheKey(key), {
|
|
6732
|
+
key: { cid: key.cid, expiresAt: key.expiresAt },
|
|
6733
|
+
bytes: bytes3.slice(),
|
|
6734
|
+
digest: digest(bytes3),
|
|
6735
|
+
metadata: { ...metadata }
|
|
6736
|
+
});
|
|
6737
|
+
}
|
|
6738
|
+
async delete(key) {
|
|
6739
|
+
this.entries.delete(cacheKey(key));
|
|
6740
|
+
}
|
|
6741
|
+
async clear() {
|
|
6742
|
+
this.entries.clear();
|
|
6743
|
+
}
|
|
6744
|
+
};
|
|
6745
|
+
|
|
6746
|
+
// src/owner-policy.ts
|
|
6747
|
+
import { ed25519 as ed255194 } from "@noble/curves/ed25519";
|
|
6748
|
+
import { sha256 as sha2565 } from "@noble/hashes/sha256";
|
|
6749
|
+
|
|
6750
|
+
// ../../node_modules/multiformats/dist/src/bases/base32.js
|
|
6751
|
+
var base322 = rfc46482({
|
|
6752
|
+
prefix: "b",
|
|
7045
6753
|
name: "base32",
|
|
7046
6754
|
alphabet: "abcdefghijklmnopqrstuvwxyz234567",
|
|
7047
6755
|
bitsPerChar: 5
|
|
@@ -7107,18 +6815,6 @@ var base36upper2 = baseX2({
|
|
|
7107
6815
|
alphabet: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
7108
6816
|
});
|
|
7109
6817
|
|
|
7110
|
-
// ../../node_modules/multiformats/dist/src/bases/base58.js
|
|
7111
|
-
var base58btc2 = baseX2({
|
|
7112
|
-
name: "base58btc",
|
|
7113
|
-
prefix: "z",
|
|
7114
|
-
alphabet: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
7115
|
-
});
|
|
7116
|
-
var base58flickr2 = baseX2({
|
|
7117
|
-
name: "base58flickr",
|
|
7118
|
-
prefix: "Z",
|
|
7119
|
-
alphabet: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
7120
|
-
});
|
|
7121
|
-
|
|
7122
6818
|
// ../../node_modules/multiformats/dist/src/vendor/varint.js
|
|
7123
6819
|
var encode_12 = encode4;
|
|
7124
6820
|
var MSB2 = 128;
|
|
@@ -7192,25 +6888,25 @@ function encodingLength2(int) {
|
|
|
7192
6888
|
}
|
|
7193
6889
|
|
|
7194
6890
|
// ../../node_modules/multiformats/dist/src/hashes/digest.js
|
|
7195
|
-
function create2(code3,
|
|
7196
|
-
const size =
|
|
6891
|
+
function create2(code3, digest3) {
|
|
6892
|
+
const size = digest3.byteLength;
|
|
7197
6893
|
const sizeOffset = encodingLength2(code3);
|
|
7198
6894
|
const digestOffset = sizeOffset + encodingLength2(size);
|
|
7199
6895
|
const bytes3 = new Uint8Array(digestOffset + size);
|
|
7200
6896
|
encodeTo2(code3, bytes3, 0);
|
|
7201
6897
|
encodeTo2(size, bytes3, sizeOffset);
|
|
7202
|
-
bytes3.set(
|
|
7203
|
-
return new Digest2(code3, size,
|
|
6898
|
+
bytes3.set(digest3, digestOffset);
|
|
6899
|
+
return new Digest2(code3, size, digest3, bytes3);
|
|
7204
6900
|
}
|
|
7205
6901
|
function decode8(multihash) {
|
|
7206
6902
|
const bytes3 = coerce3(multihash);
|
|
7207
6903
|
const [code3, sizeOffset] = decode7(bytes3);
|
|
7208
6904
|
const [size, digestOffset] = decode7(bytes3.subarray(sizeOffset));
|
|
7209
|
-
const
|
|
7210
|
-
if (
|
|
6905
|
+
const digest3 = bytes3.subarray(sizeOffset + digestOffset);
|
|
6906
|
+
if (digest3.byteLength !== size) {
|
|
7211
6907
|
throw new Error("Incorrect length");
|
|
7212
6908
|
}
|
|
7213
|
-
return new Digest2(code3, size,
|
|
6909
|
+
return new Digest2(code3, size, digest3, bytes3);
|
|
7214
6910
|
}
|
|
7215
6911
|
function equals4(a, b) {
|
|
7216
6912
|
if (a === b) {
|
|
@@ -7228,10 +6924,10 @@ var Digest2 = class {
|
|
|
7228
6924
|
/**
|
|
7229
6925
|
* Creates a multihash digest.
|
|
7230
6926
|
*/
|
|
7231
|
-
constructor(code3, size,
|
|
6927
|
+
constructor(code3, size, digest3, bytes3) {
|
|
7232
6928
|
this.code = code3;
|
|
7233
6929
|
this.size = size;
|
|
7234
|
-
this.digest =
|
|
6930
|
+
this.digest = digest3;
|
|
7235
6931
|
this.bytes = bytes3;
|
|
7236
6932
|
}
|
|
7237
6933
|
};
|
|
@@ -7314,8 +7010,8 @@ var CID2 = class _CID2 {
|
|
|
7314
7010
|
toV1() {
|
|
7315
7011
|
switch (this.version) {
|
|
7316
7012
|
case 0: {
|
|
7317
|
-
const { code: code3, digest:
|
|
7318
|
-
const multihash = create2(code3,
|
|
7013
|
+
const { code: code3, digest: digest3 } = this.multihash;
|
|
7014
|
+
const multihash = create2(code3, digest3);
|
|
7319
7015
|
return _CID2.createV1(this.code, multihash);
|
|
7320
7016
|
}
|
|
7321
7017
|
case 1: {
|
|
@@ -7369,8 +7065,8 @@ var CID2 = class _CID2 {
|
|
|
7369
7065
|
return new _CID2(version, code3, multihash, bytes3 ?? encodeCID2(version, code3, multihash.bytes));
|
|
7370
7066
|
} else if (value[cidSymbol2] === true) {
|
|
7371
7067
|
const { version, multihash, code: code3 } = value;
|
|
7372
|
-
const
|
|
7373
|
-
return _CID2.create(version, code3,
|
|
7068
|
+
const digest3 = decode8(multihash);
|
|
7069
|
+
return _CID2.create(version, code3, digest3);
|
|
7374
7070
|
} else {
|
|
7375
7071
|
return null;
|
|
7376
7072
|
}
|
|
@@ -7380,11 +7076,11 @@ var CID2 = class _CID2 {
|
|
|
7380
7076
|
* @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
|
|
7381
7077
|
* @param digest - (Multi)hash of the of the content.
|
|
7382
7078
|
*/
|
|
7383
|
-
static create(version, code3,
|
|
7079
|
+
static create(version, code3, digest3) {
|
|
7384
7080
|
if (typeof code3 !== "number") {
|
|
7385
7081
|
throw new Error("String codecs are no longer supported");
|
|
7386
7082
|
}
|
|
7387
|
-
if (!(
|
|
7083
|
+
if (!(digest3.bytes instanceof Uint8Array)) {
|
|
7388
7084
|
throw new Error("Invalid digest");
|
|
7389
7085
|
}
|
|
7390
7086
|
switch (version) {
|
|
@@ -7392,12 +7088,12 @@ var CID2 = class _CID2 {
|
|
|
7392
7088
|
if (code3 !== DAG_PB_CODE2) {
|
|
7393
7089
|
throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE2}) block encoding`);
|
|
7394
7090
|
} else {
|
|
7395
|
-
return new _CID2(version, code3,
|
|
7091
|
+
return new _CID2(version, code3, digest3, digest3.bytes);
|
|
7396
7092
|
}
|
|
7397
7093
|
}
|
|
7398
7094
|
case 1: {
|
|
7399
|
-
const bytes3 = encodeCID2(version, code3,
|
|
7400
|
-
return new _CID2(version, code3,
|
|
7095
|
+
const bytes3 = encodeCID2(version, code3, digest3.bytes);
|
|
7096
|
+
return new _CID2(version, code3, digest3, bytes3);
|
|
7401
7097
|
}
|
|
7402
7098
|
default: {
|
|
7403
7099
|
throw new Error("Invalid version");
|
|
@@ -7407,8 +7103,8 @@ var CID2 = class _CID2 {
|
|
|
7407
7103
|
/**
|
|
7408
7104
|
* Simplified version of `create` for CIDv0.
|
|
7409
7105
|
*/
|
|
7410
|
-
static createV0(
|
|
7411
|
-
return _CID2.create(0, DAG_PB_CODE2,
|
|
7106
|
+
static createV0(digest3) {
|
|
7107
|
+
return _CID2.create(0, DAG_PB_CODE2, digest3);
|
|
7412
7108
|
}
|
|
7413
7109
|
/**
|
|
7414
7110
|
* Simplified version of `create` for CIDv1.
|
|
@@ -7416,8 +7112,8 @@ var CID2 = class _CID2 {
|
|
|
7416
7112
|
* @param code - Content encoding format code.
|
|
7417
7113
|
* @param digest - Multihash of the content.
|
|
7418
7114
|
*/
|
|
7419
|
-
static createV1(code3,
|
|
7420
|
-
return _CID2.create(1, code3,
|
|
7115
|
+
static createV1(code3, digest3) {
|
|
7116
|
+
return _CID2.create(1, code3, digest3);
|
|
7421
7117
|
}
|
|
7422
7118
|
/**
|
|
7423
7119
|
* Decoded a CID from its binary representation. The byte array must contain
|
|
@@ -7450,8 +7146,8 @@ var CID2 = class _CID2 {
|
|
|
7450
7146
|
throw new Error("Incorrect length");
|
|
7451
7147
|
}
|
|
7452
7148
|
const digestBytes3 = multihashBytes.subarray(specs.multihashSize - specs.digestSize);
|
|
7453
|
-
const
|
|
7454
|
-
const cid2 = specs.version === 0 ? _CID2.createV0(
|
|
7149
|
+
const digest3 = new Digest2(specs.multihashCode, specs.digestSize, digestBytes3, multihashBytes);
|
|
7150
|
+
const cid2 = specs.version === 0 ? _CID2.createV0(digest3) : _CID2.createV1(specs.codec, digest3);
|
|
7455
7151
|
return [cid2, bytes3.subarray(specs.size)];
|
|
7456
7152
|
}
|
|
7457
7153
|
/**
|
|
@@ -7603,10 +7299,10 @@ function fromB64(value) {
|
|
|
7603
7299
|
throw new Error("base64url decoding is unavailable");
|
|
7604
7300
|
}
|
|
7605
7301
|
function cid(bytesValue) {
|
|
7606
|
-
return CID2.createV1(code2, create2(SHA256_CODE2,
|
|
7302
|
+
return CID2.createV1(code2, create2(SHA256_CODE2, sha2565(bytesValue))).toString(base322.encoder);
|
|
7607
7303
|
}
|
|
7608
|
-
function
|
|
7609
|
-
return b64(
|
|
7304
|
+
function digest2(bytesValue) {
|
|
7305
|
+
return b64(sha2565(bytesValue));
|
|
7610
7306
|
}
|
|
7611
7307
|
function didKeyFromEd25519PublicKey2(publicKey) {
|
|
7612
7308
|
if (publicKey.length !== 32) throw new Error("share key public key must be 32 bytes");
|
|
@@ -7649,824 +7345,152 @@ function dagCborEncode(value) {
|
|
|
7649
7345
|
return;
|
|
7650
7346
|
}
|
|
7651
7347
|
if (Array.isArray(item)) {
|
|
7652
|
-
writeHeader(4, item.length);
|
|
7653
|
-
item.forEach(write);
|
|
7654
|
-
return;
|
|
7655
|
-
}
|
|
7656
|
-
if (typeof item === "object" && item !== null) {
|
|
7657
|
-
const entries = Object.entries(item).map(([key, value2]) => {
|
|
7658
|
-
const keyBytes = dagCborEncode(key);
|
|
7659
|
-
return { key, value: value2, keyBytes };
|
|
7660
|
-
}).sort((left, right) => {
|
|
7661
|
-
if (left.keyBytes.length !== right.keyBytes.length) return left.keyBytes.length - right.keyBytes.length;
|
|
7662
|
-
for (let index = 0; index < left.keyBytes.length; index += 1) {
|
|
7663
|
-
const leftByte = left.keyBytes[index] ?? 0;
|
|
7664
|
-
const rightByte = right.keyBytes[index] ?? 0;
|
|
7665
|
-
if (leftByte !== rightByte) return leftByte - rightByte;
|
|
7666
|
-
}
|
|
7667
|
-
return 0;
|
|
7668
|
-
});
|
|
7669
|
-
writeHeader(5, entries.length);
|
|
7670
|
-
entries.forEach(({ key, value: value2 }) => {
|
|
7671
|
-
write(key);
|
|
7672
|
-
write(value2);
|
|
7673
|
-
});
|
|
7674
|
-
return;
|
|
7675
|
-
}
|
|
7676
|
-
throw new Error("Unsupported DAG-CBOR value");
|
|
7677
|
-
};
|
|
7678
|
-
write(value);
|
|
7679
|
-
return Uint8Array.from(output);
|
|
7680
|
-
}
|
|
7681
|
-
function assertObject(value, keys, label) {
|
|
7682
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
7683
|
-
const record2 = value;
|
|
7684
|
-
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`);
|
|
7685
|
-
return record2;
|
|
7686
|
-
}
|
|
7687
|
-
async function createDelegatedShareKey(options) {
|
|
7688
|
-
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7689
|
-
const generated = await crypto.subtle.generateKey({ name: "Ed25519" }, options.extractable, ["sign", "verify"]);
|
|
7690
|
-
const privateKey = generated.privateKey;
|
|
7691
|
-
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", generated.publicKey));
|
|
7692
|
-
let cleared = false;
|
|
7693
|
-
const key = {
|
|
7694
|
-
did: didKeyFromEd25519PublicKey2(publicKey),
|
|
7695
|
-
publicKey: bytes(publicKey),
|
|
7696
|
-
extractable: options.extractable,
|
|
7697
|
-
cryptoKey: privateKey,
|
|
7698
|
-
async sign(input) {
|
|
7699
|
-
if (cleared) throw new Error("share key has been cleared");
|
|
7700
|
-
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, privateKey, input));
|
|
7701
|
-
},
|
|
7702
|
-
clear() {
|
|
7703
|
-
cleared = true;
|
|
7704
|
-
}
|
|
7705
|
-
};
|
|
7706
|
-
return key;
|
|
7707
|
-
}
|
|
7708
|
-
function restoreDelegatedShareKey(input) {
|
|
7709
|
-
let cleared = false;
|
|
7710
|
-
return {
|
|
7711
|
-
did: didKeyFromEd25519PublicKey2(input.publicKey),
|
|
7712
|
-
publicKey: bytes(input.publicKey),
|
|
7713
|
-
extractable: input.extractable,
|
|
7714
|
-
cryptoKey: input.privateKey,
|
|
7715
|
-
async sign(signBytes) {
|
|
7716
|
-
if (cleared) throw new Error("share key has been cleared");
|
|
7717
|
-
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7718
|
-
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, input.privateKey, signBytes));
|
|
7719
|
-
},
|
|
7720
|
-
clear() {
|
|
7721
|
-
cleared = true;
|
|
7722
|
-
}
|
|
7723
|
-
};
|
|
7724
|
-
}
|
|
7725
|
-
async function createPolicyEnforcementDelegation(input) {
|
|
7726
|
-
if (input.shareKey.did.length === 0 || input.ownerDelegation.delegationCid.length === 0) throw new Error("owner delegation and share key are required");
|
|
7727
|
-
const facts = {
|
|
7728
|
-
ownerDelegationCid: input.ownerDelegation.delegationCid,
|
|
7729
|
-
policyCid: input.policyCid,
|
|
7730
|
-
shareId: input.shareId,
|
|
7731
|
-
shareKeyDid: input.shareKey.did,
|
|
7732
|
-
enforcerDid: input.enforcerDid,
|
|
7733
|
-
nodeAudience: input.nodeAudience,
|
|
7734
|
-
spaceId: input.spaceId,
|
|
7735
|
-
path: input.path,
|
|
7736
|
-
actions: [...input.actions],
|
|
7737
|
-
contentSourceDigest: input.contentSourceDigest,
|
|
7738
|
-
expiresAt: input.expiresAt
|
|
7739
|
-
};
|
|
7740
|
-
const unsigned = { type: "TinyCloudSharePolicyEnforcement", version: 2, issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts };
|
|
7741
|
-
const dagCborBytes = dagCborEncode({ domain: POLICY_ENFORCEMENT_DOMAIN, unsigned });
|
|
7742
|
-
const signature = b64(await input.shareKey.sign(dagCborBytes));
|
|
7743
|
-
return { cid: cid(dagCborBytes), dagCbor: b64(dagCborBytes), issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts, signature };
|
|
7744
|
-
}
|
|
7745
|
-
async function canonicalOwnerSharePolicy(policy) {
|
|
7746
|
-
const text = canonicalize({ domain: POLICY_DOMAIN, policy });
|
|
7747
|
-
const policyBytes = new TextEncoder().encode(text);
|
|
7748
|
-
return { bytes: policyBytes, cid: cid(policyBytes), digest: digest(policyBytes) };
|
|
7749
|
-
}
|
|
7750
|
-
function computeOwnerShareRegistrationCid(registration) {
|
|
7751
|
-
return cid(new TextEncoder().encode(canonicalize(registration)));
|
|
7752
|
-
}
|
|
7753
|
-
function validateOwnerSharePolicyRegistration(value, expected) {
|
|
7754
|
-
if (cid(expected.policy.bytes) !== expected.policy.cid) throw new Error("submitted owner-share policy bytes do not match its CID");
|
|
7755
|
-
const root = assertObject(value, ["registration", "proof"], "owner-share registration response");
|
|
7756
|
-
const registration = assertObject(root.registration, ["registrationCid", "policyCid", "ownerDelegationCid", "enforcementDelegationCid", "ownerDid", "shareKeyDid", "enforcerDid", "shareId", "recipientMatcher", "target", "resource", "actions", "contentSource", "contentSourceDigest", "registeredAt", "expiresAt"], "owner-share registration");
|
|
7757
|
-
const proof = assertObject(root.proof, ["alg", "kid", "signature"], "owner-share registration proof");
|
|
7758
|
-
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");
|
|
7759
|
-
let policyValue;
|
|
7760
|
-
try {
|
|
7761
|
-
policyValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(expected.policy.bytes));
|
|
7762
|
-
} catch {
|
|
7763
|
-
throw new Error("owner-share policy is not valid JSON");
|
|
7764
|
-
}
|
|
7765
|
-
const policy = policyValue.policy;
|
|
7766
|
-
if (policyValue.domain !== "xyz.tinycloud.share/policy/v2\0" || policy === void 0 || Array.isArray(policy)) throw new Error("owner-share policy envelope is invalid");
|
|
7767
|
-
const target = policy.target;
|
|
7768
|
-
const resource = policy.resource;
|
|
7769
|
-
const registrationTarget = registration.target;
|
|
7770
|
-
const registrationResource = registration.resource;
|
|
7771
|
-
const enforcementFacts = expected.enforcementDelegation.facts;
|
|
7772
|
-
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");
|
|
7773
|
-
if (typeof registration.registrationCid !== "string" || typeof registration.expiresAt !== "string" || typeof registration.registeredAt !== "string") throw new Error("owner-share registration timestamps are invalid");
|
|
7774
|
-
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");
|
|
7775
|
-
const { registrationCid: _registrationCid, ...registrationCore } = registration;
|
|
7776
|
-
if (computeOwnerShareRegistrationCid(registrationCore) !== registration.registrationCid) throw new Error("owner-share registration CID does not match its canonical core");
|
|
7777
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("owner-share registration proof is invalid");
|
|
7778
|
-
const proofKey = expected.nodeProof;
|
|
7779
|
-
if (proofKey === void 0) throw new Error("owner-share registration proof trust material is required");
|
|
7780
|
-
if (proof.kid !== proofKey.kid) throw new Error("owner-share registration proof key is not trusted");
|
|
7781
|
-
const encodedKid = proofKey.publicKey;
|
|
7782
|
-
if (encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1) {
|
|
7783
|
-
} else if (encodedKid.length !== 32) throw new Error("owner-share registration proof key is invalid");
|
|
7784
|
-
const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
|
|
7785
|
-
const signatureBytes = fromB64(proof.signature);
|
|
7786
|
-
const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
|
|
7787
|
-
if (signatureBytes.length !== 64 || !ed255195.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
|
|
7788
|
-
return { registration, proof };
|
|
7789
|
-
}
|
|
7790
|
-
function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
7791
|
-
let value;
|
|
7792
|
-
try {
|
|
7793
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
7794
|
-
} catch {
|
|
7795
|
-
throw new Error("owner-share registration response is not valid UTF-8 JSON");
|
|
7796
|
-
}
|
|
7797
|
-
return validateOwnerSharePolicyRegistration(value, expected);
|
|
7798
|
-
}
|
|
7799
|
-
|
|
7800
|
-
// src/authorization.ts
|
|
7801
|
-
async function authorizeShare(input) {
|
|
7802
|
-
if (input.resumeToken !== void 0) {
|
|
7803
|
-
if (input.resumeToken.length < 16 || input.resumeToken.length > 512) {
|
|
7804
|
-
return { state: "denied", reason: "rejected" };
|
|
7805
|
-
}
|
|
7806
|
-
return input.adapter.resume({
|
|
7807
|
-
envelope: input.envelope,
|
|
7808
|
-
method: input.method,
|
|
7809
|
-
resumeToken: input.resumeToken,
|
|
7810
|
-
...input.proof === void 0 ? {} : { proof: input.proof }
|
|
7811
|
-
});
|
|
7812
|
-
}
|
|
7813
|
-
return input.adapter.begin({ envelope: input.envelope, method: input.method });
|
|
7814
|
-
}
|
|
7815
|
-
function authorizationMethodForTarget(target) {
|
|
7816
|
-
if (target.kind === "recipientDid") return "openkey-device";
|
|
7817
|
-
if (target.kind === "email") return "email-claim";
|
|
7818
|
-
if (target.kind === "emailDomain") return "email-claim";
|
|
7819
|
-
return void 0;
|
|
7820
|
-
}
|
|
7821
|
-
|
|
7822
|
-
// src/targets.ts
|
|
7823
|
-
function validEmail(value) {
|
|
7824
|
-
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);
|
|
7825
|
-
}
|
|
7826
|
-
function validDomain(value) {
|
|
7827
|
-
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);
|
|
7828
|
-
}
|
|
7829
|
-
function validRecipientDid(value) {
|
|
7830
|
-
if (value.length === 0 || value.length > 2048 || /[\u0000-\u0020\u007f]/.test(value)) return false;
|
|
7831
|
-
const parts = value.split(":");
|
|
7832
|
-
if (parts.length < 3 || parts[0] !== "did" || !/^[a-z0-9]+$/.test(parts[1] ?? "")) return false;
|
|
7833
|
-
const identifier = parts.slice(2);
|
|
7834
|
-
if (identifier.some((part) => part.length === 0)) return false;
|
|
7835
|
-
if (parts[1] === "web") {
|
|
7836
|
-
const host = identifier[0] ?? "";
|
|
7837
|
-
if (host.length > 253 || host.split(".").some((label) => !label || label.length > 63 || !/^[A-Za-z0-9-]+$/.test(label) || label.startsWith("-") || label.endsWith("-"))) return false;
|
|
7838
|
-
return identifier.slice(1).every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
7839
|
-
}
|
|
7840
|
-
if (parts[1] === "pkh") return identifier.length >= 3 && identifier.every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
|
|
7841
|
-
if (parts[1] === "key") {
|
|
7842
|
-
try {
|
|
7843
|
-
const bytes3 = base58btc2.decode(identifier.join(":"));
|
|
7844
|
-
return bytes3.length === 34 && bytes3[0] === 237 && bytes3[1] === 1;
|
|
7845
|
-
} catch {
|
|
7846
|
-
return false;
|
|
7847
|
-
}
|
|
7848
|
-
}
|
|
7849
|
-
return false;
|
|
7850
|
-
}
|
|
7851
|
-
function normalizeShareTarget(target) {
|
|
7852
|
-
if (target.kind === "bearer") return target;
|
|
7853
|
-
if (target.kind === "recipientDid") {
|
|
7854
|
-
if (!validRecipientDid(target.did)) throw new TypeError("recipient DID is invalid");
|
|
7855
|
-
return { kind: target.kind, did: target.did };
|
|
7856
|
-
}
|
|
7857
|
-
if (target.kind === "email") {
|
|
7858
|
-
if (!validEmail(target.address)) throw new TypeError("recipient email is invalid");
|
|
7859
|
-
const at = target.address.lastIndexOf("@");
|
|
7860
|
-
return { kind: target.kind, address: `${target.address.slice(0, at)}@${target.address.slice(at + 1).toLowerCase()}` };
|
|
7861
|
-
}
|
|
7862
|
-
if (!validDomain(target.domain)) throw new TypeError("recipient email domain is invalid");
|
|
7863
|
-
return { kind: target.kind, domain: target.domain.toLowerCase() };
|
|
7864
|
-
}
|
|
7865
|
-
function targetAuthorizationMethod(target) {
|
|
7866
|
-
return authorizationMethodForTarget(target);
|
|
7867
|
-
}
|
|
7868
|
-
async function publishTargetShare(input) {
|
|
7869
|
-
const target = normalizeShareTarget(input.target);
|
|
7870
|
-
if (target.kind === "bearer") return publishShare(input);
|
|
7871
|
-
if (input.targetAdapter === void 0) {
|
|
7872
|
-
return {
|
|
7873
|
-
state: "authorization-required",
|
|
7874
|
-
method: targetAuthorizationMethod(target)
|
|
7875
|
-
};
|
|
7876
|
-
}
|
|
7877
|
-
const source = input.source instanceof Uint8Array ? input.source.slice() : await (async () => {
|
|
7878
|
-
const chunks = [];
|
|
7879
|
-
let size = 0;
|
|
7880
|
-
for await (const chunk of input.source) {
|
|
7881
|
-
if (!(chunk instanceof Uint8Array)) throw new TypeError("addressed publication source yielded invalid bytes");
|
|
7882
|
-
size += chunk.byteLength;
|
|
7883
|
-
if (size > (input.maxBytes ?? 100 * 1024 * 1024)) throw new TypeError("addressed publication exceeds maxBytes");
|
|
7884
|
-
chunks.push(chunk.slice());
|
|
7885
|
-
}
|
|
7886
|
-
const bytes3 = new Uint8Array(size);
|
|
7887
|
-
let offset = 0;
|
|
7888
|
-
for (const chunk of chunks) {
|
|
7889
|
-
bytes3.set(chunk, offset);
|
|
7890
|
-
offset += chunk.byteLength;
|
|
7891
|
-
}
|
|
7892
|
-
return bytes3;
|
|
7893
|
-
})();
|
|
7894
|
-
const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
|
|
7895
|
-
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
7896
|
-
let totalBytes = 0;
|
|
7897
|
-
for (const file of files) {
|
|
7898
|
-
totalBytes += file.bytes.byteLength;
|
|
7899
|
-
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
7900
|
-
throw new SharePublishError("max-bytes-exceeded", "addressed publication exceeds the combined byte limit");
|
|
7901
|
-
}
|
|
7902
|
-
}
|
|
7903
|
-
return input.targetAdapter.publish({
|
|
7904
|
-
source,
|
|
7905
|
-
filename: input.filename,
|
|
7906
|
-
...input.files === void 0 ? {} : { files: input.files },
|
|
7907
|
-
...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
|
|
7908
|
-
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
7909
|
-
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
7910
|
-
target,
|
|
7911
|
-
expiresAt: input.expiresAt ?? new Date((input.now?.() ?? Date.now()) + 7 * 24 * 60 * 60 * 1e3),
|
|
7912
|
-
origin: input.origin,
|
|
7913
|
-
...input.inline === void 0 ? {} : { inline: input.inline },
|
|
7914
|
-
upload: {
|
|
7915
|
-
...input.registryBaseUrl === void 0 ? {} : { registryBaseUrl: input.registryBaseUrl },
|
|
7916
|
-
...input.fetchFn === void 0 ? {} : { fetchFn: input.fetchFn },
|
|
7917
|
-
...input.authorizeUpload === void 0 ? {} : { authorizeUpload: input.authorizeUpload },
|
|
7918
|
-
...input.authorizationOrigin === void 0 ? {} : { authorizationOrigin: input.authorizationOrigin },
|
|
7919
|
-
...input.credentials === void 0 ? {} : { credentials: input.credentials },
|
|
7920
|
-
...input.allowInsecureRegistry === void 0 ? {} : { allowInsecureRegistry: input.allowInsecureRegistry },
|
|
7921
|
-
...input.uploadBlob === void 0 ? {} : { uploadBlob: input.uploadBlob }
|
|
7922
|
-
},
|
|
7923
|
-
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
7924
|
-
});
|
|
7925
|
-
}
|
|
7926
|
-
|
|
7927
|
-
// src/addressed-publish.ts
|
|
7928
|
-
var ENVELOPE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
7929
|
-
function targetMatcher(target) {
|
|
7930
|
-
if (target.kind === "recipientDid") return { kind: "recipientDid", value: target.did };
|
|
7931
|
-
if (target.kind === "email") return { kind: "exactEmail", value: target.address };
|
|
7932
|
-
return { kind: "emailDomain", value: target.domain };
|
|
7933
|
-
}
|
|
7934
|
-
function targetKind(target) {
|
|
7935
|
-
return target.kind;
|
|
7936
|
-
}
|
|
7937
|
-
function assertSafeInput(input) {
|
|
7938
|
-
if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
|
|
7939
|
-
if (input.filename.length === 0 || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) throw new TypeError("addressed filename is invalid");
|
|
7940
|
-
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
|
|
7941
|
-
if (input.actions.length === 0 || input.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
7942
|
-
const expiry = input.expiresAt.getTime();
|
|
7943
|
-
if (!Number.isFinite(expiry) || expiry <= Date.now()) throw new TypeError("addressed share expiry must be in the future");
|
|
7944
|
-
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");
|
|
7945
|
-
if (input.contentSource.kind !== "kv") throw new TypeError("addressed policy publication requires a KV content source");
|
|
7946
|
-
}
|
|
7947
|
-
async function sha2564(value) {
|
|
7948
|
-
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
|
|
7949
|
-
}
|
|
7950
|
-
function publicationResult(input) {
|
|
7951
|
-
const result = {
|
|
7952
|
-
protocol: "tinycloud-share",
|
|
7953
|
-
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
7954
|
-
url: input.url,
|
|
7955
|
-
link: { kind: input.options.inline === true ? "inline" : "compact", cid: input.envelopeCid },
|
|
7956
|
-
metadata: {
|
|
7957
|
-
protocol: "tinycloud-share",
|
|
7958
|
-
version: 1,
|
|
7959
|
-
shareId: input.options.shareId,
|
|
7960
|
-
origin: input.options.shareOrigin,
|
|
7961
|
-
target: {
|
|
7962
|
-
kind: targetKind(input.options.target),
|
|
7963
|
-
origin: input.options.nodeOrigin,
|
|
7964
|
-
nodeAudience: input.options.nodeAudience,
|
|
7965
|
-
spaceId: input.options.spaceId
|
|
7966
|
-
},
|
|
7967
|
-
resource: { ...input.options.resource },
|
|
7968
|
-
actions: [...input.options.actions],
|
|
7969
|
-
expiresAt: input.options.expiresAt.toISOString(),
|
|
7970
|
-
display: { filename: input.options.filename },
|
|
7971
|
-
recipientMatcher: { ...input.matcher },
|
|
7972
|
-
registrationCid: input.registration.registrationCid,
|
|
7973
|
-
policyCid: input.policyCid,
|
|
7974
|
-
ownerDelegationCid: input.registration.ownerDelegationCid,
|
|
7975
|
-
enforcementDelegationCid: input.enforcementDelegationCid,
|
|
7976
|
-
ownerDid: input.registration.ownerDid,
|
|
7977
|
-
shareKeyDid: input.shareKeyDid,
|
|
7978
|
-
enforcerDid: input.registration.enforcerDid,
|
|
7979
|
-
envelopeCid: input.envelopeCid,
|
|
7980
|
-
shareCid: input.shareCid
|
|
7981
|
-
},
|
|
7982
|
-
registryDeleteAfter: input.retention
|
|
7983
|
-
};
|
|
7984
|
-
Object.defineProperty(result, "toJSON", { enumerable: false, value: () => redactPublishedShare(result) });
|
|
7985
|
-
Object.defineProperty(result, "url", { enumerable: false, value: input.url });
|
|
7986
|
-
return result;
|
|
7987
|
-
}
|
|
7988
|
-
async function publishAddressedShare(options) {
|
|
7989
|
-
assertSafeInput(options);
|
|
7990
|
-
const target = normalizeShareTarget(options.target);
|
|
7991
|
-
if (target.kind === "bearer") throw new TypeError("addressed target is required");
|
|
7992
|
-
if (options.contentSource.kind !== "kv") throw new TypeError("addressed policy publication requires a KV content source");
|
|
7993
|
-
const contentSource = options.contentSource;
|
|
7994
|
-
const matcher = targetMatcher(target);
|
|
7995
|
-
const expiresAt = options.expiresAt.toISOString();
|
|
7996
|
-
const contentSourceDigest = options.contentSourceDigest ?? await sha2564(new TextEncoder().encode(canonicalize(contentSource)));
|
|
7997
|
-
const shareKey = await createDelegatedShareKey({ extractable: false });
|
|
7998
|
-
let envelopeKey;
|
|
7999
|
-
try {
|
|
8000
|
-
const permissions = [
|
|
8001
|
-
{
|
|
8002
|
-
service: "tinycloud.kv",
|
|
8003
|
-
path: options.resource.kind === "prefix" ? `${options.resource.path.replace(/\/+$/, "")}/` : options.resource.path,
|
|
8004
|
-
actions: [...options.policyActions]
|
|
8005
|
-
},
|
|
8006
|
-
...options.decryption === void 0 ? [] : [{
|
|
8007
|
-
service: "tinycloud.encryption",
|
|
8008
|
-
path: options.decryption.networkId,
|
|
8009
|
-
actions: [options.decryption.action]
|
|
8010
|
-
}]
|
|
8011
|
-
];
|
|
8012
|
-
const ownerDelegation = await options.authority.createOwnerDelegation({
|
|
8013
|
-
delegateDid: shareKey.did,
|
|
8014
|
-
spaceId: options.spaceId,
|
|
8015
|
-
permissions,
|
|
8016
|
-
expiresAt: options.expiresAt
|
|
8017
|
-
});
|
|
8018
|
-
const policy = {
|
|
8019
|
-
type: "TinyCloudSharePolicy",
|
|
8020
|
-
version: 2,
|
|
8021
|
-
shareId: options.shareId,
|
|
8022
|
-
ownerDid: options.authority.ownerDid,
|
|
8023
|
-
shareKeyDid: shareKey.did,
|
|
8024
|
-
recipientMatcher: matcher,
|
|
8025
|
-
target: {
|
|
8026
|
-
origin: options.nodeOrigin,
|
|
8027
|
-
nodeAudience: options.nodeAudience,
|
|
8028
|
-
enforcerDid: options.enforcerDid,
|
|
8029
|
-
spaceId: options.spaceId
|
|
8030
|
-
},
|
|
8031
|
-
resource: { ...options.resource },
|
|
8032
|
-
actions: [...options.policyActions],
|
|
8033
|
-
...options.decryption === void 0 ? {} : { decryption: options.decryption },
|
|
8034
|
-
contentSource,
|
|
8035
|
-
contentSourceDigest,
|
|
8036
|
-
ownerDelegationCid: ownerDelegation.delegationCid,
|
|
8037
|
-
expiresAt
|
|
8038
|
-
};
|
|
8039
|
-
const canonicalPolicy = await canonicalOwnerSharePolicy(policy);
|
|
8040
|
-
const policyProof = toBase64Url(await shareKey.sign(canonicalPolicy.bytes));
|
|
8041
|
-
const enforcementDelegation = await createPolicyEnforcementDelegation({
|
|
8042
|
-
ownerDelegation,
|
|
8043
|
-
shareKey,
|
|
8044
|
-
enforcerDid: options.enforcerDid,
|
|
8045
|
-
policyCid: canonicalPolicy.cid,
|
|
8046
|
-
shareId: options.shareId,
|
|
8047
|
-
spaceId: options.spaceId,
|
|
8048
|
-
nodeAudience: options.nodeAudience,
|
|
8049
|
-
path: options.resource.path,
|
|
8050
|
-
actions: options.policyActions,
|
|
8051
|
-
contentSourceDigest,
|
|
8052
|
-
expiresAt
|
|
8053
|
-
});
|
|
8054
|
-
const registration = await options.authority.registerOwnerSharePolicy({
|
|
8055
|
-
policy: { bytes: canonicalPolicy.bytes, cid: canonicalPolicy.cid, proof: policyProof },
|
|
8056
|
-
ownerDelegation,
|
|
8057
|
-
enforcementDelegation,
|
|
8058
|
-
contentSourceDigest
|
|
8059
|
-
});
|
|
8060
|
-
const authorityMaterialDigest = await sha2564(fromBase64Url(enforcementDelegation.dagCbor));
|
|
8061
|
-
const authorityTarget = {
|
|
8062
|
-
origin: options.nodeOrigin,
|
|
8063
|
-
nodeAudience: options.nodeAudience,
|
|
8064
|
-
enforcerDid: options.enforcerDid,
|
|
8065
|
-
spaceId: options.spaceId
|
|
8066
|
-
};
|
|
8067
|
-
const envelopeIdentity = {
|
|
8068
|
-
schema: "xyz.tinycloud.share/envelope/v2",
|
|
8069
|
-
version: 2,
|
|
8070
|
-
shareId: options.shareId,
|
|
8071
|
-
delegationCid: ownerDelegation.delegationCid,
|
|
8072
|
-
policyCid: canonicalPolicy.cid,
|
|
8073
|
-
target: authorityTarget,
|
|
8074
|
-
resource: options.resource,
|
|
8075
|
-
actions: options.policyActions,
|
|
8076
|
-
...options.decryption === void 0 ? {} : { decryption: options.decryption },
|
|
8077
|
-
contentSource,
|
|
8078
|
-
contentSourceDigest,
|
|
8079
|
-
expiresAt
|
|
8080
|
-
};
|
|
8081
|
-
const envelopeCid = await computeCid(new TextEncoder().encode(canonicalize(envelopeIdentity)));
|
|
8082
|
-
const shareCid = await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: options.shareId, policyCid: canonicalPolicy.cid, envelopeCid })));
|
|
8083
|
-
const outerUnsigned = { ...envelopeIdentity, envelopeCid, shareCid };
|
|
8084
|
-
const outerSignature = toBase64Url(await shareKey.sign(new TextEncoder().encode(`${ENVELOPE_DOMAIN}${canonicalize(outerUnsigned)}`)));
|
|
8085
|
-
const unsigned = {
|
|
8086
|
-
version: 2,
|
|
8087
|
-
shareId: options.shareId,
|
|
8088
|
-
recipientMatcher: matcher,
|
|
8089
|
-
...options.deliveryEmail === void 0 ? {} : { deliveryEmail: options.deliveryEmail },
|
|
8090
|
-
actions: [...options.actions],
|
|
8091
|
-
resource: { ...options.resource },
|
|
8092
|
-
target: { origin: options.nodeOrigin, nodeAudience: options.nodeAudience, spaceId: options.spaceId },
|
|
8093
|
-
delegationCid: ownerDelegation.delegationCid,
|
|
8094
|
-
authorityMaterialHandle: registration.registration.registrationCid,
|
|
8095
|
-
authorityMaterialDigest,
|
|
8096
|
-
contentSource,
|
|
8097
|
-
contentSourceDigest,
|
|
8098
|
-
authorizationTarget: { kind: "policy", policyCid: canonicalPolicy.cid, policyBytes: toBase64Url(canonicalPolicy.bytes) },
|
|
8099
|
-
display: { filename: options.filename },
|
|
8100
|
-
expiry: expiresAt,
|
|
8101
|
-
encrypted: true,
|
|
8102
|
-
metadata: {
|
|
8103
|
-
mediaType: options.mediaType,
|
|
8104
|
-
byteLength: options.byteLength,
|
|
8105
|
-
filename: options.filename,
|
|
8106
|
-
...options.mediaType.startsWith("text/") ? { encoding: "utf-8" } : {},
|
|
8107
|
-
...options.artifact === void 0 ? {} : { artifact: options.artifact }
|
|
8108
|
-
},
|
|
8109
|
-
ownerAuthority: {
|
|
8110
|
-
registrationCid: registration.registration.registrationCid,
|
|
8111
|
-
shareCid,
|
|
8112
|
-
envelopeCid,
|
|
8113
|
-
enforcementDelegation,
|
|
8114
|
-
registrationReceipt: registration,
|
|
8115
|
-
outerEnvelope: {
|
|
8116
|
-
...outerUnsigned,
|
|
8117
|
-
signature: { signerDid: shareKey.did, algorithm: "Ed25519", value: outerSignature }
|
|
7348
|
+
writeHeader(4, item.length);
|
|
7349
|
+
item.forEach(write);
|
|
7350
|
+
return;
|
|
7351
|
+
}
|
|
7352
|
+
if (typeof item === "object" && item !== null) {
|
|
7353
|
+
const entries = Object.entries(item).map(([key, value2]) => {
|
|
7354
|
+
const keyBytes = dagCborEncode(key);
|
|
7355
|
+
return { key, value: value2, keyBytes };
|
|
7356
|
+
}).sort((left, right) => {
|
|
7357
|
+
if (left.keyBytes.length !== right.keyBytes.length) return left.keyBytes.length - right.keyBytes.length;
|
|
7358
|
+
for (let index = 0; index < left.keyBytes.length; index += 1) {
|
|
7359
|
+
const leftByte = left.keyBytes[index] ?? 0;
|
|
7360
|
+
const rightByte = right.keyBytes[index] ?? 0;
|
|
7361
|
+
if (leftByte !== rightByte) return leftByte - rightByte;
|
|
8118
7362
|
}
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
const uploaded = await uploadShareBlob({
|
|
8133
|
-
source: new Uint8Array([1]),
|
|
8134
|
-
filename: options.filename,
|
|
8135
|
-
origin: options.shareOrigin,
|
|
8136
|
-
...options.upload
|
|
8137
|
-
}, { blob: sealed.blob, cid: sealed.cid, deleteAfter: expiresAt, contentLength: sealed.blob.byteLength });
|
|
8138
|
-
retention = uploaded.deleteAfter;
|
|
8139
|
-
url = encodeShareUrl({ origin: options.shareOrigin, ciphertextCid: uploaded.cid, key32: envelopeKey });
|
|
8140
|
-
}
|
|
8141
|
-
return publicationResult({
|
|
8142
|
-
options,
|
|
8143
|
-
url,
|
|
8144
|
-
envelopeCid: sealed.cid,
|
|
8145
|
-
shareCid,
|
|
8146
|
-
matcher,
|
|
8147
|
-
registration: registration.registration,
|
|
8148
|
-
policyCid: canonicalPolicy.cid,
|
|
8149
|
-
enforcementDelegationCid: enforcementDelegation.cid,
|
|
8150
|
-
shareKeyDid: shareKey.did,
|
|
8151
|
-
retention
|
|
8152
|
-
});
|
|
8153
|
-
} finally {
|
|
8154
|
-
shareKey.clear();
|
|
8155
|
-
envelopeKey?.fill(0);
|
|
8156
|
-
}
|
|
7363
|
+
return 0;
|
|
7364
|
+
});
|
|
7365
|
+
writeHeader(5, entries.length);
|
|
7366
|
+
entries.forEach(({ key, value: value2 }) => {
|
|
7367
|
+
write(key);
|
|
7368
|
+
write(value2);
|
|
7369
|
+
});
|
|
7370
|
+
return;
|
|
7371
|
+
}
|
|
7372
|
+
throw new Error("Unsupported DAG-CBOR value");
|
|
7373
|
+
};
|
|
7374
|
+
write(value);
|
|
7375
|
+
return Uint8Array.from(output);
|
|
8157
7376
|
}
|
|
8158
|
-
|
|
8159
|
-
// src/policy-authority.ts
|
|
8160
|
-
import { ed25519 as ed255196 } from "@noble/curves/ed25519";
|
|
8161
|
-
var ENVELOPE_DOMAIN2 = "xyz.tinycloud.share/envelope/v2\0";
|
|
8162
|
-
function record(value, keys, label) {
|
|
7377
|
+
function assertObject(value, keys, label) {
|
|
8163
7378
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8164
|
-
const
|
|
8165
|
-
if (Object.keys(
|
|
8166
|
-
return
|
|
8167
|
-
}
|
|
8168
|
-
function requiredString(value, label) {
|
|
8169
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${label} is invalid`);
|
|
8170
|
-
return value;
|
|
8171
|
-
}
|
|
8172
|
-
function stringArray(value, label) {
|
|
8173
|
-
if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.length > 0)) throw new Error(`${label} is invalid`);
|
|
8174
|
-
return value;
|
|
7379
|
+
const record = value;
|
|
7380
|
+
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`);
|
|
7381
|
+
return record;
|
|
8175
7382
|
}
|
|
8176
|
-
function
|
|
8177
|
-
|
|
8178
|
-
const
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
nodeAudience: requiredString(facts.nodeAudience, "node audience"),
|
|
8191
|
-
spaceId: requiredString(facts.spaceId, "space id"),
|
|
8192
|
-
path: requiredString(facts.path, "resource path"),
|
|
8193
|
-
actions: stringArray(facts.actions, "policy actions"),
|
|
8194
|
-
contentSourceDigest: requiredString(facts.contentSourceDigest, "content source digest"),
|
|
8195
|
-
expiresAt: requiredString(facts.expiresAt, "policy expiry")
|
|
7383
|
+
async function createDelegatedShareKey(options) {
|
|
7384
|
+
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7385
|
+
const generated = await crypto.subtle.generateKey({ name: "Ed25519" }, options.extractable, ["sign", "verify"]);
|
|
7386
|
+
const privateKey = generated.privateKey;
|
|
7387
|
+
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", generated.publicKey));
|
|
7388
|
+
let cleared = false;
|
|
7389
|
+
const key = {
|
|
7390
|
+
did: didKeyFromEd25519PublicKey2(publicKey),
|
|
7391
|
+
publicKey: bytes(publicKey),
|
|
7392
|
+
extractable: options.extractable,
|
|
7393
|
+
cryptoKey: privateKey,
|
|
7394
|
+
async sign(input) {
|
|
7395
|
+
if (cleared) throw new Error("share key has been cleared");
|
|
7396
|
+
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, privateKey, input));
|
|
8196
7397
|
},
|
|
8197
|
-
|
|
7398
|
+
clear() {
|
|
7399
|
+
cleared = true;
|
|
7400
|
+
}
|
|
8198
7401
|
};
|
|
7402
|
+
return key;
|
|
8199
7403
|
}
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
const dagCbor = fromBase64Url(delegation.dagCbor);
|
|
8203
|
-
if (await computeCid(dagCbor) !== delegation.cid) return false;
|
|
8204
|
-
const expectedDagCbor = dagCborEncode({
|
|
8205
|
-
domain: POLICY_ENFORCEMENT_DOMAIN,
|
|
8206
|
-
unsigned: {
|
|
8207
|
-
type: "TinyCloudSharePolicyEnforcement",
|
|
8208
|
-
version: 2,
|
|
8209
|
-
issuerDid: delegation.issuerDid,
|
|
8210
|
-
audienceDid: delegation.audienceDid,
|
|
8211
|
-
facts: delegation.facts
|
|
8212
|
-
}
|
|
8213
|
-
});
|
|
8214
|
-
if (expectedDagCbor.length !== dagCbor.length || expectedDagCbor.some((byte, index) => byte !== dagCbor[index])) return false;
|
|
8215
|
-
const signature = fromBase64Url(delegation.signature);
|
|
8216
|
-
return signature.length === 64 && ed255196.verify(
|
|
8217
|
-
signature,
|
|
8218
|
-
dagCbor,
|
|
8219
|
-
ed25519PublicKeyFromDidKey(delegation.issuerDid),
|
|
8220
|
-
{ zip215: false }
|
|
8221
|
-
);
|
|
8222
|
-
} catch {
|
|
8223
|
-
return false;
|
|
8224
|
-
}
|
|
8225
|
-
}
|
|
8226
|
-
async function verifyOuterEnvelope(value, envelope, policyCid, expectedEnforcerDid, expectedActions) {
|
|
8227
|
-
try {
|
|
8228
|
-
const hasDecryption = typeof value === "object" && value !== null && Object.hasOwn(value, "decryption");
|
|
8229
|
-
const outer = record(value, ["schema", "version", "shareId", "delegationCid", "policyCid", "target", "resource", "actions", ...hasDecryption ? ["decryption"] : [], "contentSource", "contentSourceDigest", "expiresAt", "envelopeCid", "shareCid", "signature"], "outer envelope");
|
|
8230
|
-
const target = record(outer.target, ["origin", "nodeAudience", "enforcerDid", "spaceId"], "outer target");
|
|
8231
|
-
const signature = record(outer.signature, ["signerDid", "algorithm", "value"], "outer envelope signature");
|
|
8232
|
-
const envelopeCid = requiredString(outer.envelopeCid, "outer envelope CID");
|
|
8233
|
-
const shareCid = requiredString(outer.shareCid, "outer share CID");
|
|
8234
|
-
const { signature: _signature, envelopeCid: _envelopeCid, shareCid: _shareCid, ...identity } = outer;
|
|
8235
|
-
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;
|
|
8236
|
-
if (envelopeCid !== await computeCid(new TextEncoder().encode(canonicalize(identity)))) return void 0;
|
|
8237
|
-
if (shareCid !== await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: envelope.shareId, policyCid, envelopeCid })))) return void 0;
|
|
8238
|
-
const unsigned = { ...identity, envelopeCid, shareCid };
|
|
8239
|
-
const signatureBytes = fromBase64Url(requiredString(signature.value, "outer signature"));
|
|
8240
|
-
if (signatureBytes.length !== 64 || !ed255196.verify(
|
|
8241
|
-
signatureBytes,
|
|
8242
|
-
new TextEncoder().encode(`${ENVELOPE_DOMAIN2}${canonicalize(unsigned)}`),
|
|
8243
|
-
ed25519PublicKeyFromDidKey(envelope.signature.signerDid),
|
|
8244
|
-
{ zip215: false }
|
|
8245
|
-
)) return void 0;
|
|
8246
|
-
return { envelopeCid, shareCid };
|
|
8247
|
-
} catch {
|
|
8248
|
-
return void 0;
|
|
8249
|
-
}
|
|
8250
|
-
}
|
|
8251
|
-
function createRegisteredPolicyAuthority(options) {
|
|
8252
|
-
const proofKey = options.nodeProof.publicKey.slice();
|
|
7404
|
+
function restoreDelegatedShareKey(input) {
|
|
7405
|
+
let cleared = false;
|
|
8253
7406
|
return {
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
delegation: {
|
|
8266
|
-
delegateDID: envelope.signature.signerDid,
|
|
8267
|
-
spaceId: envelope.target.spaceId,
|
|
8268
|
-
path: envelope.resource.path,
|
|
8269
|
-
actions: envelope.actions,
|
|
8270
|
-
expiry: new Date(envelope.expiry)
|
|
8271
|
-
}
|
|
8272
|
-
};
|
|
8273
|
-
const receipt = validateOwnerSharePolicyRegistration(ownerAuthority.registrationReceipt, {
|
|
8274
|
-
policy: { bytes: policyBytes, cid: policyCid, proof: "" },
|
|
8275
|
-
ownerDelegation,
|
|
8276
|
-
enforcementDelegation: enforcement,
|
|
8277
|
-
contentSourceDigest: envelope.contentSourceDigest,
|
|
8278
|
-
nodeProof: { kid: options.nodeProof.kid, publicKey: proofKey }
|
|
8279
|
-
});
|
|
8280
|
-
const registration = receipt.registration;
|
|
8281
|
-
const outer = await verifyOuterEnvelope(ownerAuthority.outerEnvelope, envelope, policyCid, options.expectedTarget.enforcerDid, registration.actions);
|
|
8282
|
-
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;
|
|
8283
|
-
return {
|
|
8284
|
-
policyCid: registration.policyCid,
|
|
8285
|
-
signerDid: registration.shareKeyDid,
|
|
8286
|
-
registrationCid: registration.registrationCid,
|
|
8287
|
-
shareId: registration.shareId,
|
|
8288
|
-
recipientMatcher: registration.recipientMatcher,
|
|
8289
|
-
target: registration.target,
|
|
8290
|
-
resource: registration.resource,
|
|
8291
|
-
actions: registration.actions,
|
|
8292
|
-
contentSource: registration.contentSource,
|
|
8293
|
-
contentSourceDigest: registration.contentSourceDigest,
|
|
8294
|
-
delegationCid: registration.ownerDelegationCid,
|
|
8295
|
-
authorityMaterialHandle: registration.registrationCid,
|
|
8296
|
-
authorityMaterialDigest: envelope.authorityMaterialDigest,
|
|
8297
|
-
expiresAt: registration.expiresAt
|
|
8298
|
-
};
|
|
8299
|
-
} catch {
|
|
8300
|
-
return void 0;
|
|
8301
|
-
}
|
|
7407
|
+
did: didKeyFromEd25519PublicKey2(input.publicKey),
|
|
7408
|
+
publicKey: bytes(input.publicKey),
|
|
7409
|
+
extractable: input.extractable,
|
|
7410
|
+
cryptoKey: input.privateKey,
|
|
7411
|
+
async sign(signBytes) {
|
|
7412
|
+
if (cleared) throw new Error("share key has been cleared");
|
|
7413
|
+
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
7414
|
+
return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, input.privateKey, signBytes));
|
|
7415
|
+
},
|
|
7416
|
+
clear() {
|
|
7417
|
+
cleared = true;
|
|
8302
7418
|
}
|
|
8303
7419
|
};
|
|
8304
7420
|
}
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
7421
|
+
async function createPolicyEnforcementDelegation(input) {
|
|
7422
|
+
if (input.shareKey.did.length === 0 || input.ownerDelegation.delegationCid.length === 0) throw new Error("owner delegation and share key are required");
|
|
7423
|
+
const facts = {
|
|
7424
|
+
ownerDelegationCid: input.ownerDelegation.delegationCid,
|
|
7425
|
+
policyCid: input.policyCid,
|
|
7426
|
+
shareId: input.shareId,
|
|
7427
|
+
shareKeyDid: input.shareKey.did,
|
|
7428
|
+
enforcerDid: input.enforcerDid,
|
|
7429
|
+
nodeAudience: input.nodeAudience,
|
|
7430
|
+
spaceId: input.spaceId,
|
|
7431
|
+
path: input.path,
|
|
7432
|
+
actions: [...input.actions],
|
|
7433
|
+
contentSourceDigest: input.contentSourceDigest,
|
|
7434
|
+
expiresAt: input.expiresAt
|
|
7435
|
+
};
|
|
7436
|
+
const unsigned = { type: "TinyCloudSharePolicyEnforcement", version: 2, issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts };
|
|
7437
|
+
const dagCborBytes = dagCborEncode({ domain: POLICY_ENFORCEMENT_DOMAIN, unsigned });
|
|
7438
|
+
const signature = b64(await input.shareKey.sign(dagCborBytes));
|
|
7439
|
+
return { cid: cid(dagCborBytes), dagCbor: b64(dagCborBytes), issuerDid: input.shareKey.did, audienceDid: input.enforcerDid, facts, signature };
|
|
8311
7440
|
}
|
|
8312
|
-
function
|
|
8313
|
-
|
|
7441
|
+
async function canonicalOwnerSharePolicy(policy) {
|
|
7442
|
+
const text = canonicalize({ domain: POLICY_DOMAIN, policy });
|
|
7443
|
+
const policyBytes = new TextEncoder().encode(text);
|
|
7444
|
+
return { bytes: policyBytes, cid: cid(policyBytes), digest: digest2(policyBytes) };
|
|
8314
7445
|
}
|
|
8315
|
-
function
|
|
8316
|
-
|
|
8317
|
-
return left.every((byte, index) => byte === right[index]);
|
|
7446
|
+
function computeOwnerShareRegistrationCid(registration) {
|
|
7447
|
+
return cid(new TextEncoder().encode(canonicalize(registration)));
|
|
8318
7448
|
}
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
const entry = this.entries.get(mapKey);
|
|
8327
|
-
if (entry === void 0) return void 0;
|
|
8328
|
-
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
|
|
8329
|
-
this.entries.delete(mapKey);
|
|
8330
|
-
return void 0;
|
|
8331
|
-
}
|
|
8332
|
-
return entry.bytes.slice();
|
|
8333
|
-
}
|
|
8334
|
-
async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
|
|
8335
|
-
const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
|
|
8336
|
-
for (const entry of entries) {
|
|
8337
|
-
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
|
|
8338
|
-
this.entries.delete(cacheKey(entry.key));
|
|
8339
|
-
continue;
|
|
8340
|
-
}
|
|
8341
|
-
return {
|
|
8342
|
-
key: { ...entry.key },
|
|
8343
|
-
bytes: entry.bytes.slice(),
|
|
8344
|
-
digest: entry.digest.slice(),
|
|
8345
|
-
metadata: { ...entry.metadata }
|
|
8346
|
-
};
|
|
8347
|
-
}
|
|
8348
|
-
return void 0;
|
|
8349
|
-
}
|
|
8350
|
-
async set(key, bytes3, metadata) {
|
|
8351
|
-
if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
|
|
8352
|
-
this.entries.set(cacheKey(key), {
|
|
8353
|
-
key: { cid: key.cid, expiresAt: key.expiresAt },
|
|
8354
|
-
bytes: bytes3.slice(),
|
|
8355
|
-
digest: digest2(bytes3),
|
|
8356
|
-
metadata: { ...metadata }
|
|
8357
|
-
});
|
|
8358
|
-
}
|
|
8359
|
-
async delete(key) {
|
|
8360
|
-
this.entries.delete(cacheKey(key));
|
|
8361
|
-
}
|
|
8362
|
-
async clear() {
|
|
8363
|
-
this.entries.clear();
|
|
8364
|
-
}
|
|
8365
|
-
};
|
|
8366
|
-
|
|
8367
|
-
// src/delivery.ts
|
|
8368
|
-
import { ed25519 as ed255197 } from "@noble/curves/ed25519";
|
|
8369
|
-
var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
|
|
8370
|
-
function isCanonicalHttpsOrigin2(value) {
|
|
8371
|
-
let url;
|
|
7449
|
+
function validateOwnerSharePolicyRegistration(value, expected) {
|
|
7450
|
+
if (cid(expected.policy.bytes) !== expected.policy.cid) throw new Error("submitted owner-share policy bytes do not match its CID");
|
|
7451
|
+
const root = assertObject(value, ["registration", "proof"], "owner-share registration response");
|
|
7452
|
+
const registration = assertObject(root.registration, ["registrationCid", "policyCid", "ownerDelegationCid", "enforcementDelegationCid", "ownerDid", "shareKeyDid", "enforcerDid", "shareId", "recipientMatcher", "target", "resource", "actions", "contentSource", "contentSourceDigest", "registeredAt", "expiresAt"], "owner-share registration");
|
|
7453
|
+
const proof = assertObject(root.proof, ["alg", "kid", "signature"], "owner-share registration proof");
|
|
7454
|
+
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");
|
|
7455
|
+
let policyValue;
|
|
8372
7456
|
try {
|
|
8373
|
-
|
|
7457
|
+
policyValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(expected.policy.bytes));
|
|
8374
7458
|
} catch {
|
|
8375
|
-
|
|
8376
|
-
}
|
|
8377
|
-
return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
8378
|
-
}
|
|
8379
|
-
var AUTHORIZATION_KEYS = [
|
|
8380
|
-
"type",
|
|
8381
|
-
"version",
|
|
8382
|
-
"jti",
|
|
8383
|
-
"shareCid",
|
|
8384
|
-
"shareId",
|
|
8385
|
-
"registrationCid",
|
|
8386
|
-
"delegationCid",
|
|
8387
|
-
"enforcementDelegationCid",
|
|
8388
|
-
"envelopeCid",
|
|
8389
|
-
"policyCid",
|
|
8390
|
-
"nodeAudience",
|
|
8391
|
-
"targetOrigin",
|
|
8392
|
-
"openCredentialsAudience",
|
|
8393
|
-
"holder",
|
|
8394
|
-
"recipientMatcher",
|
|
8395
|
-
"deliveryEmail",
|
|
8396
|
-
"shareUrl",
|
|
8397
|
-
"returnOrigin",
|
|
8398
|
-
"documentName",
|
|
8399
|
-
"senderDid",
|
|
8400
|
-
"senderTrust",
|
|
8401
|
-
"contentSource",
|
|
8402
|
-
"contentSourceDigest",
|
|
8403
|
-
"shareExpiresAt",
|
|
8404
|
-
"issuedAt",
|
|
8405
|
-
"reportAbuseToken",
|
|
8406
|
-
"actions",
|
|
8407
|
-
"resource",
|
|
8408
|
-
"authorityMaterialHandle",
|
|
8409
|
-
"authorityMaterialDigest",
|
|
8410
|
-
"requestBodyDigest",
|
|
8411
|
-
"idempotencyKey",
|
|
8412
|
-
"expiresAt",
|
|
8413
|
-
"dataAuthority"
|
|
8414
|
-
];
|
|
8415
|
-
function assertObject2(value, keys, label) {
|
|
8416
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8417
|
-
const record2 = value;
|
|
8418
|
-
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`);
|
|
8419
|
-
return record2;
|
|
8420
|
-
}
|
|
8421
|
-
function fromB642(value) {
|
|
8422
|
-
if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
|
|
8423
|
-
if (typeof atob === "function") {
|
|
8424
|
-
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
8425
|
-
return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
7459
|
+
throw new Error("owner-share policy is not valid JSON");
|
|
8426
7460
|
}
|
|
8427
|
-
|
|
8428
|
-
throw new Error("
|
|
7461
|
+
const policy = policyValue.policy;
|
|
7462
|
+
if (policyValue.domain !== "xyz.tinycloud.share/policy/v2\0" || policy === void 0 || Array.isArray(policy)) throw new Error("owner-share policy envelope is invalid");
|
|
7463
|
+
const target = policy.target;
|
|
7464
|
+
const resource = policy.resource;
|
|
7465
|
+
const registrationTarget = registration.target;
|
|
7466
|
+
const registrationResource = registration.resource;
|
|
7467
|
+
const enforcementFacts = expected.enforcementDelegation.facts;
|
|
7468
|
+
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");
|
|
7469
|
+
if (typeof registration.registrationCid !== "string" || typeof registration.expiresAt !== "string" || typeof registration.registeredAt !== "string") throw new Error("owner-share registration timestamps are invalid");
|
|
7470
|
+
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");
|
|
7471
|
+
const { registrationCid: _registrationCid, ...registrationCore } = registration;
|
|
7472
|
+
if (computeOwnerShareRegistrationCid(registrationCore) !== registration.registrationCid) throw new Error("owner-share registration CID does not match its canonical core");
|
|
7473
|
+
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("owner-share registration proof is invalid");
|
|
7474
|
+
const proofKey = expected.nodeProof;
|
|
7475
|
+
if (proofKey === void 0) throw new Error("owner-share registration proof trust material is required");
|
|
7476
|
+
if (proof.kid !== proofKey.kid) throw new Error("owner-share registration proof key is not trusted");
|
|
7477
|
+
const encodedKid = proofKey.publicKey;
|
|
7478
|
+
if (encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1) {
|
|
7479
|
+
} else if (encodedKid.length !== 32) throw new Error("owner-share registration proof key is invalid");
|
|
7480
|
+
const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
|
|
7481
|
+
const signatureBytes = fromB64(proof.signature);
|
|
7482
|
+
const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
|
|
7483
|
+
if (signatureBytes.length !== 64 || !ed255194.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
|
|
7484
|
+
return { registration, proof };
|
|
8429
7485
|
}
|
|
8430
|
-
function
|
|
7486
|
+
function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
8431
7487
|
let value;
|
|
8432
7488
|
try {
|
|
8433
7489
|
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
8434
7490
|
} catch {
|
|
8435
|
-
throw new Error("share
|
|
8436
|
-
}
|
|
8437
|
-
|
|
8438
|
-
const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
|
|
8439
|
-
const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
|
|
8440
|
-
const request = expected.request;
|
|
8441
|
-
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) {
|
|
8442
|
-
throw new Error("share delivery authorization is not bound to the submitted request");
|
|
8443
|
-
}
|
|
8444
|
-
const now = Date.now();
|
|
8445
|
-
const issuedAt = typeof authorization.issuedAt === "string" ? Date.parse(authorization.issuedAt) : Number.NaN;
|
|
8446
|
-
const expiresAt = typeof authorization.expiresAt === "string" ? Date.parse(authorization.expiresAt) : Number.NaN;
|
|
8447
|
-
if (!Number.isFinite(issuedAt) || issuedAt > now + 6e4 || issuedAt > expiresAt) throw new Error("share delivery authorization issuedAt is invalid");
|
|
8448
|
-
if (!Number.isFinite(expiresAt) || expiresAt <= now) throw new Error("share delivery authorization is expired");
|
|
8449
|
-
if (new Date(expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
|
|
8450
|
-
if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin2(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
|
|
8451
|
-
throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
|
|
8452
|
-
}
|
|
8453
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
|
|
8454
|
-
if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
|
|
8455
|
-
const encodedKid = expected.nodeProof.publicKey;
|
|
8456
|
-
const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
|
|
8457
|
-
if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
|
|
8458
|
-
const signatureBytes = fromB642(proof.signature);
|
|
8459
|
-
const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalize(authorization)}`);
|
|
8460
|
-
if (signatureBytes.length !== 64 || !ed255197.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
|
|
8461
|
-
return { authorization, proof };
|
|
8462
|
-
}
|
|
8463
|
-
function shareDeliveryTrustedKid(publicKey) {
|
|
8464
|
-
if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
|
|
8465
|
-
const prefixed = new Uint8Array(34);
|
|
8466
|
-
prefixed.set([237, 1]);
|
|
8467
|
-
prefixed.set(publicKey, 2);
|
|
8468
|
-
const did = `did:key:${base58btc2.encode(prefixed)}`;
|
|
8469
|
-
return `${did}#${did.slice("did:key:".length)}`;
|
|
7491
|
+
throw new Error("owner-share registration response is not valid UTF-8 JSON");
|
|
7492
|
+
}
|
|
7493
|
+
return validateOwnerSharePolicyRegistration(value, expected);
|
|
8470
7494
|
}
|
|
8471
7495
|
|
|
8472
7496
|
// src/history.ts
|
|
@@ -8512,13 +7536,14 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
|
|
|
8512
7536
|
registeredAt: now.toISOString(),
|
|
8513
7537
|
expiresAt: result.metadata.expiresAt,
|
|
8514
7538
|
link: result.url,
|
|
7539
|
+
...result.deliveryMaterial === void 0 ? {} : { deliveryMaterial: result.deliveryMaterial },
|
|
8515
7540
|
...result.metadata.display.filename === void 0 ? {} : { filename: result.metadata.display.filename }
|
|
8516
7541
|
};
|
|
8517
7542
|
}
|
|
8518
7543
|
var MemorySenderShareRecordStorage = class {
|
|
8519
7544
|
records = /* @__PURE__ */ new Map();
|
|
8520
|
-
async put(
|
|
8521
|
-
this.records.set(
|
|
7545
|
+
async put(record) {
|
|
7546
|
+
this.records.set(record.shareId, record);
|
|
8522
7547
|
}
|
|
8523
7548
|
async list() {
|
|
8524
7549
|
return [...this.records.values()];
|
|
@@ -8555,7 +7580,7 @@ var SenderShareStore = class {
|
|
|
8555
7580
|
async save(receipt, key) {
|
|
8556
7581
|
const registration = receipt.registration;
|
|
8557
7582
|
if (registration.shareKeyDid !== key.did) throw new Error("share key does not match the registered receipt");
|
|
8558
|
-
const
|
|
7583
|
+
const record = {
|
|
8559
7584
|
shareId: registration.shareId,
|
|
8560
7585
|
registrationCid: registration.registrationCid,
|
|
8561
7586
|
policyCid: registration.policyCid,
|
|
@@ -8572,15 +7597,15 @@ var SenderShareStore = class {
|
|
|
8572
7597
|
registeredAt: registration.registeredAt,
|
|
8573
7598
|
expiresAt: registration.expiresAt
|
|
8574
7599
|
};
|
|
8575
|
-
await this.records.put(
|
|
8576
|
-
await this.keys.put(
|
|
8577
|
-
return
|
|
7600
|
+
await this.records.put(record);
|
|
7601
|
+
await this.keys.put(record.shareId, { cryptoKey: key.cryptoKey, publicKey: key.publicKey, extractable: key.extractable });
|
|
7602
|
+
return record;
|
|
8578
7603
|
}
|
|
8579
7604
|
/** List shares created in any session, most recent first. */
|
|
8580
7605
|
async list(options) {
|
|
8581
7606
|
const now = this.now().getTime();
|
|
8582
7607
|
const records = await this.records.list();
|
|
8583
|
-
return records.filter((
|
|
7608
|
+
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));
|
|
8584
7609
|
}
|
|
8585
7610
|
async get(shareId) {
|
|
8586
7611
|
return this.records.get(shareId);
|
|
@@ -8603,17 +7628,17 @@ var SenderShareStore = class {
|
|
|
8603
7628
|
* returned CID, then call {@link markRevoked} once that succeeds.
|
|
8604
7629
|
*/
|
|
8605
7630
|
async revocationTarget(shareId, scope) {
|
|
8606
|
-
const
|
|
8607
|
-
if (
|
|
8608
|
-
const delegationCid = scope === "direct" ?
|
|
7631
|
+
const record = await this.records.get(shareId);
|
|
7632
|
+
if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7633
|
+
const delegationCid = scope === "direct" ? record.enforcementDelegationCid : record.ownerDelegationCid;
|
|
8609
7634
|
if (delegationCid === void 0) throw new Error("share has no node-enforced delegation");
|
|
8610
7635
|
return delegationCid;
|
|
8611
7636
|
}
|
|
8612
7637
|
/** Mark a share revoked locally after the caller's node-side revoke call succeeds. */
|
|
8613
7638
|
async markRevoked(shareId, revokedAt = this.now().toISOString()) {
|
|
8614
|
-
const
|
|
8615
|
-
if (
|
|
8616
|
-
const revoked = { ...
|
|
7639
|
+
const record = await this.records.get(shareId);
|
|
7640
|
+
if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7641
|
+
const revoked = { ...record, revokedAt };
|
|
8617
7642
|
await this.records.put(revoked);
|
|
8618
7643
|
return revoked;
|
|
8619
7644
|
}
|
|
@@ -8629,9 +7654,9 @@ var EncryptedSenderShareHistory = class {
|
|
|
8629
7654
|
this.key = key;
|
|
8630
7655
|
this.now = now;
|
|
8631
7656
|
}
|
|
8632
|
-
async put(
|
|
7657
|
+
async put(record) {
|
|
8633
7658
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
8634
|
-
const payload = new TextEncoder().encode(JSON.stringify(
|
|
7659
|
+
const payload = new TextEncoder().encode(JSON.stringify(record));
|
|
8635
7660
|
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, this.key, payload));
|
|
8636
7661
|
const encoded = new Uint8Array(iv.length + ciphertext.length);
|
|
8637
7662
|
encoded.set(iv);
|
|
@@ -8664,8 +7689,8 @@ var ShareNotifyError = class extends Error {
|
|
|
8664
7689
|
this.name = "ShareNotifyError";
|
|
8665
7690
|
}
|
|
8666
7691
|
};
|
|
8667
|
-
function recipientMatchesShareRecord(
|
|
8668
|
-
const matcher =
|
|
7692
|
+
function recipientMatchesShareRecord(record, recipient) {
|
|
7693
|
+
const matcher = record.recipientMatcher;
|
|
8669
7694
|
if (matcher.kind === "bearer" || matcher.kind === "recipientDid") return false;
|
|
8670
7695
|
if (matcher.kind === "exactEmail") return matcher.value === recipient;
|
|
8671
7696
|
const at = recipient.lastIndexOf("@");
|
|
@@ -8696,67 +7721,74 @@ async function notifyShare(input) {
|
|
|
8696
7721
|
}
|
|
8697
7722
|
async function defaultIdempotencyKey(shareId, recipient) {
|
|
8698
7723
|
const canonicalRecipient = canonicalize(recipient.trim().toLowerCase());
|
|
8699
|
-
const
|
|
7724
|
+
const digest3 = new Uint8Array(await crypto.subtle.digest(
|
|
8700
7725
|
"SHA-256",
|
|
8701
7726
|
new TextEncoder().encode(canonicalRecipient)
|
|
8702
7727
|
));
|
|
8703
|
-
return `tinycloud-share:${shareId}:${toBase64Url(
|
|
7728
|
+
return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
|
|
8704
7729
|
}
|
|
8705
7730
|
|
|
8706
7731
|
// src/lifecycle.ts
|
|
8707
|
-
function targetKind2(
|
|
8708
|
-
if (
|
|
8709
|
-
return
|
|
7732
|
+
function targetKind2(record) {
|
|
7733
|
+
if (record.targetKind !== void 0) return record.targetKind;
|
|
7734
|
+
return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
|
|
7735
|
+
}
|
|
7736
|
+
function policyNodeAudience(record) {
|
|
7737
|
+
const envelope = record.deliveryMaterial?.envelope;
|
|
7738
|
+
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
|
|
7739
|
+
const binding = envelope.attestedEnforcerBinding;
|
|
7740
|
+
if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
|
|
7741
|
+
const nodeAudience = binding.nodeAudience;
|
|
7742
|
+
return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
|
|
8710
7743
|
}
|
|
8711
7744
|
async function revokeShare(input) {
|
|
8712
7745
|
const target = targetKind2(input.record);
|
|
8713
|
-
if (target === "bearer") return { state: "retention-only", target, reason: "bearer-capability-cannot-be-revoked" };
|
|
8714
7746
|
if (input.adapter === void 0) return { state: "unsupported", target, reason: "node revocation authority is required", code: "unsupported-target" };
|
|
8715
7747
|
const scope = input.scope ?? "direct";
|
|
8716
7748
|
const delegationCid = scope === "ancestor" ? input.record.ownerDelegationCid : input.record.enforcementDelegationCid;
|
|
8717
7749
|
if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
|
|
8718
|
-
|
|
7750
|
+
if (target === "bearer") {
|
|
7751
|
+
if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
|
|
7752
|
+
await input.adapter.revokeDelegation({ delegationCid, scope });
|
|
7753
|
+
} else {
|
|
7754
|
+
if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
|
|
7755
|
+
if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
|
|
7756
|
+
await input.adapter.revokePolicyRoot({
|
|
7757
|
+
rootCid: delegationCid,
|
|
7758
|
+
targetRole: scope === "ancestor" ? "policy-authority" : "policy-enforcement",
|
|
7759
|
+
ownerDid: input.record.ownerDid,
|
|
7760
|
+
nodeOrigin: input.record.target.origin,
|
|
7761
|
+
nodeAudience: policyNodeAudience(input.record)
|
|
7762
|
+
});
|
|
7763
|
+
}
|
|
8719
7764
|
const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
8720
7765
|
if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
|
|
8721
7766
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
8722
7767
|
}
|
|
8723
|
-
function redactRecord(
|
|
8724
|
-
const matcher =
|
|
8725
|
-
const revealedLink = revealLink ? link ??
|
|
7768
|
+
function redactRecord(record, revealLink, link) {
|
|
7769
|
+
const matcher = record.recipientMatcher;
|
|
7770
|
+
const revealedLink = revealLink ? link ?? record.link : void 0;
|
|
8726
7771
|
return {
|
|
8727
|
-
shareId:
|
|
7772
|
+
shareId: record.shareId,
|
|
8728
7773
|
target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
|
|
8729
7774
|
...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
|
|
8730
|
-
expiresAt:
|
|
8731
|
-
revoked:
|
|
7775
|
+
expiresAt: record.expiresAt,
|
|
7776
|
+
revoked: record.revokedAt !== void 0,
|
|
8732
7777
|
...revealedLink === void 0 ? {} : { link: revealedLink }
|
|
8733
7778
|
};
|
|
8734
7779
|
}
|
|
8735
7780
|
async function listShares(storage) {
|
|
8736
7781
|
const records = await storage.list();
|
|
8737
|
-
return records.map((
|
|
7782
|
+
return records.map((record) => redactRecord(record, false));
|
|
8738
7783
|
}
|
|
8739
7784
|
async function showShare(input) {
|
|
8740
|
-
const
|
|
8741
|
-
if (
|
|
8742
|
-
return redactRecord(
|
|
8743
|
-
}
|
|
8744
|
-
|
|
8745
|
-
// src/legacy.ts
|
|
8746
|
-
function isLegacyShareLink(value) {
|
|
8747
|
-
return value.startsWith("tc1:");
|
|
8748
|
-
}
|
|
8749
|
-
async function receiveLegacyShare(link, reader) {
|
|
8750
|
-
if (!isLegacyShareLink(link)) throw new Error("legacy link must use tc1:");
|
|
8751
|
-
return reader.read(link);
|
|
8752
|
-
}
|
|
8753
|
-
async function migrateShare(input) {
|
|
8754
|
-
const value = await receiveLegacyShare(input.link, input.reader);
|
|
8755
|
-
return { protocol: "tinycloud-share", version: 1, legacy: true, value, migrated: await input.publish(value) };
|
|
7785
|
+
const record = await input.storage.get(input.shareId);
|
|
7786
|
+
if (record === void 0) throw new Error("share not found");
|
|
7787
|
+
return redactRecord(record, input.revealLink === true, input.link);
|
|
8756
7788
|
}
|
|
8757
7789
|
|
|
8758
7790
|
// src/recipient.ts
|
|
8759
|
-
import { ed25519 as
|
|
7791
|
+
import { ed25519 as ed255195, x25519 } from "@noble/curves/ed25519";
|
|
8760
7792
|
import { sha256 as sha2566 } from "@noble/hashes/sha256";
|
|
8761
7793
|
|
|
8762
7794
|
// src/protocol.ts
|
|
@@ -8848,12 +7880,6 @@ function fromBase64(value, label) {
|
|
|
8848
7880
|
if (toBase64(out) !== value) throw new Error(`${label} is invalid`);
|
|
8849
7881
|
return out;
|
|
8850
7882
|
}
|
|
8851
|
-
async function digest3(value) {
|
|
8852
|
-
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalize(value)))));
|
|
8853
|
-
}
|
|
8854
|
-
async function digestText2(value) {
|
|
8855
|
-
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))));
|
|
8856
|
-
}
|
|
8857
7883
|
async function digestBytes2(value) {
|
|
8858
7884
|
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
|
|
8859
7885
|
}
|
|
@@ -8863,24 +7889,9 @@ function trustedPublicKey(value) {
|
|
|
8863
7889
|
}
|
|
8864
7890
|
return value.invitationPublicKey;
|
|
8865
7891
|
}
|
|
8866
|
-
async function verifyWrapped(value, key, domain, trust) {
|
|
8867
|
-
const wrapper = object2(value, `${key} response`);
|
|
8868
|
-
if (Object.keys(wrapper).length !== 2 || !Object.hasOwn(wrapper, key) || !Object.hasOwn(wrapper, "proof")) throw new Error(`${key} response is invalid`);
|
|
8869
|
-
const artifact = object2(wrapper[key], `${key} artifact`);
|
|
8870
|
-
const proof = object2(wrapper.proof, `${key} proof`);
|
|
8871
|
-
if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error(`${key} proof is invalid`);
|
|
8872
|
-
if (!ed255198.verify(bytes2(proof.signature, `${key} signature`), new TextEncoder().encode(`${domain}${canonicalize(artifact)}`), trustedPublicKey(trust))) throw new Error(`${key} proof is invalid`);
|
|
8873
|
-
return artifact;
|
|
8874
|
-
}
|
|
8875
7892
|
function nativeAction(action) {
|
|
8876
7893
|
return action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : "tinycloud.kv/get";
|
|
8877
7894
|
}
|
|
8878
|
-
function uiAction(action) {
|
|
8879
|
-
return action === "tinycloud.kv/list" ? "list" : action === "tinycloud.kv/put" ? "edit" : "read";
|
|
8880
|
-
}
|
|
8881
|
-
function selectedAction(envelope) {
|
|
8882
|
-
return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
|
|
8883
|
-
}
|
|
8884
7895
|
function policyAttenuationForV3(envelope) {
|
|
8885
7896
|
const policy = object2(envelope.policy, "v3 policy");
|
|
8886
7897
|
if (!Array.isArray(policy.capabilityCeiling)) throw new Error("v3 policy capability ceiling is invalid");
|
|
@@ -8950,11 +7961,20 @@ function verifyV3PolicyAuthorization(input) {
|
|
|
8950
7961
|
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");
|
|
8951
7962
|
return compact;
|
|
8952
7963
|
}
|
|
8953
|
-
function
|
|
7964
|
+
function hex3(value) {
|
|
8954
7965
|
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8955
7966
|
}
|
|
7967
|
+
function matchesInitialCiphertextDigest(bytes3, expected) {
|
|
7968
|
+
if (typeof expected !== "string" || !LOWER_SHA256_HEX.test(expected)) return false;
|
|
7969
|
+
const actual = sha2566(bytes3);
|
|
7970
|
+
let mismatch = 0;
|
|
7971
|
+
for (let index = 0; index < actual.length; index += 1) {
|
|
7972
|
+
mismatch |= actual[index] ^ Number.parseInt(expected.slice(index * 2, index * 2 + 2), 16);
|
|
7973
|
+
}
|
|
7974
|
+
return mismatch === 0;
|
|
7975
|
+
}
|
|
8956
7976
|
function canonicalHashHex(value) {
|
|
8957
|
-
return
|
|
7977
|
+
return hex3(sha2566(new TextEncoder().encode(canonicalize(value))));
|
|
8958
7978
|
}
|
|
8959
7979
|
async function aesGcmDecrypt(key, blob) {
|
|
8960
7980
|
if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
|
|
@@ -8979,31 +7999,12 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
|
|
|
8979
7999
|
throw new Error("encrypted content envelope is malformed");
|
|
8980
8000
|
}
|
|
8981
8001
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
|
|
8982
|
-
const
|
|
8002
|
+
const record = value;
|
|
8983
8003
|
const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
|
|
8984
|
-
if (Object.keys(
|
|
8004
|
+
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"))) {
|
|
8985
8005
|
throw new Error("encrypted content envelope binding is invalid");
|
|
8986
8006
|
}
|
|
8987
|
-
return
|
|
8988
|
-
}
|
|
8989
|
-
async function verifyDetachedResponse(response, trust) {
|
|
8990
|
-
let value;
|
|
8991
|
-
try {
|
|
8992
|
-
value = await response.clone().json();
|
|
8993
|
-
} catch {
|
|
8994
|
-
throw new Error("share read response is invalid");
|
|
8995
|
-
}
|
|
8996
|
-
const record2 = object2(value, "share read response");
|
|
8997
|
-
const proof = object2(record2.proof, "share read detached proof");
|
|
8998
|
-
if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error("share read detached proof is invalid");
|
|
8999
|
-
const unsigned = { ...record2 };
|
|
9000
|
-
delete unsigned.proof;
|
|
9001
|
-
if (!ed255198.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");
|
|
9002
|
-
}
|
|
9003
|
-
async function post(fetchFn, origin, path, body) {
|
|
9004
|
-
const response = await fetchFn(new URL(path, origin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
9005
|
-
if (!response.ok) throw new Error("share authority rejected the request");
|
|
9006
|
-
return response.json();
|
|
8007
|
+
return record;
|
|
9007
8008
|
}
|
|
9008
8009
|
var ShareRecipientClient = class {
|
|
9009
8010
|
constructor(options) {
|
|
@@ -9035,72 +8036,12 @@ var ShareRecipientClient = class {
|
|
|
9035
8036
|
v3ContentKey;
|
|
9036
8037
|
v3ContentEnvelope;
|
|
9037
8038
|
async beginChallenge(envelope) {
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
9041
|
-
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
9042
|
-
const target = object2(outer.target, "owner authority target");
|
|
9043
|
-
const outerResource = object2(outer.resource, "owner authority resource");
|
|
9044
|
-
const source = object2(outer.contentSource, "owner authority content source");
|
|
9045
|
-
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");
|
|
9046
|
-
const actions = [...outer.actions];
|
|
9047
|
-
const action = selectedAction(envelope);
|
|
9048
|
-
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) };
|
|
9049
|
-
const requestBodyDigest = await digest3(challengeBody);
|
|
9050
|
-
const challenge = await verifyWrapped(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/challenges", { ...challengeBody, requestBodyDigest }), "challenge", DOMAIN, this.options.trustedNode);
|
|
9051
|
-
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");
|
|
9052
|
-
return challenge;
|
|
8039
|
+
void envelope;
|
|
8040
|
+
throw new Error("v2 Share recipient transport has been removed; use the embedded policy v3 envelope");
|
|
9053
8041
|
}
|
|
9054
8042
|
async establish(envelope) {
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
9058
|
-
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
9059
|
-
const target = object2(outer.target, "owner authority target");
|
|
9060
|
-
const outerResource = object2(outer.resource, "owner authority resource");
|
|
9061
|
-
const source = object2(outer.contentSource, "owner authority content source");
|
|
9062
|
-
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");
|
|
9063
|
-
const actions = [...outer.actions];
|
|
9064
|
-
const action = selectedAction(envelope);
|
|
9065
|
-
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) };
|
|
9066
|
-
const requestBodyDigest = await digest3(challengeBody);
|
|
9067
|
-
const challenge = await verifyWrapped(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/challenges", { ...challengeBody, requestBodyDigest }), "challenge", DOMAIN, this.options.trustedNode);
|
|
9068
|
-
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");
|
|
9069
|
-
if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
|
|
9070
|
-
const material = await this.options.buildPresentation({ challenge, envelope, policy: {} });
|
|
9071
|
-
this.holderProof = material.proof;
|
|
9072
|
-
this.signer = material.sign;
|
|
9073
|
-
this.nativeSigner = material.sign;
|
|
9074
|
-
if (this.signer === void 0) throw new Error("share holder signer is required");
|
|
9075
|
-
const presentation = {
|
|
9076
|
-
type: "TinyCloudSharePolicyPresentation",
|
|
9077
|
-
version: 2,
|
|
9078
|
-
challengeId: challenge.challengeId,
|
|
9079
|
-
nonce: challenge.nonce,
|
|
9080
|
-
shareCid: authority.shareCid,
|
|
9081
|
-
shareId: envelope.shareId,
|
|
9082
|
-
delegationCid: envelope.delegationCid,
|
|
9083
|
-
policyCid: challengeBody.policyCid,
|
|
9084
|
-
contentSource: source,
|
|
9085
|
-
contentSourceDigest: challengeBody.contentSourceDigest,
|
|
9086
|
-
holderDid: material.holderDid,
|
|
9087
|
-
targetOrigin: challengeBody.targetOrigin,
|
|
9088
|
-
nodeAudience: challengeBody.nodeAudience,
|
|
9089
|
-
...challenge.enforcerDid === void 0 ? {} : { enforcerDid: challenge.enforcerDid },
|
|
9090
|
-
credentialDigest: material.credentialDigest ?? await digestText2(material.credential),
|
|
9091
|
-
action,
|
|
9092
|
-
actions,
|
|
9093
|
-
resource: challengeBody.resource,
|
|
9094
|
-
requestBodyDigest,
|
|
9095
|
-
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9096
|
-
expiresAt: challenge.expiresAt,
|
|
9097
|
-
jti: toBase64Url(crypto.getRandomValues(new Uint8Array(16)))
|
|
9098
|
-
};
|
|
9099
|
-
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)}`))) };
|
|
9100
|
-
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);
|
|
9101
|
-
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");
|
|
9102
|
-
this.session = { sessionId: session.sessionId, expiresAt: session.expiresAt, actions: actions.map(uiAction), resource: { kind: envelope.resource.kind, path: String(session.resource) } };
|
|
9103
|
-
return this.session;
|
|
8043
|
+
void envelope;
|
|
8044
|
+
throw new Error("v2 Share recipient transport has been removed; use the embedded policy v3 envelope");
|
|
9104
8045
|
}
|
|
9105
8046
|
async authorize(envelope) {
|
|
9106
8047
|
if (this.session === void 0) await this.establish(envelope);
|
|
@@ -9128,7 +8069,7 @@ var ShareRecipientClient = class {
|
|
|
9128
8069
|
} catch {
|
|
9129
8070
|
throw new Error("v3 attested Node audience is invalid");
|
|
9130
8071
|
}
|
|
9131
|
-
const challengeResponse = await this.fetchFn(new URL("/
|
|
8072
|
+
const challengeResponse = await this.fetchFn(new URL("/policy/v3/challenges", this.options.nodeOrigin), {
|
|
9132
8073
|
method: "POST",
|
|
9133
8074
|
redirect: "error",
|
|
9134
8075
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
@@ -9149,7 +8090,7 @@ var ShareRecipientClient = class {
|
|
|
9149
8090
|
throw new Error(accountless ? "v4 ceremony requires a verified credential and requirement" : "v3 ceremony requires a claim");
|
|
9150
8091
|
}
|
|
9151
8092
|
this.options.onStage?.("policy-admission");
|
|
9152
|
-
const delegationResponse = await this.fetchFn(new URL("/
|
|
8093
|
+
const delegationResponse = await this.fetchFn(new URL("/policy/v3/delegations", this.options.nodeOrigin), {
|
|
9153
8094
|
method: "POST",
|
|
9154
8095
|
redirect: "error",
|
|
9155
8096
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
@@ -9175,12 +8116,13 @@ var ShareRecipientClient = class {
|
|
|
9175
8116
|
const envelope = this.options.envelope;
|
|
9176
8117
|
const signer = this.nativeSigner ?? this.signer;
|
|
9177
8118
|
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");
|
|
8119
|
+
if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
|
|
9178
8120
|
const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
|
|
9179
8121
|
const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
|
|
9180
8122
|
const receiverPublicKey = toBase64(x25519.getPublicKey(receiverPrivateKey));
|
|
9181
8123
|
const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
|
|
9182
8124
|
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 };
|
|
9183
|
-
const bodyHash =
|
|
8125
|
+
const bodyHash = hex3(sha2566(new TextEncoder().encode(canonicalize(body))));
|
|
9184
8126
|
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
9185
8127
|
const now = Math.floor(Date.now() / 1e3);
|
|
9186
8128
|
const invocation = await signCompactUcanAuthorization({
|
|
@@ -9200,11 +8142,11 @@ var ShareRecipientClient = class {
|
|
|
9200
8142
|
if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
|
|
9201
8143
|
const value = object2(await response.json(), "v3 decrypt response");
|
|
9202
8144
|
const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
|
|
9203
|
-
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 !==
|
|
8145
|
+
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(sha2566(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
|
|
9204
8146
|
const unsigned = { ...value };
|
|
9205
8147
|
delete unsigned.nodeSignature;
|
|
9206
8148
|
const signature = fromBase64(value.nodeSignature, "v3 decrypt response signature");
|
|
9207
|
-
if (signature.length !== 64 || !
|
|
8149
|
+
if (signature.length !== 64 || !ed255195.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
|
|
9208
8150
|
const wrapped = fromBase64(value.wrappedKey, "v3 wrapped content key");
|
|
9209
8151
|
if (wrapped.length < 61 || wrapped[32] !== 1) throw new Error("v3 wrapped content key is malformed");
|
|
9210
8152
|
const shared = x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
|
|
@@ -9226,47 +8168,16 @@ var ShareRecipientClient = class {
|
|
|
9226
8168
|
return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
|
|
9227
8169
|
}
|
|
9228
8170
|
async resumeWithProof(envelope, resumeToken, proof) {
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
const credential = material.credential;
|
|
9234
|
-
const holderDid = material.holderDid;
|
|
9235
|
-
const holderBinding = material.holderBinding;
|
|
9236
|
-
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");
|
|
9237
|
-
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");
|
|
9238
|
-
const session = await verifyWrapped(value, "session", SESSION_DOMAIN, this.options.trustedNode);
|
|
9239
|
-
const authority = envelope.ownerAuthority;
|
|
9240
|
-
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");
|
|
9241
|
-
const policyCid = envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "";
|
|
9242
|
-
const actions = [...new Set(envelope.actions.map(nativeAction))].sort();
|
|
9243
|
-
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");
|
|
9244
|
-
this.session = { sessionId: session.sessionId, expiresAt: session.expiresAt, actions: actions.map(uiAction), resource: { kind: envelope.resource.kind, path: String(session.resource) } };
|
|
9245
|
-
this.holderProof = presentationProof;
|
|
9246
|
-
this.signer ??= this.options.sign;
|
|
9247
|
-
return this.authorize(envelope);
|
|
8171
|
+
void envelope;
|
|
8172
|
+
void resumeToken;
|
|
8173
|
+
void proof;
|
|
8174
|
+
throw new Error("v2 Share recipient transport has been removed; use the embedded policy v3 envelope");
|
|
9248
8175
|
}
|
|
9249
8176
|
async nativeInvoke(request) {
|
|
9250
8177
|
if (this.session === void 0) throw new Error("share policy session is required");
|
|
9251
8178
|
const envelope = this.options.envelope;
|
|
9252
8179
|
if (envelope.version === 3) return this.nativeInvokeV3(request, envelope);
|
|
9253
|
-
|
|
9254
|
-
const authority = envelope.ownerAuthority;
|
|
9255
|
-
const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
|
|
9256
|
-
const resource = typeof request.resource?.path === "string" ? request.resource.path : this.session.resource.path;
|
|
9257
|
-
const actions = [...new Set(this.session.actions.map(nativeAction).concat(action === "tinycloud.kv/metadata" ? [action] : []))].sort();
|
|
9258
|
-
const bodyBytes = request.body === void 0 ? void 0 : Uint8Array.from(request.body);
|
|
9259
|
-
const bodyDigest = bodyBytes === void 0 ? void 0 : toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", bodyBytes)));
|
|
9260
|
-
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
9261
|
-
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
9262
|
-
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))) };
|
|
9263
|
-
const requestBodyDigest = await digest3(invocationBase);
|
|
9264
|
-
const invocation = { ...invocationBase, requestBodyDigest };
|
|
9265
|
-
const proof = { ...this.holderProof, signature: toBase64Url(await this.signer(new TextEncoder().encode(`${INVOCATION_DOMAIN}${canonicalize(invocation)}`))) };
|
|
9266
|
-
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 };
|
|
9267
|
-
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 } }) });
|
|
9268
|
-
if (response.ok) await verifyDetachedResponse(response, this.options.trustedNode);
|
|
9269
|
-
return response;
|
|
8180
|
+
throw new Error("v2 Share invocation transport has been removed; use the embedded policy v3 envelope");
|
|
9270
8181
|
}
|
|
9271
8182
|
async nativeInvokeV3(request, envelope) {
|
|
9272
8183
|
const signer = this.nativeSigner ?? this.signer;
|
|
@@ -9322,10 +8233,10 @@ function createAddressedAuthorization(input) {
|
|
|
9322
8233
|
const envelopeAction = nativeAction("read");
|
|
9323
8234
|
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;
|
|
9324
8235
|
const detached = object2(wrapper.detached, "share read detached proof");
|
|
9325
|
-
if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
|
|
8236
|
+
if (input.trustedNode === void 0 || detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
|
|
9326
8237
|
const unsigned = { ...response };
|
|
9327
8238
|
delete unsigned.proof;
|
|
9328
|
-
return
|
|
8239
|
+
return ed255195.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
|
|
9329
8240
|
} catch {
|
|
9330
8241
|
return false;
|
|
9331
8242
|
}
|
|
@@ -9357,6 +8268,37 @@ async function claimShare(input) {
|
|
|
9357
8268
|
async function resumeShareAuthorization(input) {
|
|
9358
8269
|
return authorizeShare(input);
|
|
9359
8270
|
}
|
|
8271
|
+
|
|
8272
|
+
// src/native.ts
|
|
8273
|
+
var NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
|
|
8274
|
+
function canonicalViewerUrl(viewerOrigin) {
|
|
8275
|
+
const url = new URL(viewerOrigin);
|
|
8276
|
+
if (url.protocol !== "https:" || url.origin !== viewerOrigin || url.pathname !== "/" || url.search || url.hash) throw new TypeError("viewer origin must be a canonical HTTPS origin");
|
|
8277
|
+
url.pathname = "/viewer";
|
|
8278
|
+
return url;
|
|
8279
|
+
}
|
|
8280
|
+
async function createNativeShare(sharing, input) {
|
|
8281
|
+
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");
|
|
8282
|
+
const generated = await sharing.generate({ path: input.path, actions: ["tinycloud.kv/get"], expiry: input.expiresAt });
|
|
8283
|
+
if (generated.ok !== true) throw new Error(typeof generated.error?.message === "string" ? generated.error.message : "TinyCloud sharing service rejected delegation generation");
|
|
8284
|
+
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");
|
|
8285
|
+
const delegation = sharing.decodeLink(generated.data.token);
|
|
8286
|
+
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");
|
|
8287
|
+
const url = canonicalViewerUrl(input.viewerOrigin);
|
|
8288
|
+
url.hash = `${NATIVE_SHARE_FRAGMENT_PARAMETER}=${encodeURIComponent(generated.data.token)}`;
|
|
8289
|
+
return { url: url.toString(), delegationCid: generated.data.delegation.cid, expiresAt: generated.data.expiresAt, spaceId: delegation.spaceId };
|
|
8290
|
+
}
|
|
8291
|
+
function parseNativeShareUrl(value) {
|
|
8292
|
+
const url = new URL(value);
|
|
8293
|
+
if (url.search || url.pathname !== "/viewer") throw new TypeError("native shares must carry tc1 only in the /viewer URL fragment");
|
|
8294
|
+
const fragment = new URLSearchParams(url.hash.slice(1));
|
|
8295
|
+
const token = fragment.get(NATIVE_SHARE_FRAGMENT_PARAMETER);
|
|
8296
|
+
if (!token || fragment.size !== 1) throw new TypeError("missing native share fragment");
|
|
8297
|
+
return token;
|
|
8298
|
+
}
|
|
8299
|
+
async function openNativeShare(sharing, link) {
|
|
8300
|
+
return sharing.receive(parseNativeShareUrl(link), { autoSubdelegate: false, useSessionKey: false });
|
|
8301
|
+
}
|
|
9360
8302
|
export {
|
|
9361
8303
|
DEFAULT_MAX_CONTENT_BLOB_BYTES,
|
|
9362
8304
|
DEFAULT_MAX_SEALED_BLOB_BYTES,
|
|
@@ -9367,12 +8309,11 @@ export {
|
|
|
9367
8309
|
MemorySenderShareKeyStorage,
|
|
9368
8310
|
MemorySenderShareRecordStorage,
|
|
9369
8311
|
MemoryShareCache,
|
|
8312
|
+
NATIVE_SHARE_FRAGMENT_PARAMETER,
|
|
9370
8313
|
OWNER_SHARE_REGISTRATION_DOMAIN,
|
|
9371
8314
|
SHARE_CONTENT_LIMIT,
|
|
9372
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
9373
8315
|
SHARE_PUBLISH_RESULT_VERSION,
|
|
9374
8316
|
SHARE_RESULT_VERSION,
|
|
9375
|
-
SHARE_SEALED_OVERHEAD,
|
|
9376
8317
|
SHARE_V2_PROTOCOL,
|
|
9377
8318
|
SenderShareStore,
|
|
9378
8319
|
ShareNotifyError,
|
|
@@ -9388,23 +8329,21 @@ export {
|
|
|
9388
8329
|
computeOwnerShareRegistrationCid,
|
|
9389
8330
|
createAddressedAuthorization,
|
|
9390
8331
|
createDelegatedShareKey,
|
|
8332
|
+
createNativeShare,
|
|
9391
8333
|
createPolicyEnforcementDelegation,
|
|
9392
|
-
createRegisteredPolicyAuthority,
|
|
9393
8334
|
createShareV2HolderBindingArtifact,
|
|
9394
8335
|
fromBase64Url,
|
|
9395
8336
|
historyRecordForPublishedShare,
|
|
9396
8337
|
inspectShare,
|
|
9397
|
-
isLegacyShareLink,
|
|
9398
8338
|
listShares,
|
|
9399
|
-
migrateShare,
|
|
9400
8339
|
normalizeShareTarget,
|
|
9401
8340
|
notifyShare,
|
|
8341
|
+
openNativeShare,
|
|
9402
8342
|
parseAddressedEnvelope,
|
|
8343
|
+
parseNativeShareUrl,
|
|
9403
8344
|
publishAddressedShare,
|
|
9404
8345
|
publishPolicyShare,
|
|
9405
|
-
publishShare,
|
|
9406
8346
|
publishTargetShare,
|
|
9407
|
-
receiveLegacyShare,
|
|
9408
8347
|
receiveShare,
|
|
9409
8348
|
recipientMatchesShareRecord,
|
|
9410
8349
|
redactPublishedShare,
|
|
@@ -9413,14 +8352,11 @@ export {
|
|
|
9413
8352
|
restoreDelegatedShareKey,
|
|
9414
8353
|
resumeShareAuthorization,
|
|
9415
8354
|
revokeShare,
|
|
9416
|
-
shareDeliveryTrustedKid,
|
|
9417
8355
|
showShare,
|
|
9418
8356
|
targetAuthorizationMethod,
|
|
9419
8357
|
toBase64Url,
|
|
9420
8358
|
toShareErrorInfo,
|
|
9421
8359
|
validateOwnerSharePolicyRegistration,
|
|
9422
|
-
validateOwnerSharePolicyRegistrationBytes
|
|
9423
|
-
validateShareDeliveryAuthorizationBytes,
|
|
9424
|
-
verifyBearerEnvelope
|
|
8360
|
+
validateOwnerSharePolicyRegistrationBytes
|
|
9425
8361
|
};
|
|
9426
8362
|
//# sourceMappingURL=index.js.map
|