@tinycloud/share-sdk 0.3.0-beta.2 → 1.0.0-beta.4
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 +426 -1166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -291
- package/dist/index.d.ts +120 -291
- package/dist/index.js +417 -1153
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
CredentialInvitationError: () => CredentialInvitationError,
|
|
23
24
|
DEFAULT_MAX_CONTENT_BLOB_BYTES: () => DEFAULT_MAX_CONTENT_BLOB_BYTES,
|
|
24
25
|
DEFAULT_MAX_SEALED_BLOB_BYTES: () => DEFAULT_MAX_SEALED_BLOB_BYTES,
|
|
25
26
|
DEFAULT_SHARE_LIFETIME_MS: () => DEFAULT_SHARE_LIFETIME_MS,
|
|
@@ -29,12 +30,11 @@ __export(index_exports, {
|
|
|
29
30
|
MemorySenderShareKeyStorage: () => MemorySenderShareKeyStorage,
|
|
30
31
|
MemorySenderShareRecordStorage: () => MemorySenderShareRecordStorage,
|
|
31
32
|
MemoryShareCache: () => MemoryShareCache,
|
|
33
|
+
NATIVE_SHARE_FRAGMENT_PARAMETER: () => NATIVE_SHARE_FRAGMENT_PARAMETER,
|
|
32
34
|
OWNER_SHARE_REGISTRATION_DOMAIN: () => OWNER_SHARE_REGISTRATION_DOMAIN,
|
|
33
35
|
SHARE_CONTENT_LIMIT: () => SHARE_CONTENT_LIMIT,
|
|
34
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN: () => SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
35
36
|
SHARE_PUBLISH_RESULT_VERSION: () => SHARE_PUBLISH_RESULT_VERSION,
|
|
36
37
|
SHARE_RESULT_VERSION: () => SHARE_RESULT_VERSION,
|
|
37
|
-
SHARE_SEALED_OVERHEAD: () => SHARE_SEALED_OVERHEAD,
|
|
38
38
|
SHARE_V2_PROTOCOL: () => SHARE_V2_PROTOCOL,
|
|
39
39
|
SenderShareStore: () => SenderShareStore,
|
|
40
40
|
ShareNotifyError: () => ShareNotifyError,
|
|
@@ -50,23 +50,22 @@ __export(index_exports, {
|
|
|
50
50
|
computeOwnerShareRegistrationCid: () => computeOwnerShareRegistrationCid,
|
|
51
51
|
createAddressedAuthorization: () => createAddressedAuthorization,
|
|
52
52
|
createDelegatedShareKey: () => createDelegatedShareKey,
|
|
53
|
+
createNativeShare: () => createNativeShare,
|
|
53
54
|
createPolicyEnforcementDelegation: () => createPolicyEnforcementDelegation,
|
|
54
|
-
createRegisteredPolicyAuthority: () => createRegisteredPolicyAuthority,
|
|
55
55
|
createShareV2HolderBindingArtifact: () => createShareV2HolderBindingArtifact,
|
|
56
|
+
deliverCredentialInvitation: () => deliverCredentialInvitation,
|
|
56
57
|
fromBase64Url: () => fromBase64Url,
|
|
57
58
|
historyRecordForPublishedShare: () => historyRecordForPublishedShare,
|
|
58
59
|
inspectShare: () => inspectShare,
|
|
59
|
-
isLegacyShareLink: () => isLegacyShareLink,
|
|
60
60
|
listShares: () => listShares,
|
|
61
|
-
migrateShare: () => migrateShare,
|
|
62
61
|
normalizeShareTarget: () => normalizeShareTarget,
|
|
63
62
|
notifyShare: () => notifyShare,
|
|
63
|
+
openNativeShare: () => openNativeShare,
|
|
64
64
|
parseAddressedEnvelope: () => parseAddressedEnvelope,
|
|
65
|
+
parseNativeShareUrl: () => parseNativeShareUrl,
|
|
65
66
|
publishAddressedShare: () => publishAddressedShare,
|
|
66
67
|
publishPolicyShare: () => publishPolicyShare,
|
|
67
|
-
publishShare: () => publishShare,
|
|
68
68
|
publishTargetShare: () => publishTargetShare,
|
|
69
|
-
receiveLegacyShare: () => receiveLegacyShare,
|
|
70
69
|
receiveShare: () => receiveShare,
|
|
71
70
|
recipientMatchesShareRecord: () => recipientMatchesShareRecord,
|
|
72
71
|
redactPublishedShare: () => redactPublishedShare,
|
|
@@ -75,15 +74,12 @@ __export(index_exports, {
|
|
|
75
74
|
restoreDelegatedShareKey: () => restoreDelegatedShareKey,
|
|
76
75
|
resumeShareAuthorization: () => resumeShareAuthorization,
|
|
77
76
|
revokeShare: () => revokeShare,
|
|
78
|
-
shareDeliveryTrustedKid: () => shareDeliveryTrustedKid,
|
|
79
77
|
showShare: () => showShare,
|
|
80
78
|
targetAuthorizationMethod: () => targetAuthorizationMethod,
|
|
81
79
|
toBase64Url: () => toBase64Url,
|
|
82
80
|
toShareErrorInfo: () => toShareErrorInfo,
|
|
83
81
|
validateOwnerSharePolicyRegistration: () => validateOwnerSharePolicyRegistration,
|
|
84
|
-
validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes
|
|
85
|
-
validateShareDeliveryAuthorizationBytes: () => validateShareDeliveryAuthorizationBytes,
|
|
86
|
-
verifyBearerEnvelope: () => verifyBearerEnvelope
|
|
82
|
+
validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes
|
|
87
83
|
});
|
|
88
84
|
module.exports = __toCommonJS(index_exports);
|
|
89
85
|
|
|
@@ -235,10 +231,10 @@ var util;
|
|
|
235
231
|
return obj[e];
|
|
236
232
|
});
|
|
237
233
|
};
|
|
238
|
-
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (
|
|
234
|
+
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object4) => {
|
|
239
235
|
const keys = [];
|
|
240
|
-
for (const key in
|
|
241
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
236
|
+
for (const key in object4) {
|
|
237
|
+
if (Object.prototype.hasOwnProperty.call(object4, key)) {
|
|
242
238
|
keys.push(key);
|
|
243
239
|
}
|
|
244
240
|
}
|
|
@@ -5442,7 +5438,7 @@ function validateV3Invariants(value, ctx) {
|
|
|
5442
5438
|
if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
|
|
5443
5439
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
|
|
5444
5440
|
}
|
|
5445
|
-
if (value.attestedEnforcerBinding.
|
|
5441
|
+
if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
5446
5442
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
5447
5443
|
}
|
|
5448
5444
|
if (value.policy.contentSource.shareId !== value.contentSource.shareId || value.policy.contentSource.kvResource !== value.contentSource.kvResource || value.policy.contentSource.selector !== value.contentSource.selector || value.policy.contentSource.encryptionNetwork !== value.encryptionNetwork || value.policy.contentSource.encryptedSymmetricKeyDigestHex !== value.contentSource.encryptedSymmetricKeyDigestHex || value.policy.contentSource.keyVersion !== value.contentSource.keyVersion || value.policy.contentSource.mode !== value.contentSource.mode || value.policy.contentSource.initialCiphertextDigestHex !== value.contentSource.initialCiphertextDigestHex) {
|
|
@@ -5637,36 +5633,6 @@ function checkBearerDelegation(envelope, options = {}) {
|
|
|
5637
5633
|
}
|
|
5638
5634
|
return { ok: true, delegateeDid: audience };
|
|
5639
5635
|
}
|
|
5640
|
-
function mintBearerDelegation(options) {
|
|
5641
|
-
const { issuerPrivateKey, audienceDid, resourceUri, expiresAtSeconds } = options;
|
|
5642
|
-
if (!audienceDid.startsWith("did:")) {
|
|
5643
|
-
throw new TypeError(`audienceDid must be a DID, got ${audienceDid}`);
|
|
5644
|
-
}
|
|
5645
|
-
if (resourceUri.length === 0) {
|
|
5646
|
-
throw new TypeError("resourceUri must be non-empty");
|
|
5647
|
-
}
|
|
5648
|
-
if (!Number.isSafeInteger(expiresAtSeconds) || expiresAtSeconds <= 0) {
|
|
5649
|
-
throw new TypeError(
|
|
5650
|
-
`expiresAtSeconds must be a positive integer (epoch seconds), got ${expiresAtSeconds}`
|
|
5651
|
-
);
|
|
5652
|
-
}
|
|
5653
|
-
const issuerDid = didKeyFromEd25519PublicKey(
|
|
5654
|
-
import_ed25519.ed25519.getPublicKey(issuerPrivateKey)
|
|
5655
|
-
);
|
|
5656
|
-
const header = { alg: "EdDSA", typ: "JWT", ucv: "0.9.1" };
|
|
5657
|
-
const payload = {
|
|
5658
|
-
iss: issuerDid,
|
|
5659
|
-
aud: audienceDid,
|
|
5660
|
-
att: [{ with: resourceUri, can: BEARER_READ_ABILITY }],
|
|
5661
|
-
prf: [],
|
|
5662
|
-
exp: expiresAtSeconds
|
|
5663
|
-
};
|
|
5664
|
-
const signingInput = `${toBase64Url(utf8Bytes(JSON.stringify(header)))}.${toBase64Url(
|
|
5665
|
-
utf8Bytes(JSON.stringify(payload))
|
|
5666
|
-
)}`;
|
|
5667
|
-
const signature = import_ed25519.ed25519.sign(utf8Bytes(signingInput), issuerPrivateKey);
|
|
5668
|
-
return `${signingInput}.${toBase64Url(signature)}`;
|
|
5669
|
-
}
|
|
5670
5636
|
var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
|
|
5671
5637
|
var SEALED_BLOB_VERSION = 1;
|
|
5672
5638
|
var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
|
|
@@ -5866,8 +5832,6 @@ function equal(left, right) {
|
|
|
5866
5832
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
5867
5833
|
}
|
|
5868
5834
|
var ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
5869
|
-
var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
|
|
5870
|
-
var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
5871
5835
|
var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
5872
5836
|
var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
5873
5837
|
var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
@@ -5875,22 +5839,6 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
|
5875
5839
|
var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
5876
5840
|
var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
5877
5841
|
var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
5878
|
-
function signingBytes(unsigned) {
|
|
5879
|
-
const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
|
|
5880
|
-
const body = utf8Bytes(canonicalize(unsigned));
|
|
5881
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5882
|
-
bytes3.set(domain);
|
|
5883
|
-
bytes3.set(body, domain.length);
|
|
5884
|
-
return bytes3;
|
|
5885
|
-
}
|
|
5886
|
-
function signingBytesV2(unsigned) {
|
|
5887
|
-
const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
|
|
5888
|
-
const body = utf8Bytes(canonicalize(unsigned));
|
|
5889
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5890
|
-
bytes3.set(domain);
|
|
5891
|
-
bytes3.set(body, domain.length);
|
|
5892
|
-
return bytes3;
|
|
5893
|
-
}
|
|
5894
5842
|
function signingBytesV3(unsigned) {
|
|
5895
5843
|
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
5896
5844
|
const body = utf8Bytes(canonicalize(unsigned));
|
|
@@ -5899,53 +5847,6 @@ function signingBytesV3(unsigned) {
|
|
|
5899
5847
|
bytes3.set(body, domain.length);
|
|
5900
5848
|
return bytes3;
|
|
5901
5849
|
}
|
|
5902
|
-
function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
|
|
5903
|
-
const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
|
|
5904
|
-
const publicKey = import_ed255193.ed25519.getPublicKey(ed25519PrivKey);
|
|
5905
|
-
const signature = import_ed255193.ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
|
|
5906
|
-
return {
|
|
5907
|
-
...unsigned,
|
|
5908
|
-
signature: {
|
|
5909
|
-
signerDid: didKeyFromEd25519PublicKey(publicKey),
|
|
5910
|
-
algorithm: "Ed25519",
|
|
5911
|
-
value: toBase64Url(signature)
|
|
5912
|
-
}
|
|
5913
|
-
};
|
|
5914
|
-
}
|
|
5915
|
-
function verifyEnvelopeSignatureOnly(envelope) {
|
|
5916
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
5917
|
-
const { signature, ...unsigned } = parsed;
|
|
5918
|
-
const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
|
|
5919
|
-
return import_ed255193.ed25519.verify(
|
|
5920
|
-
fromBase64Url(signature.value),
|
|
5921
|
-
signingBytes(unsigned),
|
|
5922
|
-
publicKey,
|
|
5923
|
-
ED25519_VERIFY_OPTS2
|
|
5924
|
-
);
|
|
5925
|
-
}
|
|
5926
|
-
async function verifyEnvelope(envelope, options) {
|
|
5927
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
5928
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid) return false;
|
|
5929
|
-
if (!verifyEnvelopeSignatureOnly(parsed)) return false;
|
|
5930
|
-
if (parsed.authorizationTarget.kind === "policy") {
|
|
5931
|
-
const policyBytes = fromBase64Url(parsed.authorizationTarget.policyBytes);
|
|
5932
|
-
if (await computeCid(policyBytes) !== parsed.authorizationTarget.policyCid) {
|
|
5933
|
-
return false;
|
|
5934
|
-
}
|
|
5935
|
-
}
|
|
5936
|
-
return true;
|
|
5937
|
-
}
|
|
5938
|
-
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
5939
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5940
|
-
const { signature, ...unsigned } = parsed;
|
|
5941
|
-
return import_ed255193.ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
5942
|
-
}
|
|
5943
|
-
async function verifyEnvelopeV2(envelope, options) {
|
|
5944
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5945
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
|
|
5946
|
-
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
5947
|
-
return true;
|
|
5948
|
-
}
|
|
5949
5850
|
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
5950
5851
|
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
5951
5852
|
const { signature, ...unsigned } = parsed;
|
|
@@ -5991,7 +5892,7 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
5991
5892
|
const binding = parsed.attestedEnforcerBinding;
|
|
5992
5893
|
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
5993
5894
|
const expectedBindingDigestHex = hex((0, import_sha256.sha256)(new TextEncoder().encode(canonicalize({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
5994
|
-
if (binding.
|
|
5895
|
+
if (binding.nodeAudience !== parsed.target.nodeAudience || binding.attestationBindingDigestHex !== expectedBindingDigestHex || bindingSignature.signerDid !== binding.nodeAudience || bindingSignature.suite !== "Ed25519" || Date.parse(binding.issuedAt) > Date.now() || Date.parse(binding.expiresAt) <= Date.now() || Date.parse(binding.expiresAt) < Date.parse(parsed.expiry)) return false;
|
|
5995
5896
|
try {
|
|
5996
5897
|
const digest3 = (0, import_sha256.sha256)(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize(unsignedBinding)}`));
|
|
5997
5898
|
if (!import_ed255193.ed25519.verify(fromBase64Url(bindingSignature.value), digest3, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
|
|
@@ -6033,7 +5934,6 @@ function base32Lower(bytes3) {
|
|
|
6033
5934
|
return output;
|
|
6034
5935
|
}
|
|
6035
5936
|
var KEY_LENGTH2 = 32;
|
|
6036
|
-
var INLINE_PREFIX = "#v=2&p=";
|
|
6037
5937
|
var MAX_INLINE_BYTES = 256 * 1024;
|
|
6038
5938
|
function assertCanonicalCid(cidString) {
|
|
6039
5939
|
const cid2 = CID.parse(cidString);
|
|
@@ -6042,120 +5942,67 @@ function assertCanonicalCid(cidString) {
|
|
|
6042
5942
|
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
6043
5943
|
}
|
|
6044
5944
|
}
|
|
6045
|
-
function
|
|
6046
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
6047
|
-
throw new TypeError(`key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
6048
|
-
}
|
|
6049
|
-
assertCanonicalCid(ciphertextCid);
|
|
6050
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
6051
|
-
throw new TypeError(`origin must be a canonical https origin, got ${origin}`);
|
|
6052
|
-
}
|
|
6053
|
-
return `${origin}/s/${ciphertextCid}#k=${toBase64Url(key32)}`;
|
|
6054
|
-
}
|
|
6055
|
-
function parseShareUrl(url, options = {}) {
|
|
6056
|
-
const parsed = new URL(url);
|
|
6057
|
-
if (parsed.protocol !== "https:") {
|
|
6058
|
-
throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
|
|
6059
|
-
}
|
|
6060
|
-
if (parsed.username !== "" || parsed.password !== "") {
|
|
6061
|
-
throw new TypeError("share URL must not carry userinfo");
|
|
6062
|
-
}
|
|
6063
|
-
if (parsed.search !== "") {
|
|
6064
|
-
throw new TypeError("share URL must not have a query string");
|
|
6065
|
-
}
|
|
6066
|
-
if (options.expectedOrigin !== void 0) {
|
|
6067
|
-
if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
|
|
6068
|
-
throw new TypeError(
|
|
6069
|
-
`expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
|
|
6070
|
-
);
|
|
6071
|
-
}
|
|
6072
|
-
if (parsed.origin !== options.expectedOrigin) {
|
|
6073
|
-
throw new TypeError(
|
|
6074
|
-
`share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
|
|
6075
|
-
);
|
|
6076
|
-
}
|
|
6077
|
-
}
|
|
6078
|
-
const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
|
|
6079
|
-
if (!match || match[1] === void 0) {
|
|
6080
|
-
throw new TypeError(`not a share URL path: ${parsed.pathname}`);
|
|
6081
|
-
}
|
|
6082
|
-
const ciphertextCid = match[1];
|
|
6083
|
-
assertCanonicalCid(ciphertextCid);
|
|
6084
|
-
if (!parsed.hash.startsWith("#k=")) {
|
|
6085
|
-
throw new TypeError("share URL is missing the #k= key fragment");
|
|
6086
|
-
}
|
|
6087
|
-
const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
|
|
6088
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
6089
|
-
throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
6090
|
-
}
|
|
6091
|
-
return { ciphertextCid, key32 };
|
|
6092
|
-
}
|
|
6093
|
-
async function encodeInlineShareUrl(parts) {
|
|
5945
|
+
async function encodeSealedInlineShareUrl(parts) {
|
|
6094
5946
|
if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
|
|
6095
5947
|
if (parts.ciphertext.byteLength === 0 || parts.ciphertext.byteLength > MAX_INLINE_BYTES) throw new RangeError("inline ciphertext is outside the allowed size");
|
|
6096
|
-
if (parts.key32
|
|
5948
|
+
if (parts.key32.byteLength !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
|
|
6097
5949
|
const ciphertextCid = await computeCid(parts.ciphertext);
|
|
6098
5950
|
const payload = canonicalize({
|
|
6099
5951
|
v: 2,
|
|
6100
5952
|
c: toBase64Url(parts.ciphertext),
|
|
6101
5953
|
cid: ciphertextCid,
|
|
6102
|
-
|
|
5954
|
+
k: toBase64Url(parts.key32)
|
|
6103
5955
|
});
|
|
6104
5956
|
const payloadBytes = new TextEncoder().encode(payload);
|
|
6105
5957
|
if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
|
|
6106
|
-
return `${parts.origin}/s/inline
|
|
5958
|
+
return `${parts.origin}/s/inline#v=2&p=${toBase64Url(payloadBytes)}`;
|
|
6107
5959
|
}
|
|
6108
|
-
function
|
|
5960
|
+
async function parseSealedInlineShareUrl(url, options = {}) {
|
|
6109
5961
|
const parsed = new URL(url);
|
|
6110
|
-
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== ""
|
|
6111
|
-
if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
|
|
6112
|
-
if (parsed.pathname !== "/s/inline" ||
|
|
5962
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("sealed inline share URL must be canonical HTTPS without userinfo");
|
|
5963
|
+
if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("sealed inline share URL origin is not trusted");
|
|
5964
|
+
if (parsed.pathname !== "/s/inline" || parsed.search !== "") throw new TypeError("not a Node sealed-inline share URL");
|
|
5965
|
+
if (url !== `${parsed.origin}/s/inline${parsed.hash}`) throw new TypeError("sealed inline share URL is not lexically canonical");
|
|
5966
|
+
const prefix = "#v=2&p=";
|
|
5967
|
+
if (!parsed.hash.startsWith(prefix)) throw new TypeError("sealed inline URL is missing its canonical fragment");
|
|
5968
|
+
const encoded = parsed.hash.slice(prefix.length);
|
|
5969
|
+
if (encoded.length === 0 || parsed.hash !== `${prefix}${encoded}`) throw new TypeError("sealed inline URL fragment is not canonical");
|
|
6113
5970
|
let payloadBytes;
|
|
6114
5971
|
try {
|
|
6115
|
-
payloadBytes = fromBase64Url(
|
|
5972
|
+
payloadBytes = fromBase64Url(encoded);
|
|
6116
5973
|
} catch {
|
|
6117
|
-
throw new TypeError("inline payload is not canonical base64url");
|
|
5974
|
+
throw new TypeError("sealed inline payload is not canonical base64url");
|
|
6118
5975
|
}
|
|
6119
|
-
if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("inline payload is too large");
|
|
5976
|
+
if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("sealed inline payload is too large");
|
|
5977
|
+
const payloadText = new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes);
|
|
6120
5978
|
let value;
|
|
6121
5979
|
try {
|
|
6122
|
-
value = JSON.parse(
|
|
5980
|
+
value = JSON.parse(payloadText);
|
|
6123
5981
|
} catch {
|
|
6124
|
-
throw new TypeError("inline payload is not valid JSON");
|
|
5982
|
+
throw new TypeError("sealed inline payload is not valid JSON");
|
|
6125
5983
|
}
|
|
6126
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
const keys = Object.keys(record2);
|
|
6130
|
-
if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") || record2.v !== 2 || typeof record2.c !== "string" || typeof record2.cid !== "string") throw new TypeError("inline payload has invalid fields");
|
|
5984
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || canonicalize(value) !== payloadText) throw new TypeError("sealed inline payload is not canonical JSON");
|
|
5985
|
+
const record = value;
|
|
5986
|
+
if (Object.keys(record).length !== 4 || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || typeof record.k !== "string") throw new TypeError("sealed inline payload has invalid fields");
|
|
6131
5987
|
let ciphertext;
|
|
6132
|
-
try {
|
|
6133
|
-
ciphertext = fromBase64Url(record2.c);
|
|
6134
|
-
} catch {
|
|
6135
|
-
throw new TypeError("inline ciphertext is not canonical base64url");
|
|
6136
|
-
}
|
|
6137
|
-
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
|
|
6138
|
-
assertCanonicalCid(record2.cid);
|
|
6139
|
-
if (record2.k !== void 0 && typeof record2.k !== "string") throw new TypeError("inline key must be base64url");
|
|
6140
5988
|
let key32;
|
|
6141
5989
|
try {
|
|
6142
|
-
|
|
5990
|
+
ciphertext = fromBase64Url(record.c);
|
|
5991
|
+
key32 = fromBase64Url(record.k);
|
|
6143
5992
|
} catch {
|
|
6144
|
-
throw new TypeError("inline
|
|
5993
|
+
throw new TypeError("sealed inline payload has invalid base64url material");
|
|
6145
5994
|
}
|
|
6146
|
-
if (
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
return { kind: "compact", ...parseShareUrl(url, options) };
|
|
5995
|
+
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("sealed inline ciphertext is outside the allowed size");
|
|
5996
|
+
if (key32.byteLength !== KEY_LENGTH2) throw new TypeError("sealed inline key must be 32 bytes");
|
|
5997
|
+
assertCanonicalCid(record.cid);
|
|
5998
|
+
if (await computeCid(ciphertext) !== record.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
|
|
5999
|
+
return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
|
|
6152
6000
|
}
|
|
6153
6001
|
|
|
6154
6002
|
// src/receive.ts
|
|
6155
6003
|
var SHARE_RESULT_VERSION = 1;
|
|
6156
6004
|
var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
|
|
6157
6005
|
var DEFAULT_MAX_CONTENT_BLOB_BYTES = DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
6158
|
-
var CONTENT_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
6159
6006
|
var ShareReceiveError = class extends Error {
|
|
6160
6007
|
code;
|
|
6161
6008
|
details;
|
|
@@ -6165,7 +6012,6 @@ var ShareReceiveError = class extends Error {
|
|
|
6165
6012
|
this.code = code3;
|
|
6166
6013
|
this.details = details;
|
|
6167
6014
|
}
|
|
6168
|
-
/** Machine output is deliberately code-only; diagnostics belong on stderr. */
|
|
6169
6015
|
toJSON() {
|
|
6170
6016
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: this.code } };
|
|
6171
6017
|
}
|
|
@@ -6174,338 +6020,77 @@ function toShareErrorInfo(error) {
|
|
|
6174
6020
|
if (error instanceof ShareReceiveError) return error.toJSON();
|
|
6175
6021
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: "fetch-failed" } };
|
|
6176
6022
|
}
|
|
6177
|
-
function boundedBytes(value, limit, code3) {
|
|
6178
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("fetch-failed", "share byte limit is invalid");
|
|
6179
|
-
if (!(value instanceof Uint8Array)) throw new ShareReceiveError("fetch-failed", "registry returned invalid bytes");
|
|
6180
|
-
if (value.byteLength > limit) throw new ShareReceiveError(code3, "share blob exceeds the configured byte limit");
|
|
6181
|
-
return value;
|
|
6182
|
-
}
|
|
6183
|
-
async function readResponseBytes(response, limit, tooLargeCode) {
|
|
6184
|
-
const contentLength = response.headers.get("content-length");
|
|
6185
|
-
if (contentLength !== null && Number.isSafeInteger(Number(contentLength)) && Number(contentLength) > limit) {
|
|
6186
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
6187
|
-
}
|
|
6188
|
-
if (response.body === null) {
|
|
6189
|
-
return boundedBytes(new Uint8Array(await response.arrayBuffer()), limit, tooLargeCode);
|
|
6190
|
-
}
|
|
6191
|
-
const reader = response.body.getReader();
|
|
6192
|
-
const chunks = [];
|
|
6193
|
-
let total = 0;
|
|
6194
|
-
try {
|
|
6195
|
-
for (; ; ) {
|
|
6196
|
-
const part = await reader.read();
|
|
6197
|
-
if (part.done) break;
|
|
6198
|
-
total += part.value.byteLength;
|
|
6199
|
-
if (total > limit) {
|
|
6200
|
-
await reader.cancel();
|
|
6201
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
6202
|
-
}
|
|
6203
|
-
chunks.push(part.value);
|
|
6204
|
-
}
|
|
6205
|
-
} finally {
|
|
6206
|
-
reader.releaseLock();
|
|
6207
|
-
}
|
|
6208
|
-
const bytes3 = new Uint8Array(total);
|
|
6209
|
-
let offset = 0;
|
|
6210
|
-
for (const chunk of chunks) {
|
|
6211
|
-
bytes3.set(chunk, offset);
|
|
6212
|
-
offset += chunk.byteLength;
|
|
6213
|
-
}
|
|
6214
|
-
return bytes3;
|
|
6215
|
-
}
|
|
6216
|
-
function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
|
|
6217
|
-
if (options.fetchBlob !== void 0) return async (input) => {
|
|
6218
|
-
try {
|
|
6219
|
-
options.signal?.throwIfAborted();
|
|
6220
|
-
const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
6221
|
-
options.signal?.throwIfAborted();
|
|
6222
|
-
return boundedBytes(bytes3, limit, tooLargeCode);
|
|
6223
|
-
} catch (error) {
|
|
6224
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6225
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
6226
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6227
|
-
}
|
|
6228
|
-
};
|
|
6229
|
-
if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "a registry fetch adapter is required");
|
|
6230
|
-
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
6231
|
-
const base3 = options.registryBaseUrl.replace(/\/+$/, "");
|
|
6232
|
-
return async ({ cid: cid2 }) => {
|
|
6233
|
-
try {
|
|
6234
|
-
options.signal?.throwIfAborted();
|
|
6235
|
-
const response = await fetchFn(`${base3}/ipfs/${cid2}?format=raw`, {
|
|
6236
|
-
headers: { accept: "application/vnd.ipld.raw" },
|
|
6237
|
-
redirect: "error",
|
|
6238
|
-
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
6239
|
-
});
|
|
6240
|
-
options.signal?.throwIfAborted();
|
|
6241
|
-
if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
|
|
6242
|
-
const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
|
|
6243
|
-
options.signal?.throwIfAborted();
|
|
6244
|
-
return bytes3;
|
|
6245
|
-
} catch (error) {
|
|
6246
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6247
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
6248
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6249
|
-
}
|
|
6250
|
-
};
|
|
6251
|
-
}
|
|
6252
6023
|
function metadataFor(envelope, origin) {
|
|
6253
|
-
|
|
6254
|
-
const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
6255
|
-
return {
|
|
6256
|
-
protocol: "tinycloud-share",
|
|
6257
|
-
version: 1,
|
|
6258
|
-
shareId: envelope.shareId,
|
|
6259
|
-
origin,
|
|
6260
|
-
target: { ...envelope.target, kind: targetKind3 },
|
|
6261
|
-
resource: envelope.resource,
|
|
6262
|
-
actions: envelope.actions,
|
|
6263
|
-
expiresAt: envelope.expiry,
|
|
6264
|
-
display: {
|
|
6265
|
-
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
6266
|
-
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
6267
|
-
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6268
|
-
},
|
|
6269
|
-
...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
|
|
6270
|
-
};
|
|
6271
|
-
}
|
|
6024
|
+
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
|
|
6272
6025
|
return {
|
|
6273
6026
|
protocol: "tinycloud-share",
|
|
6274
6027
|
version: 1,
|
|
6275
6028
|
shareId: envelope.shareId,
|
|
6276
6029
|
origin,
|
|
6277
|
-
target: { ...envelope.target, kind
|
|
6278
|
-
resource: envelope.
|
|
6279
|
-
actions: [
|
|
6030
|
+
target: { ...envelope.target, kind },
|
|
6031
|
+
resource: { ...envelope.resource },
|
|
6032
|
+
actions: [...envelope.actions],
|
|
6280
6033
|
expiresAt: envelope.expiry,
|
|
6281
6034
|
display: {
|
|
6282
6035
|
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
6283
6036
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
6284
6037
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6285
|
-
}
|
|
6286
|
-
...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
|
|
6038
|
+
}
|
|
6287
6039
|
};
|
|
6288
6040
|
}
|
|
6289
|
-
function
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
const value = JSON.parse(text);
|
|
6293
|
-
if (canonicalize(value) !== text) throw new Error("share envelope is not canonical JSON");
|
|
6294
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
6295
|
-
const version = value.version;
|
|
6296
|
-
if (version === 3) return shareEnvelopeV3Schema.parse(value);
|
|
6297
|
-
if (version === 2) return shareEnvelopeV2Schema.parse(value);
|
|
6298
|
-
}
|
|
6299
|
-
return shareEnvelopeSchema.parse(value);
|
|
6300
|
-
} catch {
|
|
6301
|
-
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
6302
|
-
}
|
|
6303
|
-
}
|
|
6304
|
-
async function resolveShareEnvelope(link, options = {}) {
|
|
6041
|
+
async function resolvePolicyShare(link, options) {
|
|
6042
|
+
options.signal?.throwIfAborted();
|
|
6043
|
+
let url;
|
|
6305
6044
|
let parsed;
|
|
6306
6045
|
try {
|
|
6307
|
-
|
|
6046
|
+
url = new URL(link);
|
|
6047
|
+
parsed = await parseSealedInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
|
|
6308
6048
|
} catch {
|
|
6309
6049
|
throw new ShareReceiveError("invalid-link", "share link format is invalid");
|
|
6310
6050
|
}
|
|
6051
|
+
const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
6052
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed policy envelope exceeds the configured byte limit");
|
|
6053
|
+
if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "sealed envelope bytes do not match the link CID");
|
|
6054
|
+
options.signal?.throwIfAborted();
|
|
6055
|
+
let envelope;
|
|
6311
6056
|
try {
|
|
6312
|
-
|
|
6313
|
-
const
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
let plaintext;
|
|
6317
|
-
try {
|
|
6318
|
-
plaintext = parsed.key32 === void 0 ? sealed : await open(sealed, parsed.key32);
|
|
6319
|
-
} catch {
|
|
6320
|
-
throw new ShareReceiveError("decrypt-failed", "share envelope could not be opened");
|
|
6321
|
-
}
|
|
6322
|
-
return { envelope: parseEnvelope(plaintext), origin: url.origin, cid: parsed.ciphertextCid, kind: parsed.kind };
|
|
6323
|
-
} finally {
|
|
6324
|
-
parsed.key32?.fill(0);
|
|
6325
|
-
}
|
|
6326
|
-
}
|
|
6327
|
-
function actionName(value) {
|
|
6328
|
-
return value === "read" ? "tinycloud.kv/get" : value === "list" ? "tinycloud.kv/list" : value === "edit" ? "tinycloud.kv/put" : value;
|
|
6329
|
-
}
|
|
6330
|
-
function resourceContains(outer, inner) {
|
|
6331
|
-
if (outer.kind === "exact") return inner.kind === "exact" && outer.path === inner.path;
|
|
6332
|
-
const prefix = outer.path.endsWith("/") ? outer.path : `${outer.path}/`;
|
|
6333
|
-
return inner.path === outer.path || inner.path.startsWith(prefix);
|
|
6334
|
-
}
|
|
6335
|
-
function policyEvidenceMatches(envelope, evidence) {
|
|
6336
|
-
const registeredActions = new Set(evidence.actions.map(actionName));
|
|
6337
|
-
return evidence.policyCid === (envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "") && evidence.signerDid === envelope.signature.signerDid && evidence.registrationCid.length > 0 && evidence.shareId === envelope.shareId && canonicalize(evidence.recipientMatcher) === canonicalize(envelope.recipientMatcher) && canonicalize(evidence.target) === canonicalize(envelope.target) && resourceContains(evidence.resource, envelope.resource) && envelope.actions.every((action) => registeredActions.has(actionName(action))) && canonicalize(evidence.contentSource) === canonicalize(envelope.contentSource) && evidence.contentSourceDigest === envelope.contentSourceDigest && evidence.delegationCid === envelope.delegationCid && evidence.authorityMaterialHandle === envelope.authorityMaterialHandle && evidence.authorityMaterialDigest === envelope.authorityMaterialDigest && evidence.expiresAt === envelope.expiry && Number.isFinite(Date.parse(evidence.expiresAt)) && Date.parse(evidence.expiresAt) === Date.parse(envelope.expiry);
|
|
6338
|
-
}
|
|
6339
|
-
async function verifyV2Envelope(envelope, linkOrigin, options) {
|
|
6340
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
6341
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
6342
|
-
}
|
|
6343
|
-
try {
|
|
6344
|
-
if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
6057
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(await open(parsed.ciphertext, parsed.key32));
|
|
6058
|
+
const value = JSON.parse(encoded);
|
|
6059
|
+
if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
|
|
6060
|
+
envelope = shareEnvelopeV3Schema.parse(value);
|
|
6345
6061
|
} catch {
|
|
6346
|
-
throw new ShareReceiveError("
|
|
6347
|
-
}
|
|
6348
|
-
const expiry = Date.parse(envelope.expiry);
|
|
6349
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6350
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6351
|
-
if (envelope.authorizationTarget.kind === "policy") {
|
|
6352
|
-
if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "an external policy authority is required");
|
|
6353
|
-
let evidence;
|
|
6354
|
-
try {
|
|
6355
|
-
evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
|
|
6356
|
-
} catch {
|
|
6357
|
-
evidence = void 0;
|
|
6358
|
-
}
|
|
6359
|
-
if (evidence === void 0 || !policyEvidenceMatches(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "share policy authority evidence is not bound to the envelope");
|
|
6360
|
-
if (options.trustedSignerDid !== void 0 && (evidence.signerDid !== options.trustedSignerDid || envelope.signature.signerDid !== options.trustedSignerDid)) {
|
|
6361
|
-
throw new ShareReceiveError("signature-invalid", "share signer is not trusted");
|
|
6362
|
-
}
|
|
6363
|
-
} else if (envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
|
|
6364
|
-
throw new ShareReceiveError("capability-invalid", "recipient authorization does not match the signed recipient");
|
|
6365
|
-
} else {
|
|
6366
|
-
if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
|
|
6367
|
-
}
|
|
6368
|
-
}
|
|
6369
|
-
async function verifyV3Envelope(envelope, linkOrigin, options) {
|
|
6370
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
6371
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
6062
|
+
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
6372
6063
|
}
|
|
6373
6064
|
try {
|
|
6374
6065
|
if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
|
|
6375
6066
|
} catch {
|
|
6376
6067
|
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
6377
6068
|
}
|
|
6069
|
+
options.signal?.throwIfAborted();
|
|
6378
6070
|
const expiry = Date.parse(envelope.expiry);
|
|
6379
6071
|
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6380
6072
|
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
|
|
6384
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
6385
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
6386
|
-
}
|
|
6387
|
-
throw new ShareReceiveError("unsupported-target", "share policy target is not supported", { reason: "policy-target" });
|
|
6388
|
-
}
|
|
6389
|
-
async function verifyBearerEnvelope(envelope, linkOrigin, options = {}) {
|
|
6390
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
6391
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
6392
|
-
}
|
|
6393
|
-
if (envelope.authorizationTarget.kind !== "bearerKey") throw new ShareReceiveError("unsupported-target", "this receive path only handles bearer shares", { reason: envelope.authorizationTarget.kind === "policy" ? "policy-target" : "recipient-did-target" });
|
|
6394
|
-
if (envelope.target.resource.kind !== "exact") throw new ShareReceiveError("unsupported-target", "this receive path only handles exact resources", { reason: "prefix-resource" });
|
|
6395
|
-
const expiry = Date.parse(envelope.expiry);
|
|
6396
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6397
|
-
try {
|
|
6398
|
-
if (!await verifyEnvelope(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
6399
|
-
} catch {
|
|
6400
|
-
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
6401
|
-
}
|
|
6402
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6403
|
-
const capability = checkBearerDelegation(envelope, options.now === void 0 ? {} : { now: options.now });
|
|
6404
|
-
if (!capability.ok) throw new ShareReceiveError("capability-invalid", "share delegation does not authorize the target");
|
|
6405
|
-
}
|
|
6406
|
-
async function verifyResolved(envelope, linkOrigin, options) {
|
|
6407
|
-
await verifyBearerEnvelope(envelope, linkOrigin, options);
|
|
6073
|
+
options.onResolvedAddressedEnvelope?.(envelope, parsed.ciphertextCid);
|
|
6074
|
+
return { envelope, origin: url.origin, cid: parsed.ciphertextCid };
|
|
6408
6075
|
}
|
|
6409
6076
|
async function inspectShare(link, options = {}) {
|
|
6410
|
-
const resolved = await
|
|
6411
|
-
|
|
6412
|
-
else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
6413
|
-
else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6414
|
-
else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6415
|
-
if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6416
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
|
|
6077
|
+
const resolved = await resolvePolicyShare(link, options);
|
|
6078
|
+
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
|
|
6417
6079
|
}
|
|
6418
6080
|
async function receiveShare(link, options = {}) {
|
|
6419
|
-
const resolved = await
|
|
6420
|
-
|
|
6421
|
-
await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6422
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6423
|
-
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6424
|
-
}
|
|
6425
|
-
if (resolved.envelope.version === 2) {
|
|
6426
|
-
await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6427
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6428
|
-
const method = resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
|
|
6429
|
-
if (options.authorization === void 0) return { state: "authorization-required", method };
|
|
6430
|
-
const result = options.authorizationResumeToken === void 0 ? await options.authorization.begin({ envelope: resolved.envelope, method }) : await options.authorization.resume({ envelope: resolved.envelope, method, resumeToken: options.authorizationResumeToken, ...options.authorizationProof === void 0 ? {} : { proof: options.authorizationProof } });
|
|
6431
|
-
if (result.state === "authorization-required") return result;
|
|
6432
|
-
if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "share authorization was denied");
|
|
6433
|
-
if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
|
|
6434
|
-
throw new ShareReceiveError("content-integrity-failed", "share authorization returned an unsigned content result");
|
|
6435
|
-
}
|
|
6436
|
-
const authorized = result.value;
|
|
6437
|
-
try {
|
|
6438
|
-
if (!await options.authorization.verifyResult({ envelope: resolved.envelope, value: authorized, proof: authorized.proof })) throw new Error("node proof");
|
|
6439
|
-
} catch {
|
|
6440
|
-
throw new ShareReceiveError("content-integrity-failed", "node authorization proof is invalid");
|
|
6441
|
-
}
|
|
6442
|
-
const bytes4 = authorized.bytes.slice();
|
|
6443
|
-
const maxBytes = options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES;
|
|
6444
|
-
if (bytes4.byteLength > maxBytes) throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
6445
|
-
const digestBytes3 = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes4));
|
|
6446
|
-
const bodyDigest = toBase64Url(digestBytes3);
|
|
6447
|
-
if (authorized.bodyDigest !== bodyDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content digest does not match");
|
|
6448
|
-
if (authorized.contentSourceDigest !== resolved.envelope.contentSourceDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content source does not match");
|
|
6449
|
-
const binding = authorized.binding;
|
|
6450
|
-
if (binding.shareId !== resolved.envelope.shareId || binding.delegationCid !== resolved.envelope.delegationCid || binding.authorityMaterialHandle !== resolved.envelope.authorityMaterialHandle || binding.authorityMaterialDigest !== resolved.envelope.authorityMaterialDigest || binding.resource.kind !== resolved.envelope.resource.kind || binding.resource.path !== resolved.envelope.resource.path || binding.action !== void 0 && !resolved.envelope.actions.some((action) => action === binding.action || `tinycloud.kv/${action}` === binding.action)) throw new ShareReceiveError("content-integrity-failed", "authorized content binding does not match");
|
|
6451
|
-
if (resolved.envelope.metadata.byteLength !== void 0 && resolved.envelope.metadata.byteLength !== bytes4.byteLength) {
|
|
6452
|
-
throw new ShareReceiveError("content-integrity-failed", "authorized content length does not match");
|
|
6453
|
-
}
|
|
6454
|
-
let text2;
|
|
6455
|
-
try {
|
|
6456
|
-
text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes4);
|
|
6457
|
-
} catch {
|
|
6458
|
-
}
|
|
6459
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes4, ...text2 === void 0 ? {} : { text: text2 } };
|
|
6460
|
-
}
|
|
6461
|
-
if (resolved.envelope.authorizationTarget.kind === "policy") {
|
|
6462
|
-
await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
|
|
6463
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6464
|
-
return { state: "authorization-required", method: "email-claim" };
|
|
6465
|
-
}
|
|
6466
|
-
await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
6467
|
-
let bytes3 = new Uint8Array();
|
|
6468
|
-
if (resolved.envelope.content !== void 0) {
|
|
6469
|
-
bytes3 = await registryFetcher(
|
|
6470
|
-
options,
|
|
6471
|
-
(options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES) + CONTENT_SEALED_OVERHEAD,
|
|
6472
|
-
"max-bytes-exceeded",
|
|
6473
|
-
"content"
|
|
6474
|
-
)({ origin: resolved.origin, cid: resolved.envelope.content.cid });
|
|
6475
|
-
if (await computeCid(bytes3) !== resolved.envelope.content.cid) throw new ShareReceiveError("content-integrity-failed", "shared content CID does not match");
|
|
6476
|
-
const key = fromBase64Url(resolved.envelope.content.key);
|
|
6477
|
-
try {
|
|
6478
|
-
bytes3 = await open(bytes3, key);
|
|
6479
|
-
} catch {
|
|
6480
|
-
throw new ShareReceiveError("content-integrity-failed", "shared content could not be opened");
|
|
6481
|
-
} finally {
|
|
6482
|
-
key.fill(0);
|
|
6483
|
-
}
|
|
6484
|
-
if (bytes3.byteLength > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) {
|
|
6485
|
-
throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
6486
|
-
}
|
|
6487
|
-
}
|
|
6488
|
-
let text;
|
|
6489
|
-
if (bytes3.byteLength !== 0) {
|
|
6490
|
-
try {
|
|
6491
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
|
|
6492
|
-
} catch {
|
|
6493
|
-
}
|
|
6494
|
-
}
|
|
6495
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes3, ...text === void 0 ? {} : { text } };
|
|
6081
|
+
const resolved = await resolvePolicyShare(link, options);
|
|
6082
|
+
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6496
6083
|
}
|
|
6497
6084
|
|
|
6498
6085
|
// src/publish.ts
|
|
6499
|
-
var import_ed255194 = require("@noble/curves/ed25519");
|
|
6500
6086
|
var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
|
|
6501
|
-
var SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
6502
6087
|
var SHARE_PUBLISH_RESULT_VERSION = 1;
|
|
6503
6088
|
var DEFAULT_SHARE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6504
6089
|
function redactPublishedShare(result) {
|
|
6505
6090
|
return {
|
|
6506
6091
|
protocol: "tinycloud-share",
|
|
6507
6092
|
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
6508
|
-
link: {
|
|
6093
|
+
link: { ...result.link },
|
|
6509
6094
|
metadata: {
|
|
6510
6095
|
protocol: "tinycloud-share",
|
|
6511
6096
|
version: 1,
|
|
@@ -6515,10 +6100,8 @@ function redactPublishedShare(result) {
|
|
|
6515
6100
|
resource: { ...result.metadata.resource },
|
|
6516
6101
|
actions: [...result.metadata.actions],
|
|
6517
6102
|
expiresAt: result.metadata.expiresAt,
|
|
6518
|
-
display: { ...result.metadata.display }
|
|
6519
|
-
|
|
6520
|
-
},
|
|
6521
|
-
registryDeleteAfter: result.registryDeleteAfter
|
|
6103
|
+
display: { ...result.metadata.display }
|
|
6104
|
+
}
|
|
6522
6105
|
};
|
|
6523
6106
|
}
|
|
6524
6107
|
var SharePublishError = class extends Error {
|
|
@@ -6529,237 +6112,6 @@ var SharePublishError = class extends Error {
|
|
|
6529
6112
|
this.code = code3;
|
|
6530
6113
|
}
|
|
6531
6114
|
};
|
|
6532
|
-
function assertSafeFilename(filename) {
|
|
6533
|
-
if (filename.length === 0 || filename === "." || filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(filename)) {
|
|
6534
|
-
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
6535
|
-
}
|
|
6536
|
-
}
|
|
6537
|
-
function assertOrigin(origin, label) {
|
|
6538
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
6539
|
-
throw new SharePublishError("invalid-argument", `${label} must be a canonical HTTPS origin`);
|
|
6540
|
-
}
|
|
6541
|
-
}
|
|
6542
|
-
async function snapshotSource(source, limit) {
|
|
6543
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
6544
|
-
throw new SharePublishError("invalid-argument", "maxBytes must be a positive safe integer");
|
|
6545
|
-
}
|
|
6546
|
-
if (source instanceof Uint8Array) {
|
|
6547
|
-
if (source.byteLength > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
6548
|
-
return source.slice();
|
|
6549
|
-
}
|
|
6550
|
-
const chunks = [];
|
|
6551
|
-
let total = 0;
|
|
6552
|
-
try {
|
|
6553
|
-
for await (const chunk of source) {
|
|
6554
|
-
if (!(chunk instanceof Uint8Array)) throw new SharePublishError("invalid-argument", "share source yielded invalid bytes");
|
|
6555
|
-
total += chunk.byteLength;
|
|
6556
|
-
if (total > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
6557
|
-
chunks.push(chunk);
|
|
6558
|
-
}
|
|
6559
|
-
} catch (error) {
|
|
6560
|
-
if (error instanceof SharePublishError) throw error;
|
|
6561
|
-
throw new SharePublishError("upload-failed", "share source could not be read");
|
|
6562
|
-
}
|
|
6563
|
-
const bytes3 = new Uint8Array(total);
|
|
6564
|
-
let offset = 0;
|
|
6565
|
-
for (const chunk of chunks) {
|
|
6566
|
-
bytes3.set(chunk, offset);
|
|
6567
|
-
offset += chunk.byteLength;
|
|
6568
|
-
}
|
|
6569
|
-
return bytes3;
|
|
6570
|
-
}
|
|
6571
|
-
function assertTarget(target) {
|
|
6572
|
-
if (target.kind !== "bearer") {
|
|
6573
|
-
throw new SharePublishError("unsupported-target", "this release publishes bearer shares only");
|
|
6574
|
-
}
|
|
6575
|
-
}
|
|
6576
|
-
function assertRegistryUrl(value, allowInsecure) {
|
|
6577
|
-
let url;
|
|
6578
|
-
try {
|
|
6579
|
-
url = new URL(value);
|
|
6580
|
-
} catch {
|
|
6581
|
-
throw new SharePublishError("invalid-argument", "registry URL is invalid");
|
|
6582
|
-
}
|
|
6583
|
-
if (url.username || url.password || url.search || url.hash) throw new SharePublishError("invalid-argument", "registry URL must not contain credentials, query, or fragment");
|
|
6584
|
-
if (!allowInsecure && url.protocol !== "https:") throw new SharePublishError("invalid-argument", "production registry uploads require HTTPS");
|
|
6585
|
-
if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost") throw new SharePublishError("invalid-argument", "insecure registry uploads are limited to localhost");
|
|
6586
|
-
return url;
|
|
6587
|
-
}
|
|
6588
|
-
async function defaultUpload(options, input) {
|
|
6589
|
-
if (options.registryBaseUrl === void 0) throw new SharePublishError("upload-auth-required", "an authenticated share registry is required");
|
|
6590
|
-
const base3 = assertRegistryUrl(options.registryBaseUrl, options.allowInsecureRegistry === true).toString().replace(/\/$/, "");
|
|
6591
|
-
const localInsecure = options.allowInsecureRegistry === true && (new URL(base3).hostname === "127.0.0.1" || new URL(base3).hostname === "localhost");
|
|
6592
|
-
if (options.authorizeUpload === void 0 && !localInsecure) {
|
|
6593
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization is required");
|
|
6594
|
-
}
|
|
6595
|
-
let authorization;
|
|
6596
|
-
if (options.authorizeUpload !== void 0) {
|
|
6597
|
-
try {
|
|
6598
|
-
authorization = await options.authorizeUpload(input);
|
|
6599
|
-
} catch {
|
|
6600
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6601
|
-
}
|
|
6602
|
-
}
|
|
6603
|
-
if (authorization !== void 0 && options.authorizationOrigin !== void 0) {
|
|
6604
|
-
let authorizationUrl;
|
|
6605
|
-
try {
|
|
6606
|
-
authorizationUrl = new URL(options.authorizationOrigin);
|
|
6607
|
-
} catch {
|
|
6608
|
-
throw new SharePublishError("invalid-argument", "upload authorization origin is invalid");
|
|
6609
|
-
}
|
|
6610
|
-
if (authorizationUrl.origin !== new URL(base3).origin) throw new SharePublishError("upload-auth-required", "upload authorization is scoped to another origin");
|
|
6611
|
-
}
|
|
6612
|
-
const headers = new Headers({
|
|
6613
|
-
"content-type": "application/vnd.ipld.raw",
|
|
6614
|
-
"if-none-match": "*",
|
|
6615
|
-
"x-delete-after": input.deleteAfter
|
|
6616
|
-
});
|
|
6617
|
-
if (typeof authorization === "string") {
|
|
6618
|
-
headers.set("x-tinycloud-upload-attestation", authorization);
|
|
6619
|
-
try {
|
|
6620
|
-
const attestation = JSON.parse(authorization);
|
|
6621
|
-
if (attestation.retention === void 0) throw new Error("retention");
|
|
6622
|
-
headers.set("x-tinycloud-retention", canonicalize(attestation.retention));
|
|
6623
|
-
} catch {
|
|
6624
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6625
|
-
}
|
|
6626
|
-
} else if (authorization !== void 0) {
|
|
6627
|
-
new Headers(authorization).forEach((value, key) => headers.set(key, value));
|
|
6628
|
-
}
|
|
6629
|
-
let response;
|
|
6630
|
-
try {
|
|
6631
|
-
response = await (options.fetchFn ?? globalThis.fetch)(`${base3}/blobs`, {
|
|
6632
|
-
method: "POST",
|
|
6633
|
-
headers,
|
|
6634
|
-
body: input.blob,
|
|
6635
|
-
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
6636
|
-
redirect: "error",
|
|
6637
|
-
referrerPolicy: "no-referrer"
|
|
6638
|
-
});
|
|
6639
|
-
} catch {
|
|
6640
|
-
throw new SharePublishError("upload-failed", "share registry is unavailable");
|
|
6641
|
-
}
|
|
6642
|
-
if (response.status === 401 || response.status === 403) throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
6643
|
-
if (!response.ok) throw new SharePublishError("upload-failed", "share registry rejected the encrypted blob");
|
|
6644
|
-
let body;
|
|
6645
|
-
try {
|
|
6646
|
-
body = await response.json();
|
|
6647
|
-
} catch {
|
|
6648
|
-
throw new SharePublishError("upload-failed", "share registry returned an invalid response");
|
|
6649
|
-
}
|
|
6650
|
-
const record2 = typeof body === "object" && body !== null ? body : {};
|
|
6651
|
-
if (record2.cid !== input.cid || typeof record2.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share registry returned an unexpected CID or retention value");
|
|
6652
|
-
return { cid: input.cid, deleteAfter: record2.deleteAfter };
|
|
6653
|
-
}
|
|
6654
|
-
async function uploadShareBlob(options, input) {
|
|
6655
|
-
const result = await (options.uploadBlob ?? ((value) => defaultUpload(options, value)))(input);
|
|
6656
|
-
if (result.cid !== input.cid || typeof result.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share uploader returned an invalid result");
|
|
6657
|
-
return result;
|
|
6658
|
-
}
|
|
6659
|
-
async function publishShare(options) {
|
|
6660
|
-
const target = options.target ?? { kind: "bearer" };
|
|
6661
|
-
assertTarget(target);
|
|
6662
|
-
assertSafeFilename(options.filename);
|
|
6663
|
-
assertOrigin(options.origin, "share origin");
|
|
6664
|
-
if (options.mediaType !== void 0 && options.mediaType.length === 0) throw new SharePublishError("invalid-argument", "mediaType must not be empty");
|
|
6665
|
-
const maxBytes = options.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
6666
|
-
if (maxBytes > SHARE_CONTENT_LIMIT) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the maximum 100 MiB limit");
|
|
6667
|
-
const content = await snapshotSource(options.source, maxBytes);
|
|
6668
|
-
if (content.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
6669
|
-
if (options.allowBinary !== true) {
|
|
6670
|
-
try {
|
|
6671
|
-
new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
6672
|
-
} catch {
|
|
6673
|
-
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
6674
|
-
}
|
|
6675
|
-
}
|
|
6676
|
-
const nowMs = options.now?.() ?? Date.now();
|
|
6677
|
-
const expiresAt = options.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
|
|
6678
|
-
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
6679
|
-
const expiry = expiresAt.toISOString();
|
|
6680
|
-
const nodeAudience = options.nodeAudience ?? "did:web:node.tinycloud.xyz";
|
|
6681
|
-
const spaceId = options.spaceId ?? "bearer";
|
|
6682
|
-
const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
|
|
6683
|
-
const path = `shares/${shareId}/${options.filename}`;
|
|
6684
|
-
const sessionPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
|
|
6685
|
-
const senderPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
|
|
6686
|
-
let contentKey;
|
|
6687
|
-
let envelopeKey;
|
|
6688
|
-
try {
|
|
6689
|
-
const sessionPublicKey = import_ed255194.ed25519.getPublicKey(sessionPrivateKey);
|
|
6690
|
-
const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
|
|
6691
|
-
const delegation = mintBearerDelegation({
|
|
6692
|
-
issuerPrivateKey: senderPrivateKey,
|
|
6693
|
-
audienceDid: sessionDid,
|
|
6694
|
-
resourceUri: bearerResourceUri(options.origin, spaceId, path),
|
|
6695
|
-
expiresAtSeconds: Math.ceil(expiresAt.getTime() / 1e3)
|
|
6696
|
-
});
|
|
6697
|
-
contentKey = generateKey();
|
|
6698
|
-
const sealedContent = await seal(content, contentKey);
|
|
6699
|
-
const unsigned = {
|
|
6700
|
-
version: 1,
|
|
6701
|
-
shareId,
|
|
6702
|
-
delegation,
|
|
6703
|
-
authorizationTarget: {
|
|
6704
|
-
kind: "bearerKey",
|
|
6705
|
-
sessionJwk: { kty: "OKP", crv: "Ed25519", x: toBase64Url(sessionPublicKey), d: toBase64Url(sessionPrivateKey) }
|
|
6706
|
-
},
|
|
6707
|
-
target: { origin: options.origin, nodeAudience, spaceId, resource: { kind: "exact", path } },
|
|
6708
|
-
display: { filename: options.filename },
|
|
6709
|
-
expiry,
|
|
6710
|
-
content: { cid: sealedContent.cid, key: toBase64Url(contentKey) }
|
|
6711
|
-
};
|
|
6712
|
-
const envelope = signEnvelope(unsigned, senderPrivateKey);
|
|
6713
|
-
envelopeKey = generateKey();
|
|
6714
|
-
const sealedEnvelope = await seal(new TextEncoder().encode(canonicalize(envelope)), envelopeKey);
|
|
6715
|
-
let inlineUrl;
|
|
6716
|
-
if (options.inline === true) {
|
|
6717
|
-
try {
|
|
6718
|
-
inlineUrl = await encodeInlineShareUrl({ origin: options.origin, ciphertext: sealedEnvelope.blob, key32: envelopeKey });
|
|
6719
|
-
} catch (error) {
|
|
6720
|
-
throw new SharePublishError("inline-too-large", error instanceof Error ? error.message : "inline share is too large");
|
|
6721
|
-
}
|
|
6722
|
-
}
|
|
6723
|
-
const contentUpload = await uploadShareBlob(options, { blob: sealedContent.blob, cid: sealedContent.cid, deleteAfter: expiry, contentLength: sealedContent.blob.byteLength });
|
|
6724
|
-
let url;
|
|
6725
|
-
let kind;
|
|
6726
|
-
let retention = contentUpload.deleteAfter;
|
|
6727
|
-
if (options.inline === true) {
|
|
6728
|
-
if (inlineUrl === void 0) throw new SharePublishError("inline-too-large", "inline share is too large");
|
|
6729
|
-
url = inlineUrl;
|
|
6730
|
-
kind = "inline";
|
|
6731
|
-
} else {
|
|
6732
|
-
const envelopeUpload = await uploadShareBlob(options, { blob: sealedEnvelope.blob, cid: sealedEnvelope.cid, deleteAfter: expiry, contentLength: sealedEnvelope.blob.byteLength });
|
|
6733
|
-
url = encodeShareUrl({ origin: options.origin, ciphertextCid: envelopeUpload.cid, key32: envelopeKey });
|
|
6734
|
-
kind = "compact";
|
|
6735
|
-
retention = envelopeUpload.deleteAfter;
|
|
6736
|
-
}
|
|
6737
|
-
const metadata = {
|
|
6738
|
-
protocol: "tinycloud-share",
|
|
6739
|
-
version: 1,
|
|
6740
|
-
shareId,
|
|
6741
|
-
origin: options.origin,
|
|
6742
|
-
target: { kind: "bearer", origin: options.origin, nodeAudience, spaceId },
|
|
6743
|
-
resource: { kind: "exact", path },
|
|
6744
|
-
actions: ["read"],
|
|
6745
|
-
expiresAt: expiry,
|
|
6746
|
-
display: { filename: options.filename },
|
|
6747
|
-
content: { cid: sealedContent.cid }
|
|
6748
|
-
};
|
|
6749
|
-
const result = { protocol: "tinycloud-share", version: SHARE_PUBLISH_RESULT_VERSION, link: { kind, cid: sealedEnvelope.cid }, metadata, registryDeleteAfter: retention };
|
|
6750
|
-
Object.defineProperty(result, "toJSON", {
|
|
6751
|
-
enumerable: false,
|
|
6752
|
-
value: () => redactPublishedShare(result)
|
|
6753
|
-
});
|
|
6754
|
-
Object.defineProperty(result, "url", { enumerable: false, value: url });
|
|
6755
|
-
return result;
|
|
6756
|
-
} finally {
|
|
6757
|
-
sessionPrivateKey.fill(0);
|
|
6758
|
-
senderPrivateKey.fill(0);
|
|
6759
|
-
contentKey?.fill(0);
|
|
6760
|
-
envelopeKey?.fill(0);
|
|
6761
|
-
}
|
|
6762
|
-
}
|
|
6763
6115
|
|
|
6764
6116
|
// src/addressed.ts
|
|
6765
6117
|
async function parseAddressedEnvelope(value) {
|
|
@@ -7203,8 +6555,8 @@ function targetAuthorizationMethod(target) {
|
|
|
7203
6555
|
}
|
|
7204
6556
|
async function publishTargetShare(input) {
|
|
7205
6557
|
const target = normalizeShareTarget(input.target);
|
|
7206
|
-
if (target.kind === "bearer") return publishShare(input);
|
|
7207
6558
|
if (input.targetAdapter === void 0) {
|
|
6559
|
+
if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
|
|
7208
6560
|
return {
|
|
7209
6561
|
state: "authorization-required",
|
|
7210
6562
|
method: targetAuthorizationMethod(target)
|
|
@@ -7214,9 +6566,9 @@ async function publishTargetShare(input) {
|
|
|
7214
6566
|
const chunks = [];
|
|
7215
6567
|
let size = 0;
|
|
7216
6568
|
for await (const chunk of input.source) {
|
|
7217
|
-
if (!(chunk instanceof Uint8Array)) throw new TypeError("
|
|
6569
|
+
if (!(chunk instanceof Uint8Array)) throw new TypeError("share publication source yielded invalid bytes");
|
|
7218
6570
|
size += chunk.byteLength;
|
|
7219
|
-
if (size > (input.maxBytes ??
|
|
6571
|
+
if (size > (input.maxBytes ?? SHARE_CONTENT_LIMIT)) throw new SharePublishError("max-bytes-exceeded", "share publication exceeds maxBytes");
|
|
7220
6572
|
chunks.push(chunk.slice());
|
|
7221
6573
|
}
|
|
7222
6574
|
const bytes3 = new Uint8Array(size);
|
|
@@ -7227,15 +6579,29 @@ async function publishTargetShare(input) {
|
|
|
7227
6579
|
}
|
|
7228
6580
|
return bytes3;
|
|
7229
6581
|
})();
|
|
6582
|
+
if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
6583
|
+
if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
|
|
6584
|
+
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
6585
|
+
}
|
|
6586
|
+
if (target.kind === "bearer" && input.allowBinary !== true) {
|
|
6587
|
+
try {
|
|
6588
|
+
new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
6589
|
+
} catch {
|
|
6590
|
+
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
7230
6593
|
const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
|
|
7231
6594
|
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
7232
6595
|
let totalBytes = 0;
|
|
7233
6596
|
for (const file of files) {
|
|
7234
6597
|
totalBytes += file.bytes.byteLength;
|
|
7235
6598
|
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
7236
|
-
throw new SharePublishError("max-bytes-exceeded", "
|
|
6599
|
+
throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
|
|
7237
6600
|
}
|
|
7238
6601
|
}
|
|
6602
|
+
const nowMs = input.now?.() ?? Date.now();
|
|
6603
|
+
const expiresAt = input.expiresAt ?? new Date(nowMs + DEFAULT_SHARE_LIFETIME_MS);
|
|
6604
|
+
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
7239
6605
|
return input.targetAdapter.publish({
|
|
7240
6606
|
source,
|
|
7241
6607
|
filename: input.filename,
|
|
@@ -7244,18 +6610,8 @@ async function publishTargetShare(input) {
|
|
|
7244
6610
|
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
7245
6611
|
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
7246
6612
|
target,
|
|
7247
|
-
expiresAt
|
|
6613
|
+
expiresAt,
|
|
7248
6614
|
origin: input.origin,
|
|
7249
|
-
...input.inline === void 0 ? {} : { inline: input.inline },
|
|
7250
|
-
upload: {
|
|
7251
|
-
...input.registryBaseUrl === void 0 ? {} : { registryBaseUrl: input.registryBaseUrl },
|
|
7252
|
-
...input.fetchFn === void 0 ? {} : { fetchFn: input.fetchFn },
|
|
7253
|
-
...input.authorizeUpload === void 0 ? {} : { authorizeUpload: input.authorizeUpload },
|
|
7254
|
-
...input.authorizationOrigin === void 0 ? {} : { authorizationOrigin: input.authorizationOrigin },
|
|
7255
|
-
...input.credentials === void 0 ? {} : { credentials: input.credentials },
|
|
7256
|
-
...input.allowInsecureRegistry === void 0 ? {} : { allowInsecureRegistry: input.allowInsecureRegistry },
|
|
7257
|
-
...input.uploadBlob === void 0 ? {} : { uploadBlob: input.uploadBlob }
|
|
7258
|
-
},
|
|
7259
6615
|
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
7260
6616
|
});
|
|
7261
6617
|
}
|
|
@@ -7347,7 +6703,7 @@ function publicationResult(input) {
|
|
|
7347
6703
|
protocol: "tinycloud-share",
|
|
7348
6704
|
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
7349
6705
|
url: input.url,
|
|
7350
|
-
link: { kind:
|
|
6706
|
+
link: { kind: "policy", cid: input.envelopeCid },
|
|
7351
6707
|
metadata: {
|
|
7352
6708
|
protocol: "tinycloud-share",
|
|
7353
6709
|
version: 1,
|
|
@@ -7366,8 +6722,7 @@ function publicationResult(input) {
|
|
|
7366
6722
|
enforcerDid: input.enforcerDid,
|
|
7367
6723
|
envelopeCid: input.envelopeCid,
|
|
7368
6724
|
shareCid: input.envelopeCid
|
|
7369
|
-
}
|
|
7370
|
-
registryDeleteAfter: input.retention
|
|
6725
|
+
}
|
|
7371
6726
|
};
|
|
7372
6727
|
Object.defineProperty(result, "toJSON", { enumerable: false, value: () => redactPublishedShare(result) });
|
|
7373
6728
|
Object.defineProperty(result, "url", { enumerable: false, value: input.url });
|
|
@@ -7449,40 +6804,100 @@ async function publishAddressedShare(options) {
|
|
|
7449
6804
|
if (envelopeSignature.byteLength !== 64) throw new TypeError("v3 envelope signature must be Ed25519");
|
|
7450
6805
|
const envelope = { ...unsigned, signature: { signerDid: options.authority.ownerDid, algorithm: "Ed25519", value: toBase64Url(envelopeSignature) } };
|
|
7451
6806
|
shareEnvelopeV3Schema.parse(envelope);
|
|
7452
|
-
const
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
6807
|
+
const envelopeBytes = textEncoder.encode(canonicalize(envelope));
|
|
6808
|
+
const envelopeKeyBytes = generateKey();
|
|
6809
|
+
const sealed = await seal(envelopeBytes, envelopeKeyBytes);
|
|
6810
|
+
const envelopeKey = toBase64Url(envelopeKeyBytes);
|
|
6811
|
+
const url = await encodeSealedInlineShareUrl({
|
|
6812
|
+
origin: options.shareOrigin,
|
|
6813
|
+
ciphertext: sealed.blob,
|
|
6814
|
+
key32: envelopeKeyBytes
|
|
6815
|
+
});
|
|
6816
|
+
const deliveryMaterial = {
|
|
6817
|
+
envelope,
|
|
6818
|
+
shareCid: sealed.cid,
|
|
6819
|
+
sealedEnvelope: toBase64Url(sealed.blob),
|
|
6820
|
+
envelopeKey
|
|
6821
|
+
};
|
|
6822
|
+
options.onDeliveryMaterial?.(deliveryMaterial);
|
|
6823
|
+
return publicationResult({
|
|
6824
|
+
options,
|
|
6825
|
+
url,
|
|
6826
|
+
envelopeCid: sealed.cid,
|
|
6827
|
+
matcher,
|
|
6828
|
+
policyCid: created.policyCid,
|
|
6829
|
+
policyRootCid: policyRoot.cid,
|
|
6830
|
+
enforcementRootCid: enforcementRoot.cid,
|
|
6831
|
+
enforcerDid: registration.attestedEnforcerBinding.enforcerDid,
|
|
6832
|
+
expiry,
|
|
6833
|
+
deliveryMaterial
|
|
6834
|
+
});
|
|
7478
6835
|
}
|
|
7479
6836
|
|
|
7480
|
-
// src/
|
|
7481
|
-
var
|
|
6837
|
+
// src/cache.ts
|
|
6838
|
+
var import_sha2563 = require("@noble/hashes/sha256");
|
|
6839
|
+
function cacheKey(key) {
|
|
6840
|
+
if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
|
|
6841
|
+
return `${key.cid}\0${key.expiresAt}`;
|
|
6842
|
+
}
|
|
6843
|
+
function digest(bytes3) {
|
|
6844
|
+
return (0, import_sha2563.sha256)(bytes3);
|
|
6845
|
+
}
|
|
6846
|
+
function equalBytes(left, right) {
|
|
6847
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
6848
|
+
return left.every((byte, index) => byte === right[index]);
|
|
6849
|
+
}
|
|
6850
|
+
var MemoryShareCache = class {
|
|
6851
|
+
constructor(now = () => /* @__PURE__ */ new Date()) {
|
|
6852
|
+
this.now = now;
|
|
6853
|
+
}
|
|
6854
|
+
entries = /* @__PURE__ */ new Map();
|
|
6855
|
+
async get(key, now = /* @__PURE__ */ new Date()) {
|
|
6856
|
+
const mapKey = cacheKey(key);
|
|
6857
|
+
const entry = this.entries.get(mapKey);
|
|
6858
|
+
if (entry === void 0) return void 0;
|
|
6859
|
+
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
|
|
6860
|
+
this.entries.delete(mapKey);
|
|
6861
|
+
return void 0;
|
|
6862
|
+
}
|
|
6863
|
+
return entry.bytes.slice();
|
|
6864
|
+
}
|
|
6865
|
+
async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
|
|
6866
|
+
const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
|
|
6867
|
+
for (const entry of entries) {
|
|
6868
|
+
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest(entry.bytes))) {
|
|
6869
|
+
this.entries.delete(cacheKey(entry.key));
|
|
6870
|
+
continue;
|
|
6871
|
+
}
|
|
6872
|
+
return {
|
|
6873
|
+
key: { ...entry.key },
|
|
6874
|
+
bytes: entry.bytes.slice(),
|
|
6875
|
+
digest: entry.digest.slice(),
|
|
6876
|
+
metadata: { ...entry.metadata }
|
|
6877
|
+
};
|
|
6878
|
+
}
|
|
6879
|
+
return void 0;
|
|
6880
|
+
}
|
|
6881
|
+
async set(key, bytes3, metadata) {
|
|
6882
|
+
if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
|
|
6883
|
+
this.entries.set(cacheKey(key), {
|
|
6884
|
+
key: { cid: key.cid, expiresAt: key.expiresAt },
|
|
6885
|
+
bytes: bytes3.slice(),
|
|
6886
|
+
digest: digest(bytes3),
|
|
6887
|
+
metadata: { ...metadata }
|
|
6888
|
+
});
|
|
6889
|
+
}
|
|
6890
|
+
async delete(key) {
|
|
6891
|
+
this.entries.delete(cacheKey(key));
|
|
6892
|
+
}
|
|
6893
|
+
async clear() {
|
|
6894
|
+
this.entries.clear();
|
|
6895
|
+
}
|
|
6896
|
+
};
|
|
7482
6897
|
|
|
7483
6898
|
// src/owner-policy.ts
|
|
7484
|
-
var
|
|
7485
|
-
var
|
|
6899
|
+
var import_ed255194 = require("@noble/curves/ed25519");
|
|
6900
|
+
var import_sha2564 = require("@noble/hashes/sha256");
|
|
7486
6901
|
|
|
7487
6902
|
// ../../node_modules/multiformats/dist/src/bases/base32.js
|
|
7488
6903
|
var base322 = rfc46482({
|
|
@@ -8036,10 +7451,10 @@ function fromB64(value) {
|
|
|
8036
7451
|
throw new Error("base64url decoding is unavailable");
|
|
8037
7452
|
}
|
|
8038
7453
|
function cid(bytesValue) {
|
|
8039
|
-
return CID2.createV1(code2, create2(SHA256_CODE2, (0,
|
|
7454
|
+
return CID2.createV1(code2, create2(SHA256_CODE2, (0, import_sha2564.sha256)(bytesValue))).toString(base322.encoder);
|
|
8040
7455
|
}
|
|
8041
|
-
function
|
|
8042
|
-
return b64((0,
|
|
7456
|
+
function digest2(bytesValue) {
|
|
7457
|
+
return b64((0, import_sha2564.sha256)(bytesValue));
|
|
8043
7458
|
}
|
|
8044
7459
|
function didKeyFromEd25519PublicKey2(publicKey) {
|
|
8045
7460
|
if (publicKey.length !== 32) throw new Error("share key public key must be 32 bytes");
|
|
@@ -8113,9 +7528,9 @@ function dagCborEncode(value) {
|
|
|
8113
7528
|
}
|
|
8114
7529
|
function assertObject(value, keys, label) {
|
|
8115
7530
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8116
|
-
const
|
|
8117
|
-
if (Object.keys(
|
|
8118
|
-
return
|
|
7531
|
+
const record = value;
|
|
7532
|
+
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`);
|
|
7533
|
+
return record;
|
|
8119
7534
|
}
|
|
8120
7535
|
async function createDelegatedShareKey(options) {
|
|
8121
7536
|
if (!globalThis.crypto?.subtle) throw new Error("WebCrypto Ed25519 is required for delegated share keys");
|
|
@@ -8178,7 +7593,7 @@ async function createPolicyEnforcementDelegation(input) {
|
|
|
8178
7593
|
async function canonicalOwnerSharePolicy(policy) {
|
|
8179
7594
|
const text = canonicalize({ domain: POLICY_DOMAIN, policy });
|
|
8180
7595
|
const policyBytes = new TextEncoder().encode(text);
|
|
8181
|
-
return { bytes: policyBytes, cid: cid(policyBytes), digest:
|
|
7596
|
+
return { bytes: policyBytes, cid: cid(policyBytes), digest: digest2(policyBytes) };
|
|
8182
7597
|
}
|
|
8183
7598
|
function computeOwnerShareRegistrationCid(registration) {
|
|
8184
7599
|
return cid(new TextEncoder().encode(canonicalize(registration)));
|
|
@@ -8217,7 +7632,7 @@ function validateOwnerSharePolicyRegistration(value, expected) {
|
|
|
8217
7632
|
const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
|
|
8218
7633
|
const signatureBytes = fromB64(proof.signature);
|
|
8219
7634
|
const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
|
|
8220
|
-
if (signatureBytes.length !== 64 || !
|
|
7635
|
+
if (signatureBytes.length !== 64 || !import_ed255194.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
|
|
8221
7636
|
return { registration, proof };
|
|
8222
7637
|
}
|
|
8223
7638
|
function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
@@ -8230,318 +7645,6 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
|
8230
7645
|
return validateOwnerSharePolicyRegistration(value, expected);
|
|
8231
7646
|
}
|
|
8232
7647
|
|
|
8233
|
-
// src/policy-authority.ts
|
|
8234
|
-
var ENVELOPE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
8235
|
-
function record(value, keys, label) {
|
|
8236
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8237
|
-
const output = value;
|
|
8238
|
-
if (Object.keys(output).length !== keys.length || keys.some((key) => !Object.hasOwn(output, key))) throw new Error(`${label} has unknown or missing fields`);
|
|
8239
|
-
return output;
|
|
8240
|
-
}
|
|
8241
|
-
function requiredString(value, label) {
|
|
8242
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${label} is invalid`);
|
|
8243
|
-
return value;
|
|
8244
|
-
}
|
|
8245
|
-
function stringArray(value, label) {
|
|
8246
|
-
if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.length > 0)) throw new Error(`${label} is invalid`);
|
|
8247
|
-
return value;
|
|
8248
|
-
}
|
|
8249
|
-
function parseEnforcement(value) {
|
|
8250
|
-
const delegation = record(value, ["cid", "dagCbor", "issuerDid", "audienceDid", "facts", "signature"], "policy enforcement delegation");
|
|
8251
|
-
const facts = record(delegation.facts, ["ownerDelegationCid", "policyCid", "shareId", "shareKeyDid", "enforcerDid", "nodeAudience", "spaceId", "path", "actions", "contentSourceDigest", "expiresAt"], "policy enforcement facts");
|
|
8252
|
-
return {
|
|
8253
|
-
cid: requiredString(delegation.cid, "policy enforcement CID"),
|
|
8254
|
-
dagCbor: requiredString(delegation.dagCbor, "policy enforcement bytes"),
|
|
8255
|
-
issuerDid: requiredString(delegation.issuerDid, "policy enforcement issuer"),
|
|
8256
|
-
audienceDid: requiredString(delegation.audienceDid, "policy enforcement audience"),
|
|
8257
|
-
facts: {
|
|
8258
|
-
ownerDelegationCid: requiredString(facts.ownerDelegationCid, "owner delegation CID"),
|
|
8259
|
-
policyCid: requiredString(facts.policyCid, "policy CID"),
|
|
8260
|
-
shareId: requiredString(facts.shareId, "share id"),
|
|
8261
|
-
shareKeyDid: requiredString(facts.shareKeyDid, "share key DID"),
|
|
8262
|
-
enforcerDid: requiredString(facts.enforcerDid, "enforcer DID"),
|
|
8263
|
-
nodeAudience: requiredString(facts.nodeAudience, "node audience"),
|
|
8264
|
-
spaceId: requiredString(facts.spaceId, "space id"),
|
|
8265
|
-
path: requiredString(facts.path, "resource path"),
|
|
8266
|
-
actions: stringArray(facts.actions, "policy actions"),
|
|
8267
|
-
contentSourceDigest: requiredString(facts.contentSourceDigest, "content source digest"),
|
|
8268
|
-
expiresAt: requiredString(facts.expiresAt, "policy expiry")
|
|
8269
|
-
},
|
|
8270
|
-
signature: requiredString(delegation.signature, "policy enforcement signature")
|
|
8271
|
-
};
|
|
8272
|
-
}
|
|
8273
|
-
async function verifyEnforcement(delegation) {
|
|
8274
|
-
try {
|
|
8275
|
-
const dagCbor = fromBase64Url(delegation.dagCbor);
|
|
8276
|
-
if (await computeCid(dagCbor) !== delegation.cid) return false;
|
|
8277
|
-
const expectedDagCbor = dagCborEncode({
|
|
8278
|
-
domain: POLICY_ENFORCEMENT_DOMAIN,
|
|
8279
|
-
unsigned: {
|
|
8280
|
-
type: "TinyCloudSharePolicyEnforcement",
|
|
8281
|
-
version: 2,
|
|
8282
|
-
issuerDid: delegation.issuerDid,
|
|
8283
|
-
audienceDid: delegation.audienceDid,
|
|
8284
|
-
facts: delegation.facts
|
|
8285
|
-
}
|
|
8286
|
-
});
|
|
8287
|
-
if (expectedDagCbor.length !== dagCbor.length || expectedDagCbor.some((byte, index) => byte !== dagCbor[index])) return false;
|
|
8288
|
-
const signature = fromBase64Url(delegation.signature);
|
|
8289
|
-
return signature.length === 64 && import_ed255196.ed25519.verify(
|
|
8290
|
-
signature,
|
|
8291
|
-
dagCbor,
|
|
8292
|
-
ed25519PublicKeyFromDidKey(delegation.issuerDid),
|
|
8293
|
-
{ zip215: false }
|
|
8294
|
-
);
|
|
8295
|
-
} catch {
|
|
8296
|
-
return false;
|
|
8297
|
-
}
|
|
8298
|
-
}
|
|
8299
|
-
async function verifyOuterEnvelope(value, envelope, policyCid, expectedEnforcerDid, expectedActions) {
|
|
8300
|
-
try {
|
|
8301
|
-
const hasDecryption = typeof value === "object" && value !== null && Object.hasOwn(value, "decryption");
|
|
8302
|
-
const outer = record(value, ["schema", "version", "shareId", "delegationCid", "policyCid", "target", "resource", "actions", ...hasDecryption ? ["decryption"] : [], "contentSource", "contentSourceDigest", "expiresAt", "envelopeCid", "shareCid", "signature"], "outer envelope");
|
|
8303
|
-
const target = record(outer.target, ["origin", "nodeAudience", "enforcerDid", "spaceId"], "outer target");
|
|
8304
|
-
const signature = record(outer.signature, ["signerDid", "algorithm", "value"], "outer envelope signature");
|
|
8305
|
-
const envelopeCid = requiredString(outer.envelopeCid, "outer envelope CID");
|
|
8306
|
-
const shareCid = requiredString(outer.shareCid, "outer share CID");
|
|
8307
|
-
const { signature: _signature, envelopeCid: _envelopeCid, shareCid: _shareCid, ...identity } = outer;
|
|
8308
|
-
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;
|
|
8309
|
-
if (envelopeCid !== await computeCid(new TextEncoder().encode(canonicalize(identity)))) return void 0;
|
|
8310
|
-
if (shareCid !== await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: envelope.shareId, policyCid, envelopeCid })))) return void 0;
|
|
8311
|
-
const unsigned = { ...identity, envelopeCid, shareCid };
|
|
8312
|
-
const signatureBytes = fromBase64Url(requiredString(signature.value, "outer signature"));
|
|
8313
|
-
if (signatureBytes.length !== 64 || !import_ed255196.ed25519.verify(
|
|
8314
|
-
signatureBytes,
|
|
8315
|
-
new TextEncoder().encode(`${ENVELOPE_DOMAIN}${canonicalize(unsigned)}`),
|
|
8316
|
-
ed25519PublicKeyFromDidKey(envelope.signature.signerDid),
|
|
8317
|
-
{ zip215: false }
|
|
8318
|
-
)) return void 0;
|
|
8319
|
-
return { envelopeCid, shareCid };
|
|
8320
|
-
} catch {
|
|
8321
|
-
return void 0;
|
|
8322
|
-
}
|
|
8323
|
-
}
|
|
8324
|
-
function createRegisteredPolicyAuthority(options) {
|
|
8325
|
-
const proofKey = options.nodeProof.publicKey.slice();
|
|
8326
|
-
return {
|
|
8327
|
-
async resolve({ policyCid, envelope }) {
|
|
8328
|
-
try {
|
|
8329
|
-
if (envelope.authorizationTarget.kind !== "policy" || envelope.authorizationTarget.policyCid !== policyCid) return void 0;
|
|
8330
|
-
const ownerAuthority = envelope.ownerAuthority;
|
|
8331
|
-
if (ownerAuthority?.registrationReceipt === void 0) return void 0;
|
|
8332
|
-
const enforcement = parseEnforcement(ownerAuthority.enforcementDelegation);
|
|
8333
|
-
if (!await verifyEnforcement(enforcement)) return void 0;
|
|
8334
|
-
const policyBytes = fromBase64Url(envelope.authorizationTarget.policyBytes);
|
|
8335
|
-
const ownerDelegation = {
|
|
8336
|
-
delegationCid: envelope.delegationCid,
|
|
8337
|
-
signedDagCbor: new Uint8Array(),
|
|
8338
|
-
delegation: {
|
|
8339
|
-
delegateDID: envelope.signature.signerDid,
|
|
8340
|
-
spaceId: envelope.target.spaceId,
|
|
8341
|
-
path: envelope.resource.path,
|
|
8342
|
-
actions: envelope.actions,
|
|
8343
|
-
expiry: new Date(envelope.expiry)
|
|
8344
|
-
}
|
|
8345
|
-
};
|
|
8346
|
-
const receipt = validateOwnerSharePolicyRegistration(ownerAuthority.registrationReceipt, {
|
|
8347
|
-
policy: { bytes: policyBytes, cid: policyCid, proof: "" },
|
|
8348
|
-
ownerDelegation,
|
|
8349
|
-
enforcementDelegation: enforcement,
|
|
8350
|
-
contentSourceDigest: envelope.contentSourceDigest,
|
|
8351
|
-
nodeProof: { kid: options.nodeProof.kid, publicKey: proofKey }
|
|
8352
|
-
});
|
|
8353
|
-
const registration = receipt.registration;
|
|
8354
|
-
const outer = await verifyOuterEnvelope(ownerAuthority.outerEnvelope, envelope, policyCid, options.expectedTarget.enforcerDid, registration.actions);
|
|
8355
|
-
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;
|
|
8356
|
-
return {
|
|
8357
|
-
policyCid: registration.policyCid,
|
|
8358
|
-
signerDid: registration.shareKeyDid,
|
|
8359
|
-
registrationCid: registration.registrationCid,
|
|
8360
|
-
shareId: registration.shareId,
|
|
8361
|
-
recipientMatcher: registration.recipientMatcher,
|
|
8362
|
-
target: registration.target,
|
|
8363
|
-
resource: registration.resource,
|
|
8364
|
-
actions: registration.actions,
|
|
8365
|
-
contentSource: registration.contentSource,
|
|
8366
|
-
contentSourceDigest: registration.contentSourceDigest,
|
|
8367
|
-
delegationCid: registration.ownerDelegationCid,
|
|
8368
|
-
authorityMaterialHandle: registration.registrationCid,
|
|
8369
|
-
authorityMaterialDigest: envelope.authorityMaterialDigest,
|
|
8370
|
-
expiresAt: registration.expiresAt
|
|
8371
|
-
};
|
|
8372
|
-
} catch {
|
|
8373
|
-
return void 0;
|
|
8374
|
-
}
|
|
8375
|
-
}
|
|
8376
|
-
};
|
|
8377
|
-
}
|
|
8378
|
-
|
|
8379
|
-
// src/cache.ts
|
|
8380
|
-
var import_sha2564 = require("@noble/hashes/sha256");
|
|
8381
|
-
function cacheKey(key) {
|
|
8382
|
-
if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
|
|
8383
|
-
return `${key.cid}\0${key.expiresAt}`;
|
|
8384
|
-
}
|
|
8385
|
-
function digest2(bytes3) {
|
|
8386
|
-
return (0, import_sha2564.sha256)(bytes3);
|
|
8387
|
-
}
|
|
8388
|
-
function equalBytes(left, right) {
|
|
8389
|
-
if (left.byteLength !== right.byteLength) return false;
|
|
8390
|
-
return left.every((byte, index) => byte === right[index]);
|
|
8391
|
-
}
|
|
8392
|
-
var MemoryShareCache = class {
|
|
8393
|
-
constructor(now = () => /* @__PURE__ */ new Date()) {
|
|
8394
|
-
this.now = now;
|
|
8395
|
-
}
|
|
8396
|
-
entries = /* @__PURE__ */ new Map();
|
|
8397
|
-
async get(key, now = /* @__PURE__ */ new Date()) {
|
|
8398
|
-
const mapKey = cacheKey(key);
|
|
8399
|
-
const entry = this.entries.get(mapKey);
|
|
8400
|
-
if (entry === void 0) return void 0;
|
|
8401
|
-
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
|
|
8402
|
-
this.entries.delete(mapKey);
|
|
8403
|
-
return void 0;
|
|
8404
|
-
}
|
|
8405
|
-
return entry.bytes.slice();
|
|
8406
|
-
}
|
|
8407
|
-
async getByCid(cid2, now = /* @__PURE__ */ new Date()) {
|
|
8408
|
-
const entries = [...this.entries.values()].filter((entry) => entry.key.cid === cid2);
|
|
8409
|
-
for (const entry of entries) {
|
|
8410
|
-
if (Date.parse(entry.key.expiresAt) <= now.getTime() || !equalBytes(entry.digest, digest2(entry.bytes))) {
|
|
8411
|
-
this.entries.delete(cacheKey(entry.key));
|
|
8412
|
-
continue;
|
|
8413
|
-
}
|
|
8414
|
-
return {
|
|
8415
|
-
key: { ...entry.key },
|
|
8416
|
-
bytes: entry.bytes.slice(),
|
|
8417
|
-
digest: entry.digest.slice(),
|
|
8418
|
-
metadata: { ...entry.metadata }
|
|
8419
|
-
};
|
|
8420
|
-
}
|
|
8421
|
-
return void 0;
|
|
8422
|
-
}
|
|
8423
|
-
async set(key, bytes3, metadata) {
|
|
8424
|
-
if (Date.parse(key.expiresAt) <= this.now().getTime()) return;
|
|
8425
|
-
this.entries.set(cacheKey(key), {
|
|
8426
|
-
key: { cid: key.cid, expiresAt: key.expiresAt },
|
|
8427
|
-
bytes: bytes3.slice(),
|
|
8428
|
-
digest: digest2(bytes3),
|
|
8429
|
-
metadata: { ...metadata }
|
|
8430
|
-
});
|
|
8431
|
-
}
|
|
8432
|
-
async delete(key) {
|
|
8433
|
-
this.entries.delete(cacheKey(key));
|
|
8434
|
-
}
|
|
8435
|
-
async clear() {
|
|
8436
|
-
this.entries.clear();
|
|
8437
|
-
}
|
|
8438
|
-
};
|
|
8439
|
-
|
|
8440
|
-
// src/delivery.ts
|
|
8441
|
-
var import_ed255197 = require("@noble/curves/ed25519");
|
|
8442
|
-
var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
|
|
8443
|
-
function isCanonicalHttpsOrigin2(value) {
|
|
8444
|
-
let url;
|
|
8445
|
-
try {
|
|
8446
|
-
url = new URL(value);
|
|
8447
|
-
} catch {
|
|
8448
|
-
return false;
|
|
8449
|
-
}
|
|
8450
|
-
return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
8451
|
-
}
|
|
8452
|
-
var AUTHORIZATION_KEYS = [
|
|
8453
|
-
"type",
|
|
8454
|
-
"version",
|
|
8455
|
-
"jti",
|
|
8456
|
-
"shareCid",
|
|
8457
|
-
"shareId",
|
|
8458
|
-
"registrationCid",
|
|
8459
|
-
"delegationCid",
|
|
8460
|
-
"enforcementDelegationCid",
|
|
8461
|
-
"envelopeCid",
|
|
8462
|
-
"policyCid",
|
|
8463
|
-
"nodeAudience",
|
|
8464
|
-
"targetOrigin",
|
|
8465
|
-
"openCredentialsAudience",
|
|
8466
|
-
"holder",
|
|
8467
|
-
"recipientMatcher",
|
|
8468
|
-
"deliveryEmail",
|
|
8469
|
-
"shareUrl",
|
|
8470
|
-
"returnOrigin",
|
|
8471
|
-
"documentName",
|
|
8472
|
-
"senderDid",
|
|
8473
|
-
"senderTrust",
|
|
8474
|
-
"contentSource",
|
|
8475
|
-
"contentSourceDigest",
|
|
8476
|
-
"shareExpiresAt",
|
|
8477
|
-
"issuedAt",
|
|
8478
|
-
"reportAbuseToken",
|
|
8479
|
-
"actions",
|
|
8480
|
-
"resource",
|
|
8481
|
-
"authorityMaterialHandle",
|
|
8482
|
-
"authorityMaterialDigest",
|
|
8483
|
-
"requestBodyDigest",
|
|
8484
|
-
"idempotencyKey",
|
|
8485
|
-
"expiresAt",
|
|
8486
|
-
"dataAuthority"
|
|
8487
|
-
];
|
|
8488
|
-
function assertObject2(value, keys, label) {
|
|
8489
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8490
|
-
const record2 = value;
|
|
8491
|
-
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`);
|
|
8492
|
-
return record2;
|
|
8493
|
-
}
|
|
8494
|
-
function fromB642(value) {
|
|
8495
|
-
if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
|
|
8496
|
-
if (typeof atob === "function") {
|
|
8497
|
-
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
8498
|
-
return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
8499
|
-
}
|
|
8500
|
-
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
|
|
8501
|
-
throw new Error("base64url decoding is unavailable");
|
|
8502
|
-
}
|
|
8503
|
-
function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
|
|
8504
|
-
let value;
|
|
8505
|
-
try {
|
|
8506
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
|
|
8507
|
-
} catch {
|
|
8508
|
-
throw new Error("share delivery authorization response is not valid UTF-8 JSON");
|
|
8509
|
-
}
|
|
8510
|
-
const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
|
|
8511
|
-
const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
|
|
8512
|
-
const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
|
|
8513
|
-
const request = expected.request;
|
|
8514
|
-
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) {
|
|
8515
|
-
throw new Error("share delivery authorization is not bound to the submitted request");
|
|
8516
|
-
}
|
|
8517
|
-
const now = Date.now();
|
|
8518
|
-
const issuedAt = typeof authorization.issuedAt === "string" ? Date.parse(authorization.issuedAt) : Number.NaN;
|
|
8519
|
-
const expiresAt = typeof authorization.expiresAt === "string" ? Date.parse(authorization.expiresAt) : Number.NaN;
|
|
8520
|
-
if (!Number.isFinite(issuedAt) || issuedAt > now + 6e4 || issuedAt > expiresAt) throw new Error("share delivery authorization issuedAt is invalid");
|
|
8521
|
-
if (!Number.isFinite(expiresAt) || expiresAt <= now) throw new Error("share delivery authorization is expired");
|
|
8522
|
-
if (new Date(expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
|
|
8523
|
-
if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin2(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
|
|
8524
|
-
throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
|
|
8525
|
-
}
|
|
8526
|
-
if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
|
|
8527
|
-
if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
|
|
8528
|
-
const encodedKid = expected.nodeProof.publicKey;
|
|
8529
|
-
const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
|
|
8530
|
-
if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
|
|
8531
|
-
const signatureBytes = fromB642(proof.signature);
|
|
8532
|
-
const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalize(authorization)}`);
|
|
8533
|
-
if (signatureBytes.length !== 64 || !import_ed255197.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
|
|
8534
|
-
return { authorization, proof };
|
|
8535
|
-
}
|
|
8536
|
-
function shareDeliveryTrustedKid(publicKey) {
|
|
8537
|
-
if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
|
|
8538
|
-
const prefixed = new Uint8Array(34);
|
|
8539
|
-
prefixed.set([237, 1]);
|
|
8540
|
-
prefixed.set(publicKey, 2);
|
|
8541
|
-
const did = `did:key:${base58btc2.encode(prefixed)}`;
|
|
8542
|
-
return `${did}#${did.slice("did:key:".length)}`;
|
|
8543
|
-
}
|
|
8544
|
-
|
|
8545
7648
|
// src/history.ts
|
|
8546
7649
|
var MemoryEncryptedShareHistoryStorage = class {
|
|
8547
7650
|
values = [];
|
|
@@ -8591,8 +7694,8 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
|
|
|
8591
7694
|
}
|
|
8592
7695
|
var MemorySenderShareRecordStorage = class {
|
|
8593
7696
|
records = /* @__PURE__ */ new Map();
|
|
8594
|
-
async put(
|
|
8595
|
-
this.records.set(
|
|
7697
|
+
async put(record) {
|
|
7698
|
+
this.records.set(record.shareId, record);
|
|
8596
7699
|
}
|
|
8597
7700
|
async list() {
|
|
8598
7701
|
return [...this.records.values()];
|
|
@@ -8629,7 +7732,7 @@ var SenderShareStore = class {
|
|
|
8629
7732
|
async save(receipt, key) {
|
|
8630
7733
|
const registration = receipt.registration;
|
|
8631
7734
|
if (registration.shareKeyDid !== key.did) throw new Error("share key does not match the registered receipt");
|
|
8632
|
-
const
|
|
7735
|
+
const record = {
|
|
8633
7736
|
shareId: registration.shareId,
|
|
8634
7737
|
registrationCid: registration.registrationCid,
|
|
8635
7738
|
policyCid: registration.policyCid,
|
|
@@ -8646,15 +7749,15 @@ var SenderShareStore = class {
|
|
|
8646
7749
|
registeredAt: registration.registeredAt,
|
|
8647
7750
|
expiresAt: registration.expiresAt
|
|
8648
7751
|
};
|
|
8649
|
-
await this.records.put(
|
|
8650
|
-
await this.keys.put(
|
|
8651
|
-
return
|
|
7752
|
+
await this.records.put(record);
|
|
7753
|
+
await this.keys.put(record.shareId, { cryptoKey: key.cryptoKey, publicKey: key.publicKey, extractable: key.extractable });
|
|
7754
|
+
return record;
|
|
8652
7755
|
}
|
|
8653
7756
|
/** List shares created in any session, most recent first. */
|
|
8654
7757
|
async list(options) {
|
|
8655
7758
|
const now = this.now().getTime();
|
|
8656
7759
|
const records = await this.records.list();
|
|
8657
|
-
return records.filter((
|
|
7760
|
+
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));
|
|
8658
7761
|
}
|
|
8659
7762
|
async get(shareId) {
|
|
8660
7763
|
return this.records.get(shareId);
|
|
@@ -8677,17 +7780,17 @@ var SenderShareStore = class {
|
|
|
8677
7780
|
* returned CID, then call {@link markRevoked} once that succeeds.
|
|
8678
7781
|
*/
|
|
8679
7782
|
async revocationTarget(shareId, scope) {
|
|
8680
|
-
const
|
|
8681
|
-
if (
|
|
8682
|
-
const delegationCid = scope === "direct" ?
|
|
7783
|
+
const record = await this.records.get(shareId);
|
|
7784
|
+
if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7785
|
+
const delegationCid = scope === "direct" ? record.enforcementDelegationCid : record.ownerDelegationCid;
|
|
8683
7786
|
if (delegationCid === void 0) throw new Error("share has no node-enforced delegation");
|
|
8684
7787
|
return delegationCid;
|
|
8685
7788
|
}
|
|
8686
7789
|
/** Mark a share revoked locally after the caller's node-side revoke call succeeds. */
|
|
8687
7790
|
async markRevoked(shareId, revokedAt = this.now().toISOString()) {
|
|
8688
|
-
const
|
|
8689
|
-
if (
|
|
8690
|
-
const revoked = { ...
|
|
7791
|
+
const record = await this.records.get(shareId);
|
|
7792
|
+
if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7793
|
+
const revoked = { ...record, revokedAt };
|
|
8691
7794
|
await this.records.put(revoked);
|
|
8692
7795
|
return revoked;
|
|
8693
7796
|
}
|
|
@@ -8703,9 +7806,9 @@ var EncryptedSenderShareHistory = class {
|
|
|
8703
7806
|
this.key = key;
|
|
8704
7807
|
this.now = now;
|
|
8705
7808
|
}
|
|
8706
|
-
async put(
|
|
7809
|
+
async put(record) {
|
|
8707
7810
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
8708
|
-
const payload = new TextEncoder().encode(JSON.stringify(
|
|
7811
|
+
const payload = new TextEncoder().encode(JSON.stringify(record));
|
|
8709
7812
|
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, this.key, payload));
|
|
8710
7813
|
const encoded = new Uint8Array(iv.length + ciphertext.length);
|
|
8711
7814
|
encoded.set(iv);
|
|
@@ -8738,8 +7841,8 @@ var ShareNotifyError = class extends Error {
|
|
|
8738
7841
|
this.name = "ShareNotifyError";
|
|
8739
7842
|
}
|
|
8740
7843
|
};
|
|
8741
|
-
function recipientMatchesShareRecord(
|
|
8742
|
-
const matcher =
|
|
7844
|
+
function recipientMatchesShareRecord(record, recipient) {
|
|
7845
|
+
const matcher = record.recipientMatcher;
|
|
8743
7846
|
if (matcher.kind === "bearer" || matcher.kind === "recipientDid") return false;
|
|
8744
7847
|
if (matcher.kind === "exactEmail") return matcher.value === recipient;
|
|
8745
7848
|
const at = recipient.lastIndexOf("@");
|
|
@@ -8777,60 +7880,180 @@ async function defaultIdempotencyKey(shareId, recipient) {
|
|
|
8777
7880
|
return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
|
|
8778
7881
|
}
|
|
8779
7882
|
|
|
7883
|
+
// src/credential-invitation.ts
|
|
7884
|
+
var CredentialInvitationError = class extends Error {
|
|
7885
|
+
constructor(code3, message) {
|
|
7886
|
+
super(message);
|
|
7887
|
+
this.code = code3;
|
|
7888
|
+
this.name = "CredentialInvitationError";
|
|
7889
|
+
}
|
|
7890
|
+
};
|
|
7891
|
+
function canonicalOrigin(value) {
|
|
7892
|
+
let url;
|
|
7893
|
+
try {
|
|
7894
|
+
url = new URL(value);
|
|
7895
|
+
} catch {
|
|
7896
|
+
throw new CredentialInvitationError(
|
|
7897
|
+
"invalid-origin",
|
|
7898
|
+
"credential invitation origin is invalid"
|
|
7899
|
+
);
|
|
7900
|
+
}
|
|
7901
|
+
if (url.origin !== value || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || url.protocol !== "https:")
|
|
7902
|
+
throw new CredentialInvitationError(
|
|
7903
|
+
"invalid-origin",
|
|
7904
|
+
"credential invitation origin is invalid"
|
|
7905
|
+
);
|
|
7906
|
+
return url.origin;
|
|
7907
|
+
}
|
|
7908
|
+
function object2(value) {
|
|
7909
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7910
|
+
}
|
|
7911
|
+
async function validateReceipt(receipt, shareUrl) {
|
|
7912
|
+
if (!object2(receipt) || !object2(receipt.request) || !object2(receipt.admission) || !object2(receipt.proof)) {
|
|
7913
|
+
throw new CredentialInvitationError(
|
|
7914
|
+
"invalid-receipt",
|
|
7915
|
+
"credential invitation receipt is invalid"
|
|
7916
|
+
);
|
|
7917
|
+
}
|
|
7918
|
+
if (receipt.request.returnLink !== shareUrl) {
|
|
7919
|
+
throw new CredentialInvitationError(
|
|
7920
|
+
"invalid-receipt",
|
|
7921
|
+
"credential invitation is not bound to the share link"
|
|
7922
|
+
);
|
|
7923
|
+
}
|
|
7924
|
+
let link;
|
|
7925
|
+
try {
|
|
7926
|
+
link = new URL(shareUrl);
|
|
7927
|
+
} catch {
|
|
7928
|
+
throw new CredentialInvitationError(
|
|
7929
|
+
"invalid-receipt",
|
|
7930
|
+
"credential invitation share link is invalid"
|
|
7931
|
+
);
|
|
7932
|
+
}
|
|
7933
|
+
if (link.search !== "" || link.pathname !== "/s/inline") {
|
|
7934
|
+
throw new CredentialInvitationError(
|
|
7935
|
+
"invalid-receipt",
|
|
7936
|
+
"credential invitation share link must be sealed inline"
|
|
7937
|
+
);
|
|
7938
|
+
}
|
|
7939
|
+
try {
|
|
7940
|
+
await parseSealedInlineShareUrl(shareUrl);
|
|
7941
|
+
} catch {
|
|
7942
|
+
throw new CredentialInvitationError(
|
|
7943
|
+
"invalid-receipt",
|
|
7944
|
+
"credential invitation share link is invalid"
|
|
7945
|
+
);
|
|
7946
|
+
}
|
|
7947
|
+
}
|
|
7948
|
+
async function deliverCredentialInvitation(input) {
|
|
7949
|
+
const credentialsOrigin = canonicalOrigin(input.credentialsOrigin);
|
|
7950
|
+
await validateReceipt(input.receipt, input.shareUrl);
|
|
7951
|
+
const fetchFn = input.fetchFn ?? globalThis.fetch;
|
|
7952
|
+
let response;
|
|
7953
|
+
try {
|
|
7954
|
+
response = await fetchFn(`${credentialsOrigin}/v1/credential-invitations`, {
|
|
7955
|
+
method: "POST",
|
|
7956
|
+
credentials: "omit",
|
|
7957
|
+
redirect: "error",
|
|
7958
|
+
referrerPolicy: "no-referrer",
|
|
7959
|
+
headers: {
|
|
7960
|
+
accept: "application/json",
|
|
7961
|
+
"content-type": "application/json"
|
|
7962
|
+
},
|
|
7963
|
+
body: JSON.stringify(input.receipt),
|
|
7964
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
7965
|
+
});
|
|
7966
|
+
} catch {
|
|
7967
|
+
throw new CredentialInvitationError(
|
|
7968
|
+
"transport",
|
|
7969
|
+
"credential invitation delivery is unavailable"
|
|
7970
|
+
);
|
|
7971
|
+
}
|
|
7972
|
+
if (response.status !== 202) {
|
|
7973
|
+
throw new CredentialInvitationError(
|
|
7974
|
+
"rejected",
|
|
7975
|
+
"credential invitation delivery was not accepted"
|
|
7976
|
+
);
|
|
7977
|
+
}
|
|
7978
|
+
let body;
|
|
7979
|
+
try {
|
|
7980
|
+
body = await response.json();
|
|
7981
|
+
} catch {
|
|
7982
|
+
throw new CredentialInvitationError(
|
|
7983
|
+
"invalid-response",
|
|
7984
|
+
"credential invitation response is invalid"
|
|
7985
|
+
);
|
|
7986
|
+
}
|
|
7987
|
+
if (!object2(body) || Object.keys(body).length !== 1 || body.status !== "accepted") {
|
|
7988
|
+
throw new CredentialInvitationError(
|
|
7989
|
+
"invalid-response",
|
|
7990
|
+
"credential invitation response is invalid"
|
|
7991
|
+
);
|
|
7992
|
+
}
|
|
7993
|
+
return { status: "accepted" };
|
|
7994
|
+
}
|
|
7995
|
+
|
|
8780
7996
|
// src/lifecycle.ts
|
|
8781
|
-
function targetKind2(
|
|
8782
|
-
if (
|
|
8783
|
-
return
|
|
7997
|
+
function targetKind2(record) {
|
|
7998
|
+
if (record.targetKind !== void 0) return record.targetKind;
|
|
7999
|
+
return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
|
|
8000
|
+
}
|
|
8001
|
+
function policyNodeAudience(record) {
|
|
8002
|
+
const envelope = record.deliveryMaterial?.envelope;
|
|
8003
|
+
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
|
|
8004
|
+
const binding = envelope.attestedEnforcerBinding;
|
|
8005
|
+
if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
|
|
8006
|
+
const nodeAudience = binding.nodeAudience;
|
|
8007
|
+
return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
|
|
8784
8008
|
}
|
|
8785
8009
|
async function revokeShare(input) {
|
|
8786
8010
|
const target = targetKind2(input.record);
|
|
8787
|
-
if (target === "bearer") return { state: "retention-only", target, reason: "bearer-capability-cannot-be-revoked" };
|
|
8788
8011
|
if (input.adapter === void 0) return { state: "unsupported", target, reason: "node revocation authority is required", code: "unsupported-target" };
|
|
8789
8012
|
const scope = input.scope ?? "direct";
|
|
8790
8013
|
const delegationCid = scope === "ancestor" ? input.record.ownerDelegationCid : input.record.enforcementDelegationCid;
|
|
8791
8014
|
if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
|
|
8792
|
-
|
|
8015
|
+
if (target === "bearer") {
|
|
8016
|
+
if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
|
|
8017
|
+
await input.adapter.revokeDelegation({ delegationCid, scope });
|
|
8018
|
+
} else {
|
|
8019
|
+
if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
|
|
8020
|
+
if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
|
|
8021
|
+
await input.adapter.revokePolicyRoot({
|
|
8022
|
+
rootCid: delegationCid,
|
|
8023
|
+
targetRole: scope === "ancestor" ? "policy-authority" : "policy-enforcement",
|
|
8024
|
+
ownerDid: input.record.ownerDid,
|
|
8025
|
+
nodeOrigin: input.record.target.origin,
|
|
8026
|
+
nodeAudience: policyNodeAudience(input.record)
|
|
8027
|
+
});
|
|
8028
|
+
}
|
|
8793
8029
|
const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
8794
8030
|
if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
|
|
8795
8031
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
8796
8032
|
}
|
|
8797
|
-
function redactRecord(
|
|
8798
|
-
const matcher =
|
|
8799
|
-
const revealedLink = revealLink ? link ??
|
|
8033
|
+
function redactRecord(record, revealLink, link) {
|
|
8034
|
+
const matcher = record.recipientMatcher;
|
|
8035
|
+
const revealedLink = revealLink ? link ?? record.link : void 0;
|
|
8800
8036
|
return {
|
|
8801
|
-
shareId:
|
|
8037
|
+
shareId: record.shareId,
|
|
8802
8038
|
target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
|
|
8803
8039
|
...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
|
|
8804
|
-
expiresAt:
|
|
8805
|
-
revoked:
|
|
8040
|
+
expiresAt: record.expiresAt,
|
|
8041
|
+
revoked: record.revokedAt !== void 0,
|
|
8806
8042
|
...revealedLink === void 0 ? {} : { link: revealedLink }
|
|
8807
8043
|
};
|
|
8808
8044
|
}
|
|
8809
8045
|
async function listShares(storage) {
|
|
8810
8046
|
const records = await storage.list();
|
|
8811
|
-
return records.map((
|
|
8047
|
+
return records.map((record) => redactRecord(record, false));
|
|
8812
8048
|
}
|
|
8813
8049
|
async function showShare(input) {
|
|
8814
|
-
const
|
|
8815
|
-
if (
|
|
8816
|
-
return redactRecord(
|
|
8817
|
-
}
|
|
8818
|
-
|
|
8819
|
-
// src/legacy.ts
|
|
8820
|
-
function isLegacyShareLink(value) {
|
|
8821
|
-
return value.startsWith("tc1:");
|
|
8822
|
-
}
|
|
8823
|
-
async function receiveLegacyShare(link, reader) {
|
|
8824
|
-
if (!isLegacyShareLink(link)) throw new Error("legacy link must use tc1:");
|
|
8825
|
-
return reader.read(link);
|
|
8826
|
-
}
|
|
8827
|
-
async function migrateShare(input) {
|
|
8828
|
-
const value = await receiveLegacyShare(input.link, input.reader);
|
|
8829
|
-
return { protocol: "tinycloud-share", version: 1, legacy: true, value, migrated: await input.publish(value) };
|
|
8050
|
+
const record = await input.storage.get(input.shareId);
|
|
8051
|
+
if (record === void 0) throw new Error("share not found");
|
|
8052
|
+
return redactRecord(record, input.revealLink === true, input.link);
|
|
8830
8053
|
}
|
|
8831
8054
|
|
|
8832
8055
|
// src/recipient.ts
|
|
8833
|
-
var
|
|
8056
|
+
var import_ed255195 = require("@noble/curves/ed25519");
|
|
8834
8057
|
var import_sha2565 = require("@noble/hashes/sha256");
|
|
8835
8058
|
|
|
8836
8059
|
// src/protocol.ts
|
|
@@ -8876,7 +8099,7 @@ var DOMAIN = SHARE_V2_PROTOCOL.challengeDomain;
|
|
|
8876
8099
|
var PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
8877
8100
|
var SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
8878
8101
|
var INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
|
|
8879
|
-
function
|
|
8102
|
+
function object3(value, label) {
|
|
8880
8103
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8881
8104
|
return value;
|
|
8882
8105
|
}
|
|
@@ -8935,11 +8158,11 @@ function nativeAction(action) {
|
|
|
8935
8158
|
return action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : "tinycloud.kv/get";
|
|
8936
8159
|
}
|
|
8937
8160
|
function policyAttenuationForV3(envelope) {
|
|
8938
|
-
const policy =
|
|
8161
|
+
const policy = object3(envelope.policy, "v3 policy");
|
|
8939
8162
|
if (!Array.isArray(policy.capabilityCeiling)) throw new Error("v3 policy capability ceiling is invalid");
|
|
8940
8163
|
const attenuation = {};
|
|
8941
8164
|
for (const raw of policy.capabilityCeiling) {
|
|
8942
|
-
const capability =
|
|
8165
|
+
const capability = object3(raw, "v3 policy capability");
|
|
8943
8166
|
if (typeof capability.resource !== "string" || attenuation[capability.resource] !== void 0) throw new Error("v3 policy capability is invalid");
|
|
8944
8167
|
if (capability.kind === "encryption") {
|
|
8945
8168
|
if (capability.action !== "tinycloud.encryption/decrypt") throw new Error("v3 policy capability is invalid");
|
|
@@ -8997,8 +8220,8 @@ function verifyV3PolicyAuthorization(input) {
|
|
|
8997
8220
|
const factKeys = Object.keys(fact);
|
|
8998
8221
|
const legacyShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
|
|
8999
8222
|
const v4Shape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
|
|
9000
|
-
const binding =
|
|
9001
|
-
const policy =
|
|
8223
|
+
const binding = object3(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
8224
|
+
const policy = object3(input.envelope.policy, "v3 policy");
|
|
9002
8225
|
const now = Math.floor(Date.now() / 1e3);
|
|
9003
8226
|
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");
|
|
9004
8227
|
return compact;
|
|
@@ -9006,6 +8229,15 @@ function verifyV3PolicyAuthorization(input) {
|
|
|
9006
8229
|
function hex3(value) {
|
|
9007
8230
|
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
9008
8231
|
}
|
|
8232
|
+
function matchesInitialCiphertextDigest(bytes3, expected) {
|
|
8233
|
+
if (typeof expected !== "string" || !LOWER_SHA256_HEX.test(expected)) return false;
|
|
8234
|
+
const actual = (0, import_sha2565.sha256)(bytes3);
|
|
8235
|
+
let mismatch = 0;
|
|
8236
|
+
for (let index = 0; index < actual.length; index += 1) {
|
|
8237
|
+
mismatch |= actual[index] ^ Number.parseInt(expected.slice(index * 2, index * 2 + 2), 16);
|
|
8238
|
+
}
|
|
8239
|
+
return mismatch === 0;
|
|
8240
|
+
}
|
|
9009
8241
|
function canonicalHashHex(value) {
|
|
9010
8242
|
return hex3((0, import_sha2565.sha256)(new TextEncoder().encode(canonicalize(value))));
|
|
9011
8243
|
}
|
|
@@ -9032,12 +8264,12 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
|
|
|
9032
8264
|
throw new Error("encrypted content envelope is malformed");
|
|
9033
8265
|
}
|
|
9034
8266
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
|
|
9035
|
-
const
|
|
8267
|
+
const record = value;
|
|
9036
8268
|
const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
|
|
9037
|
-
if (Object.keys(
|
|
8269
|
+
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"))) {
|
|
9038
8270
|
throw new Error("encrypted content envelope binding is invalid");
|
|
9039
8271
|
}
|
|
9040
|
-
return
|
|
8272
|
+
return record;
|
|
9041
8273
|
}
|
|
9042
8274
|
var ShareRecipientClient = class {
|
|
9043
8275
|
constructor(options) {
|
|
@@ -9080,7 +8312,7 @@ var ShareRecipientClient = class {
|
|
|
9080
8312
|
if (this.session === void 0) await this.establish(envelope);
|
|
9081
8313
|
const response = await this.nativeInvoke({ action: "get", resource: envelope.resource });
|
|
9082
8314
|
if (!response.ok) throw new Error("share recipient read was rejected");
|
|
9083
|
-
const value =
|
|
8315
|
+
const value = object3(await response.json(), "share read response");
|
|
9084
8316
|
const expectedAction = nativeAction("read");
|
|
9085
8317
|
if (value.type !== "TinyCloudShareInvokeResponse" || value.version !== 2 || value.action !== expectedAction || value.resource !== envelope.resource.path || typeof value.content !== "string" || typeof value.bodyDigest !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.bodyDigest) || !Object.hasOwn(value, "proof")) throw new Error("share read response is invalid");
|
|
9086
8318
|
const content = fromBase64Url(value.content);
|
|
@@ -9095,7 +8327,7 @@ var ShareRecipientClient = class {
|
|
|
9095
8327
|
}
|
|
9096
8328
|
async establishV3(envelope) {
|
|
9097
8329
|
if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
|
|
9098
|
-
const attestedEnforcer =
|
|
8330
|
+
const attestedEnforcer = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
9099
8331
|
if (attestedEnforcer.enforcerDid !== envelope.target.nodeAudience || typeof attestedEnforcer.nodeAudience !== "string") throw new Error("v3 attested enforcer binding mismatch");
|
|
9100
8332
|
try {
|
|
9101
8333
|
ed25519PublicKeyFromDidKey(attestedEnforcer.nodeAudience);
|
|
@@ -9110,7 +8342,7 @@ var ShareRecipientClient = class {
|
|
|
9110
8342
|
...this.options.signal === void 0 ? {} : { signal: this.options.signal }
|
|
9111
8343
|
});
|
|
9112
8344
|
if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
|
|
9113
|
-
const challenge =
|
|
8345
|
+
const challenge = object3(await challengeResponse.json(), "v3 policy challenge");
|
|
9114
8346
|
if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid || challenge.nodeAudience !== attestedEnforcer.nodeAudience || challenge.enforcerDid !== void 0 && challenge.enforcerDid !== attestedEnforcer.enforcerDid || typeof challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("v3 policy challenge binding mismatch");
|
|
9115
8347
|
const material = await this.options.buildPresentation({
|
|
9116
8348
|
challenge,
|
|
@@ -9131,7 +8363,7 @@ var ShareRecipientClient = class {
|
|
|
9131
8363
|
...this.options.signal === void 0 ? {} : { signal: this.options.signal }
|
|
9132
8364
|
});
|
|
9133
8365
|
if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
|
|
9134
|
-
const delegation =
|
|
8366
|
+
const delegation = object3(await delegationResponse.json(), "v3 policy delegation");
|
|
9135
8367
|
if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
|
|
9136
8368
|
const compact = verifyV3PolicyAuthorization({ authorization: delegation.authorization, cid: delegation.sessionCid, envelope, holderDid: this.options.holderDid });
|
|
9137
8369
|
const fact = compact.payload.fct[0];
|
|
@@ -9149,9 +8381,10 @@ var ShareRecipientClient = class {
|
|
|
9149
8381
|
const envelope = this.options.envelope;
|
|
9150
8382
|
const signer = this.nativeSigner ?? this.signer;
|
|
9151
8383
|
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");
|
|
8384
|
+
if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
|
|
9152
8385
|
const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
|
|
9153
8386
|
const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
|
|
9154
|
-
const receiverPublicKey = toBase64(
|
|
8387
|
+
const receiverPublicKey = toBase64(import_ed255195.x25519.getPublicKey(receiverPrivateKey));
|
|
9155
8388
|
const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
|
|
9156
8389
|
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 };
|
|
9157
8390
|
const bodyHash = hex3((0, import_sha2565.sha256)(new TextEncoder().encode(canonicalize(body))));
|
|
@@ -9172,16 +8405,16 @@ var ShareRecipientClient = class {
|
|
|
9172
8405
|
this.options.onStage?.("decryption");
|
|
9173
8406
|
const response = await this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json", Authorization: invocation.authorization }, body: JSON.stringify(body), ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
|
|
9174
8407
|
if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
|
|
9175
|
-
const value =
|
|
8408
|
+
const value = object3(await response.json(), "v3 decrypt response");
|
|
9176
8409
|
const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
|
|
9177
8410
|
if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex3((0, import_sha2565.sha256)(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
|
|
9178
8411
|
const unsigned = { ...value };
|
|
9179
8412
|
delete unsigned.nodeSignature;
|
|
9180
8413
|
const signature = fromBase64(value.nodeSignature, "v3 decrypt response signature");
|
|
9181
|
-
if (signature.length !== 64 || !
|
|
8414
|
+
if (signature.length !== 64 || !import_ed255195.ed25519.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
|
|
9182
8415
|
const wrapped = fromBase64(value.wrappedKey, "v3 wrapped content key");
|
|
9183
8416
|
if (wrapped.length < 61 || wrapped[32] !== 1) throw new Error("v3 wrapped content key is malformed");
|
|
9184
|
-
const shared =
|
|
8417
|
+
const shared = import_ed255195.x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
|
|
9185
8418
|
const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(33));
|
|
9186
8419
|
shared.fill(0);
|
|
9187
8420
|
if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
|
|
@@ -9260,15 +8493,15 @@ function createAddressedAuthorization(input) {
|
|
|
9260
8493
|
},
|
|
9261
8494
|
async verifyResult({ envelope, value, proof }) {
|
|
9262
8495
|
try {
|
|
9263
|
-
const wrapper =
|
|
9264
|
-
const response =
|
|
8496
|
+
const wrapper = object3(proof, "share read proof");
|
|
8497
|
+
const response = object3(wrapper.response, "share read response proof");
|
|
9265
8498
|
const envelopeAction = nativeAction("read");
|
|
9266
8499
|
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;
|
|
9267
|
-
const detached =
|
|
9268
|
-
if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
|
|
8500
|
+
const detached = object3(wrapper.detached, "share read detached proof");
|
|
8501
|
+
if (input.trustedNode === void 0 || detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
|
|
9269
8502
|
const unsigned = { ...response };
|
|
9270
8503
|
delete unsigned.proof;
|
|
9271
|
-
return
|
|
8504
|
+
return import_ed255195.ed25519.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
|
|
9272
8505
|
} catch {
|
|
9273
8506
|
return false;
|
|
9274
8507
|
}
|
|
@@ -9300,8 +8533,40 @@ async function claimShare(input) {
|
|
|
9300
8533
|
async function resumeShareAuthorization(input) {
|
|
9301
8534
|
return authorizeShare(input);
|
|
9302
8535
|
}
|
|
8536
|
+
|
|
8537
|
+
// src/native.ts
|
|
8538
|
+
var NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
|
|
8539
|
+
function canonicalViewerUrl(viewerOrigin) {
|
|
8540
|
+
const url = new URL(viewerOrigin);
|
|
8541
|
+
if (url.protocol !== "https:" || url.origin !== viewerOrigin || url.pathname !== "/" || url.search || url.hash) throw new TypeError("viewer origin must be a canonical HTTPS origin");
|
|
8542
|
+
url.pathname = "/viewer";
|
|
8543
|
+
return url;
|
|
8544
|
+
}
|
|
8545
|
+
async function createNativeShare(sharing, input) {
|
|
8546
|
+
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");
|
|
8547
|
+
const generated = await sharing.generate({ path: input.path, actions: ["tinycloud.kv/get"], expiry: input.expiresAt });
|
|
8548
|
+
if (generated.ok !== true) throw new Error(typeof generated.error?.message === "string" ? generated.error.message : "TinyCloud sharing service rejected delegation generation");
|
|
8549
|
+
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");
|
|
8550
|
+
const delegation = sharing.decodeLink(generated.data.token);
|
|
8551
|
+
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");
|
|
8552
|
+
const url = canonicalViewerUrl(input.viewerOrigin);
|
|
8553
|
+
url.hash = `${NATIVE_SHARE_FRAGMENT_PARAMETER}=${encodeURIComponent(generated.data.token)}`;
|
|
8554
|
+
return { url: url.toString(), delegationCid: generated.data.delegation.cid, expiresAt: generated.data.expiresAt, spaceId: delegation.spaceId };
|
|
8555
|
+
}
|
|
8556
|
+
function parseNativeShareUrl(value) {
|
|
8557
|
+
const url = new URL(value);
|
|
8558
|
+
if (url.search || url.pathname !== "/viewer") throw new TypeError("native shares must carry tc1 only in the /viewer URL fragment");
|
|
8559
|
+
const fragment = new URLSearchParams(url.hash.slice(1));
|
|
8560
|
+
const token = fragment.get(NATIVE_SHARE_FRAGMENT_PARAMETER);
|
|
8561
|
+
if (!token || fragment.size !== 1) throw new TypeError("missing native share fragment");
|
|
8562
|
+
return token;
|
|
8563
|
+
}
|
|
8564
|
+
async function openNativeShare(sharing, link) {
|
|
8565
|
+
return sharing.receive(parseNativeShareUrl(link), { autoSubdelegate: false, useSessionKey: false });
|
|
8566
|
+
}
|
|
9303
8567
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9304
8568
|
0 && (module.exports = {
|
|
8569
|
+
CredentialInvitationError,
|
|
9305
8570
|
DEFAULT_MAX_CONTENT_BLOB_BYTES,
|
|
9306
8571
|
DEFAULT_MAX_SEALED_BLOB_BYTES,
|
|
9307
8572
|
DEFAULT_SHARE_LIFETIME_MS,
|
|
@@ -9311,12 +8576,11 @@ async function resumeShareAuthorization(input) {
|
|
|
9311
8576
|
MemorySenderShareKeyStorage,
|
|
9312
8577
|
MemorySenderShareRecordStorage,
|
|
9313
8578
|
MemoryShareCache,
|
|
8579
|
+
NATIVE_SHARE_FRAGMENT_PARAMETER,
|
|
9314
8580
|
OWNER_SHARE_REGISTRATION_DOMAIN,
|
|
9315
8581
|
SHARE_CONTENT_LIMIT,
|
|
9316
|
-
SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
|
|
9317
8582
|
SHARE_PUBLISH_RESULT_VERSION,
|
|
9318
8583
|
SHARE_RESULT_VERSION,
|
|
9319
|
-
SHARE_SEALED_OVERHEAD,
|
|
9320
8584
|
SHARE_V2_PROTOCOL,
|
|
9321
8585
|
SenderShareStore,
|
|
9322
8586
|
ShareNotifyError,
|
|
@@ -9332,23 +8596,22 @@ async function resumeShareAuthorization(input) {
|
|
|
9332
8596
|
computeOwnerShareRegistrationCid,
|
|
9333
8597
|
createAddressedAuthorization,
|
|
9334
8598
|
createDelegatedShareKey,
|
|
8599
|
+
createNativeShare,
|
|
9335
8600
|
createPolicyEnforcementDelegation,
|
|
9336
|
-
createRegisteredPolicyAuthority,
|
|
9337
8601
|
createShareV2HolderBindingArtifact,
|
|
8602
|
+
deliverCredentialInvitation,
|
|
9338
8603
|
fromBase64Url,
|
|
9339
8604
|
historyRecordForPublishedShare,
|
|
9340
8605
|
inspectShare,
|
|
9341
|
-
isLegacyShareLink,
|
|
9342
8606
|
listShares,
|
|
9343
|
-
migrateShare,
|
|
9344
8607
|
normalizeShareTarget,
|
|
9345
8608
|
notifyShare,
|
|
8609
|
+
openNativeShare,
|
|
9346
8610
|
parseAddressedEnvelope,
|
|
8611
|
+
parseNativeShareUrl,
|
|
9347
8612
|
publishAddressedShare,
|
|
9348
8613
|
publishPolicyShare,
|
|
9349
|
-
publishShare,
|
|
9350
8614
|
publishTargetShare,
|
|
9351
|
-
receiveLegacyShare,
|
|
9352
8615
|
receiveShare,
|
|
9353
8616
|
recipientMatchesShareRecord,
|
|
9354
8617
|
redactPublishedShare,
|
|
@@ -9357,14 +8620,11 @@ async function resumeShareAuthorization(input) {
|
|
|
9357
8620
|
restoreDelegatedShareKey,
|
|
9358
8621
|
resumeShareAuthorization,
|
|
9359
8622
|
revokeShare,
|
|
9360
|
-
shareDeliveryTrustedKid,
|
|
9361
8623
|
showShare,
|
|
9362
8624
|
targetAuthorizationMethod,
|
|
9363
8625
|
toBase64Url,
|
|
9364
8626
|
toShareErrorInfo,
|
|
9365
8627
|
validateOwnerSharePolicyRegistration,
|
|
9366
|
-
validateOwnerSharePolicyRegistrationBytes
|
|
9367
|
-
validateShareDeliveryAuthorizationBytes,
|
|
9368
|
-
verifyBearerEnvelope
|
|
8628
|
+
validateOwnerSharePolicyRegistrationBytes
|
|
9369
8629
|
});
|
|
9370
8630
|
//# sourceMappingURL=index.cjs.map
|