@tinycloud/cli 0.9.1-beta.9 → 1.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1128 -2637
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +214 -1103
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +5 -5
package/dist/legacy-entry.js
CHANGED
|
@@ -319,9 +319,9 @@ function capSpecFromAuthMeta(resource, action) {
|
|
|
319
319
|
if (nextSlash <= 0) return void 0;
|
|
320
320
|
const serviceShort = rest.slice(0, nextSlash);
|
|
321
321
|
const path = rest.slice(nextSlash + 1);
|
|
322
|
-
const
|
|
322
|
+
const actionName = action.includes("/") ? action.slice(action.indexOf("/") + 1) : action;
|
|
323
323
|
const spaceName = spaceUri.startsWith("tinycloud:") ? spaceUri.slice(spaceUri.lastIndexOf(":") + 1) : spaceUri;
|
|
324
|
-
return `tinycloud.${serviceShort}:${spaceName}:${path}:${
|
|
324
|
+
return `tinycloud.${serviceShort}:${spaceName}:${path}:${actionName}`;
|
|
325
325
|
}
|
|
326
326
|
function buildNetworkHint() {
|
|
327
327
|
const readHost = (name2) => {
|
|
@@ -13307,7 +13307,7 @@ var init_dist2 = __esm({
|
|
|
13307
13307
|
*/
|
|
13308
13308
|
getFullPath(key, prefixOverride) {
|
|
13309
13309
|
const prefix = prefixOverride ?? this._config.prefix ?? "";
|
|
13310
|
-
return prefix ? `${prefix}/${key}` : key;
|
|
13310
|
+
return prefix ? key === "" ? prefix : `${prefix}/${key}` : key;
|
|
13311
13311
|
}
|
|
13312
13312
|
/**
|
|
13313
13313
|
* Get the host URL.
|
|
@@ -17609,17 +17609,6 @@ function fromBase64Url(text) {
|
|
|
17609
17609
|
function utf8Bytes(text) {
|
|
17610
17610
|
return new TextEncoder().encode(text);
|
|
17611
17611
|
}
|
|
17612
|
-
function didKeyFromEd25519PublicKey(publicKey) {
|
|
17613
|
-
if (publicKey.length !== PUBLIC_KEY_LENGTH) {
|
|
17614
|
-
throw new TypeError(
|
|
17615
|
-
`ed25519 public key must be ${PUBLIC_KEY_LENGTH} bytes, got ${publicKey.length}`
|
|
17616
|
-
);
|
|
17617
|
-
}
|
|
17618
|
-
const prefixed = new Uint8Array(ED25519_MULTICODEC_PREFIX.length + publicKey.length);
|
|
17619
|
-
prefixed.set(ED25519_MULTICODEC_PREFIX, 0);
|
|
17620
|
-
prefixed.set(publicKey, ED25519_MULTICODEC_PREFIX.length);
|
|
17621
|
-
return `did:key:${base58btc.encode(prefixed)}`;
|
|
17622
|
-
}
|
|
17623
17612
|
function ed25519PublicKeyFromDidKey(did) {
|
|
17624
17613
|
if (!did.startsWith("did:key:")) {
|
|
17625
17614
|
throw new TypeError(`not a did:key: ${did}`);
|
|
@@ -17631,15 +17620,6 @@ function ed25519PublicKeyFromDidKey(did) {
|
|
|
17631
17620
|
}
|
|
17632
17621
|
return prefixed.slice(ED25519_MULTICODEC_PREFIX.length);
|
|
17633
17622
|
}
|
|
17634
|
-
function getBearerSessionJwk(envelope) {
|
|
17635
|
-
const target = envelope.authorizationTarget;
|
|
17636
|
-
if (target.kind !== "bearerKey") {
|
|
17637
|
-
throw new TypeError(
|
|
17638
|
-
`envelope authorizationTarget is "${target.kind}", not "bearerKey"`
|
|
17639
|
-
);
|
|
17640
|
-
}
|
|
17641
|
-
return target.sessionJwk;
|
|
17642
|
-
}
|
|
17643
17623
|
function decodeBase64UrlOrNull(value) {
|
|
17644
17624
|
try {
|
|
17645
17625
|
return fromBase64Url(value);
|
|
@@ -17697,7 +17677,7 @@ function validateV3Invariants(value, ctx) {
|
|
|
17697
17677
|
if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
|
|
17698
17678
|
ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
|
|
17699
17679
|
}
|
|
17700
|
-
if (value.attestedEnforcerBinding.
|
|
17680
|
+
if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
17701
17681
|
ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
17702
17682
|
}
|
|
17703
17683
|
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) {
|
|
@@ -17712,211 +17692,6 @@ function validateV3Invariants(value, ctx) {
|
|
|
17712
17692
|
ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
|
|
17713
17693
|
}
|
|
17714
17694
|
}
|
|
17715
|
-
function decodeJsonSegment(segment) {
|
|
17716
|
-
try {
|
|
17717
|
-
const bytes3 = fromBase64Url(segment);
|
|
17718
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes3));
|
|
17719
|
-
} catch {
|
|
17720
|
-
return null;
|
|
17721
|
-
}
|
|
17722
|
-
}
|
|
17723
|
-
function isJsonObject(value) {
|
|
17724
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17725
|
-
}
|
|
17726
|
-
function parseCapability(value) {
|
|
17727
|
-
if (!isJsonObject(value)) return null;
|
|
17728
|
-
const withUri = value["with"];
|
|
17729
|
-
const can = value["can"];
|
|
17730
|
-
if (typeof withUri !== "string" || withUri.length === 0) return null;
|
|
17731
|
-
if (typeof can !== "string" || can.length === 0) return null;
|
|
17732
|
-
return { with: withUri, can };
|
|
17733
|
-
}
|
|
17734
|
-
function parseCanonicalResourceUri(uri) {
|
|
17735
|
-
const match = /^(https:\/\/[^/\\]+)\/(.*)$/.exec(uri);
|
|
17736
|
-
if (match === null) return null;
|
|
17737
|
-
const [, origin, path] = match;
|
|
17738
|
-
if (!isCanonicalHttpsOrigin(origin)) return null;
|
|
17739
|
-
if (!isCanonicalResourcePath(path)) return null;
|
|
17740
|
-
return { origin, segments: path.split("/") };
|
|
17741
|
-
}
|
|
17742
|
-
function resourceUriCovers(granted, target) {
|
|
17743
|
-
const targetParsed = parseCanonicalResourceUri(target);
|
|
17744
|
-
if (targetParsed === null) return false;
|
|
17745
|
-
const wildcard = granted.endsWith("/*");
|
|
17746
|
-
const grantedParsed = parseCanonicalResourceUri(
|
|
17747
|
-
wildcard ? granted.slice(0, -2) : granted
|
|
17748
|
-
);
|
|
17749
|
-
if (grantedParsed === null) return false;
|
|
17750
|
-
if (grantedParsed.origin !== targetParsed.origin) return false;
|
|
17751
|
-
const grantedSegments = grantedParsed.segments;
|
|
17752
|
-
const targetSegments = targetParsed.segments;
|
|
17753
|
-
const prefixMatches = grantedSegments.every(
|
|
17754
|
-
(segment, index) => segment === targetSegments[index]
|
|
17755
|
-
);
|
|
17756
|
-
if (!prefixMatches) return false;
|
|
17757
|
-
return wildcard ? targetSegments.length > grantedSegments.length : targetSegments.length === grantedSegments.length;
|
|
17758
|
-
}
|
|
17759
|
-
function bearerResourceUri(origin, spaceId, path) {
|
|
17760
|
-
return `${origin}/${spaceId}/${path}`;
|
|
17761
|
-
}
|
|
17762
|
-
function requiredResourceUri(envelope) {
|
|
17763
|
-
const { origin, spaceId, resource } = envelope.target;
|
|
17764
|
-
return bearerResourceUri(origin, spaceId, resource.path);
|
|
17765
|
-
}
|
|
17766
|
-
function checkBearerDelegation(envelope, options = {}) {
|
|
17767
|
-
let sessionDid;
|
|
17768
|
-
try {
|
|
17769
|
-
const jwk = getBearerSessionJwk(envelope);
|
|
17770
|
-
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519") {
|
|
17771
|
-
return {
|
|
17772
|
-
ok: false,
|
|
17773
|
-
detail: `unsupported bearer session key family ${jwk.kty}/${jwk.crv}; this build binds Ed25519 keys only`
|
|
17774
|
-
};
|
|
17775
|
-
}
|
|
17776
|
-
const publicKey = fromBase64Url(jwk.x);
|
|
17777
|
-
const privateKey = fromBase64Url(jwk.d);
|
|
17778
|
-
if (publicKey.length !== 32 || privateKey.length !== 32) {
|
|
17779
|
-
return { ok: false, detail: "bearer session key must contain 32-byte Ed25519 coordinates" };
|
|
17780
|
-
}
|
|
17781
|
-
const derivedPublicKey = ed25519.getPublicKey(privateKey);
|
|
17782
|
-
if (!derivedPublicKey.every((byte, index) => byte === publicKey[index])) {
|
|
17783
|
-
return { ok: false, detail: "bearer session private and public keys do not match" };
|
|
17784
|
-
}
|
|
17785
|
-
sessionDid = didKeyFromEd25519PublicKey(publicKey);
|
|
17786
|
-
} catch (error) {
|
|
17787
|
-
return {
|
|
17788
|
-
ok: false,
|
|
17789
|
-
detail: `could not derive did:key from embedded session key: ${error instanceof Error ? error.message : String(error)}`
|
|
17790
|
-
};
|
|
17791
|
-
}
|
|
17792
|
-
const segments = envelope.delegation.split(".");
|
|
17793
|
-
if (segments.length !== 3 || segments.some((segment) => segment.length === 0)) {
|
|
17794
|
-
return { ok: false, detail: "delegation is not a three-segment JWT-shaped token" };
|
|
17795
|
-
}
|
|
17796
|
-
const [headerSegment, payloadSegment, signatureSegment] = segments;
|
|
17797
|
-
const header = decodeJsonSegment(headerSegment);
|
|
17798
|
-
if (!isJsonObject(header)) {
|
|
17799
|
-
return { ok: false, detail: "delegation header is not a base64url JSON object" };
|
|
17800
|
-
}
|
|
17801
|
-
const payload = decodeJsonSegment(payloadSegment);
|
|
17802
|
-
if (!isJsonObject(payload)) {
|
|
17803
|
-
return { ok: false, detail: "delegation payload is not a base64url JSON object" };
|
|
17804
|
-
}
|
|
17805
|
-
let signatureBytes;
|
|
17806
|
-
try {
|
|
17807
|
-
signatureBytes = fromBase64Url(signatureSegment);
|
|
17808
|
-
if (signatureBytes.length === 0) {
|
|
17809
|
-
return { ok: false, detail: "delegation signature segment is empty" };
|
|
17810
|
-
}
|
|
17811
|
-
} catch {
|
|
17812
|
-
return { ok: false, detail: "delegation signature segment is not base64url" };
|
|
17813
|
-
}
|
|
17814
|
-
if (header["alg"] !== "EdDSA") {
|
|
17815
|
-
return {
|
|
17816
|
-
ok: false,
|
|
17817
|
-
detail: `delegation alg must be EdDSA, got ${JSON.stringify(header["alg"])}`
|
|
17818
|
-
};
|
|
17819
|
-
}
|
|
17820
|
-
const issuer = payload["iss"];
|
|
17821
|
-
if (typeof issuer !== "string" || issuer.length === 0) {
|
|
17822
|
-
return { ok: false, detail: "delegation payload has no issuer (iss) DID" };
|
|
17823
|
-
}
|
|
17824
|
-
let issuerPublicKey;
|
|
17825
|
-
try {
|
|
17826
|
-
issuerPublicKey = ed25519PublicKeyFromDidKey(issuer);
|
|
17827
|
-
} catch (error) {
|
|
17828
|
-
return {
|
|
17829
|
-
ok: false,
|
|
17830
|
-
detail: `delegation issuer is not an ed25519 did:key: ${error instanceof Error ? error.message : String(error)}`
|
|
17831
|
-
};
|
|
17832
|
-
}
|
|
17833
|
-
let signatureValid;
|
|
17834
|
-
try {
|
|
17835
|
-
signatureValid = ed25519.verify(
|
|
17836
|
-
signatureBytes,
|
|
17837
|
-
utf8Bytes(`${headerSegment}.${payloadSegment}`),
|
|
17838
|
-
issuerPublicKey,
|
|
17839
|
-
ED25519_VERIFY_OPTS
|
|
17840
|
-
);
|
|
17841
|
-
} catch {
|
|
17842
|
-
signatureValid = false;
|
|
17843
|
-
}
|
|
17844
|
-
if (!signatureValid) {
|
|
17845
|
-
return { ok: false, detail: "delegation signature does not verify against iss" };
|
|
17846
|
-
}
|
|
17847
|
-
const nowMs2 = options.now?.() ?? Date.now();
|
|
17848
|
-
const exp = payload["exp"];
|
|
17849
|
-
if (typeof exp !== "number" || !Number.isSafeInteger(exp) || exp <= 0) {
|
|
17850
|
-
return { ok: false, detail: "delegation has no valid expiry (exp) claim" };
|
|
17851
|
-
}
|
|
17852
|
-
if (nowMs2 >= exp * 1e3) {
|
|
17853
|
-
return { ok: false, detail: "delegation is expired" };
|
|
17854
|
-
}
|
|
17855
|
-
const nbf = payload["nbf"];
|
|
17856
|
-
if (nbf !== void 0) {
|
|
17857
|
-
if (typeof nbf !== "number" || !Number.isSafeInteger(nbf)) {
|
|
17858
|
-
return { ok: false, detail: "delegation nbf claim is not a valid time" };
|
|
17859
|
-
}
|
|
17860
|
-
if (nowMs2 < nbf * 1e3) {
|
|
17861
|
-
return { ok: false, detail: "delegation is not yet valid (nbf)" };
|
|
17862
|
-
}
|
|
17863
|
-
}
|
|
17864
|
-
const audience = payload["aud"];
|
|
17865
|
-
if (typeof audience !== "string" || audience.length === 0) {
|
|
17866
|
-
return { ok: false, detail: "delegation payload has no audience (aud) DID" };
|
|
17867
|
-
}
|
|
17868
|
-
if (audience !== sessionDid) {
|
|
17869
|
-
return {
|
|
17870
|
-
ok: false,
|
|
17871
|
-
detail: "delegation audience is not the link's embedded session key"
|
|
17872
|
-
};
|
|
17873
|
-
}
|
|
17874
|
-
const att = payload["att"];
|
|
17875
|
-
if (!Array.isArray(att) || att.length === 0) {
|
|
17876
|
-
return { ok: false, detail: "delegation payload has no capabilities (att)" };
|
|
17877
|
-
}
|
|
17878
|
-
const required = requiredResourceUri(envelope);
|
|
17879
|
-
const granted = att.map(parseCapability).some(
|
|
17880
|
-
(capability) => capability !== null && READ_ABILITIES.has(capability.can) && resourceUriCovers(capability.with, required)
|
|
17881
|
-
);
|
|
17882
|
-
if (!granted) {
|
|
17883
|
-
return {
|
|
17884
|
-
ok: false,
|
|
17885
|
-
detail: `delegation grants no read capability covering ${required}`
|
|
17886
|
-
};
|
|
17887
|
-
}
|
|
17888
|
-
return { ok: true, delegateeDid: audience };
|
|
17889
|
-
}
|
|
17890
|
-
function mintBearerDelegation(options) {
|
|
17891
|
-
const { issuerPrivateKey, audienceDid, resourceUri, expiresAtSeconds } = options;
|
|
17892
|
-
if (!audienceDid.startsWith("did:")) {
|
|
17893
|
-
throw new TypeError(`audienceDid must be a DID, got ${audienceDid}`);
|
|
17894
|
-
}
|
|
17895
|
-
if (resourceUri.length === 0) {
|
|
17896
|
-
throw new TypeError("resourceUri must be non-empty");
|
|
17897
|
-
}
|
|
17898
|
-
if (!Number.isSafeInteger(expiresAtSeconds) || expiresAtSeconds <= 0) {
|
|
17899
|
-
throw new TypeError(
|
|
17900
|
-
`expiresAtSeconds must be a positive integer (epoch seconds), got ${expiresAtSeconds}`
|
|
17901
|
-
);
|
|
17902
|
-
}
|
|
17903
|
-
const issuerDid = didKeyFromEd25519PublicKey(
|
|
17904
|
-
ed25519.getPublicKey(issuerPrivateKey)
|
|
17905
|
-
);
|
|
17906
|
-
const header = { alg: "EdDSA", typ: "JWT", ucv: "0.9.1" };
|
|
17907
|
-
const payload = {
|
|
17908
|
-
iss: issuerDid,
|
|
17909
|
-
aud: audienceDid,
|
|
17910
|
-
att: [{ with: resourceUri, can: BEARER_READ_ABILITY }],
|
|
17911
|
-
prf: [],
|
|
17912
|
-
exp: expiresAtSeconds
|
|
17913
|
-
};
|
|
17914
|
-
const signingInput = `${toBase64Url(utf8Bytes(JSON.stringify(header)))}.${toBase64Url(
|
|
17915
|
-
utf8Bytes(JSON.stringify(payload))
|
|
17916
|
-
)}`;
|
|
17917
|
-
const signature = ed25519.sign(utf8Bytes(signingInput), issuerPrivateKey);
|
|
17918
|
-
return `${signingInput}.${toBase64Url(signature)}`;
|
|
17919
|
-
}
|
|
17920
17695
|
function assertKey(key32) {
|
|
17921
17696
|
if (key32.length !== KEY_LENGTH) {
|
|
17922
17697
|
throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
|
|
@@ -17932,17 +17707,6 @@ async function importAesKey(key32, usage) {
|
|
|
17932
17707
|
[usage]
|
|
17933
17708
|
);
|
|
17934
17709
|
}
|
|
17935
|
-
function generateKey() {
|
|
17936
|
-
return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
|
17937
|
-
}
|
|
17938
|
-
async function seal(plaintextBytes, key32) {
|
|
17939
|
-
const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
|
|
17940
|
-
const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
|
|
17941
|
-
blob[0] = SEALED_BLOB_VERSION;
|
|
17942
|
-
blob.set(nonce, HEADER_LENGTH);
|
|
17943
|
-
blob.set(ciphertext, HEADER_LENGTH + nonce.length);
|
|
17944
|
-
return { blob, cid: await computeCid(blob) };
|
|
17945
|
-
}
|
|
17946
17710
|
async function open(blob, key32) {
|
|
17947
17711
|
if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
|
|
17948
17712
|
throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
|
|
@@ -17954,18 +17718,6 @@ async function open(blob, key32) {
|
|
|
17954
17718
|
const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
|
|
17955
17719
|
return decryptEnvelope(nonce, ciphertext, key32);
|
|
17956
17720
|
}
|
|
17957
|
-
async function encryptEnvelope(plaintextBytes, key32) {
|
|
17958
|
-
const key = await importAesKey(key32, "encrypt");
|
|
17959
|
-
const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
|
17960
|
-
const ciphertext = new Uint8Array(
|
|
17961
|
-
await globalThis.crypto.subtle.encrypt(
|
|
17962
|
-
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
17963
|
-
key,
|
|
17964
|
-
plaintextBytes
|
|
17965
|
-
)
|
|
17966
|
-
);
|
|
17967
|
-
return { nonce, ciphertext };
|
|
17968
|
-
}
|
|
17969
17721
|
async function decryptEnvelope(nonce, ciphertext, key32) {
|
|
17970
17722
|
if (nonce.length !== NONCE_LENGTH) {
|
|
17971
17723
|
throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
|
|
@@ -18077,22 +17829,6 @@ function assertExactKeys(value, keys, label) {
|
|
|
18077
17829
|
function equal(left, right) {
|
|
18078
17830
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
18079
17831
|
}
|
|
18080
|
-
function signingBytes(unsigned) {
|
|
18081
|
-
const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
|
|
18082
|
-
const body = utf8Bytes(canonicalize2(unsigned));
|
|
18083
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
18084
|
-
bytes3.set(domain);
|
|
18085
|
-
bytes3.set(body, domain.length);
|
|
18086
|
-
return bytes3;
|
|
18087
|
-
}
|
|
18088
|
-
function signingBytesV2(unsigned) {
|
|
18089
|
-
const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
|
|
18090
|
-
const body = utf8Bytes(canonicalize2(unsigned));
|
|
18091
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
18092
|
-
bytes3.set(domain);
|
|
18093
|
-
bytes3.set(body, domain.length);
|
|
18094
|
-
return bytes3;
|
|
18095
|
-
}
|
|
18096
17832
|
function signingBytesV3(unsigned) {
|
|
18097
17833
|
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
18098
17834
|
const body = utf8Bytes(canonicalize2(unsigned));
|
|
@@ -18101,53 +17837,6 @@ function signingBytesV3(unsigned) {
|
|
|
18101
17837
|
bytes3.set(body, domain.length);
|
|
18102
17838
|
return bytes3;
|
|
18103
17839
|
}
|
|
18104
|
-
function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
|
|
18105
|
-
const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
|
|
18106
|
-
const publicKey = ed25519.getPublicKey(ed25519PrivKey);
|
|
18107
|
-
const signature = ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
|
|
18108
|
-
return {
|
|
18109
|
-
...unsigned,
|
|
18110
|
-
signature: {
|
|
18111
|
-
signerDid: didKeyFromEd25519PublicKey(publicKey),
|
|
18112
|
-
algorithm: "Ed25519",
|
|
18113
|
-
value: toBase64Url(signature)
|
|
18114
|
-
}
|
|
18115
|
-
};
|
|
18116
|
-
}
|
|
18117
|
-
function verifyEnvelopeSignatureOnly(envelope) {
|
|
18118
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
18119
|
-
const { signature, ...unsigned } = parsed;
|
|
18120
|
-
const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
|
|
18121
|
-
return ed25519.verify(
|
|
18122
|
-
fromBase64Url(signature.value),
|
|
18123
|
-
signingBytes(unsigned),
|
|
18124
|
-
publicKey,
|
|
18125
|
-
ED25519_VERIFY_OPTS2
|
|
18126
|
-
);
|
|
18127
|
-
}
|
|
18128
|
-
async function verifyEnvelope(envelope, options) {
|
|
18129
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
18130
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid) return false;
|
|
18131
|
-
if (!verifyEnvelopeSignatureOnly(parsed)) return false;
|
|
18132
|
-
if (parsed.authorizationTarget.kind === "policy") {
|
|
18133
|
-
const policyBytes = fromBase64Url(parsed.authorizationTarget.policyBytes);
|
|
18134
|
-
if (await computeCid(policyBytes) !== parsed.authorizationTarget.policyCid) {
|
|
18135
|
-
return false;
|
|
18136
|
-
}
|
|
18137
|
-
}
|
|
18138
|
-
return true;
|
|
18139
|
-
}
|
|
18140
|
-
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
18141
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
18142
|
-
const { signature, ...unsigned } = parsed;
|
|
18143
|
-
return ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
18144
|
-
}
|
|
18145
|
-
async function verifyEnvelopeV2(envelope, options) {
|
|
18146
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
18147
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
|
|
18148
|
-
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
18149
|
-
return true;
|
|
18150
|
-
}
|
|
18151
17840
|
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
18152
17841
|
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
18153
17842
|
const { signature, ...unsigned } = parsed;
|
|
@@ -18193,7 +17882,7 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
18193
17882
|
const binding = parsed.attestedEnforcerBinding;
|
|
18194
17883
|
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
18195
17884
|
const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize2({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
18196
|
-
if (binding.
|
|
17885
|
+
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;
|
|
18197
17886
|
try {
|
|
18198
17887
|
const digest3 = sha2562(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize2(unsignedBinding)}`));
|
|
18199
17888
|
if (!ed25519.verify(fromBase64Url(bindingSignature.value), digest3, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
|
|
@@ -18241,439 +17930,113 @@ function assertCanonicalCid(cidString) {
|
|
|
18241
17930
|
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
18242
17931
|
}
|
|
18243
17932
|
}
|
|
18244
|
-
function
|
|
18245
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
18246
|
-
throw new TypeError(`key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
18247
|
-
}
|
|
18248
|
-
assertCanonicalCid(ciphertextCid);
|
|
18249
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
18250
|
-
throw new TypeError(`origin must be a canonical https origin, got ${origin}`);
|
|
18251
|
-
}
|
|
18252
|
-
return `${origin}/s/${ciphertextCid}#k=${toBase64Url(key32)}`;
|
|
18253
|
-
}
|
|
18254
|
-
function parseShareUrl(url, options = {}) {
|
|
17933
|
+
async function parseSealedInlineShareUrl(url, options = {}) {
|
|
18255
17934
|
const parsed = new URL(url);
|
|
18256
|
-
if (parsed.protocol !== "https:")
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
if (parsed.
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
}
|
|
18265
|
-
if (options.expectedOrigin !== void 0) {
|
|
18266
|
-
if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
|
|
18267
|
-
throw new TypeError(
|
|
18268
|
-
`expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
|
|
18269
|
-
);
|
|
18270
|
-
}
|
|
18271
|
-
if (parsed.origin !== options.expectedOrigin) {
|
|
18272
|
-
throw new TypeError(
|
|
18273
|
-
`share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
|
|
18274
|
-
);
|
|
18275
|
-
}
|
|
18276
|
-
}
|
|
18277
|
-
const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
|
|
18278
|
-
if (!match || match[1] === void 0) {
|
|
18279
|
-
throw new TypeError(`not a share URL path: ${parsed.pathname}`);
|
|
18280
|
-
}
|
|
18281
|
-
const ciphertextCid = match[1];
|
|
18282
|
-
assertCanonicalCid(ciphertextCid);
|
|
18283
|
-
if (!parsed.hash.startsWith("#k=")) {
|
|
18284
|
-
throw new TypeError("share URL is missing the #k= key fragment");
|
|
18285
|
-
}
|
|
18286
|
-
const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
|
|
18287
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
18288
|
-
throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
18289
|
-
}
|
|
18290
|
-
return { ciphertextCid, key32 };
|
|
18291
|
-
}
|
|
18292
|
-
async function encodeInlineShareUrl(parts) {
|
|
18293
|
-
if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
|
|
18294
|
-
if (parts.ciphertext.byteLength === 0 || parts.ciphertext.byteLength > MAX_INLINE_BYTES) throw new RangeError("inline ciphertext is outside the allowed size");
|
|
18295
|
-
if (parts.key32 !== void 0 && parts.key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
|
|
18296
|
-
const ciphertextCid = await computeCid(parts.ciphertext);
|
|
18297
|
-
const payload = canonicalize2({
|
|
18298
|
-
v: 2,
|
|
18299
|
-
c: toBase64Url(parts.ciphertext),
|
|
18300
|
-
cid: ciphertextCid,
|
|
18301
|
-
...parts.key32 === void 0 ? {} : { k: toBase64Url(parts.key32) }
|
|
18302
|
-
});
|
|
18303
|
-
const payloadBytes = new TextEncoder().encode(payload);
|
|
18304
|
-
if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
|
|
18305
|
-
return `${parts.origin}/s/inline${INLINE_PREFIX}${toBase64Url(payloadBytes)}`;
|
|
18306
|
-
}
|
|
18307
|
-
function parseInlineShareUrl(url, options = {}) {
|
|
18308
|
-
const parsed = new URL(url);
|
|
18309
|
-
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "") throw new TypeError("inline share URL must be canonical HTTPS without query or userinfo");
|
|
18310
|
-
if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
|
|
18311
|
-
if (parsed.pathname !== "/s/inline" || !parsed.hash.startsWith(INLINE_PREFIX)) throw new TypeError("not an inline v2 share URL");
|
|
17935
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("sealed inline share URL must be canonical HTTPS without userinfo");
|
|
17936
|
+
if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("sealed inline share URL origin is not trusted");
|
|
17937
|
+
if (parsed.pathname !== "/s/inline" || parsed.search !== "") throw new TypeError("not a Node sealed-inline share URL");
|
|
17938
|
+
if (url !== `${parsed.origin}/s/inline${parsed.hash}`) throw new TypeError("sealed inline share URL is not lexically canonical");
|
|
17939
|
+
const prefix = "#v=2&p=";
|
|
17940
|
+
if (!parsed.hash.startsWith(prefix)) throw new TypeError("sealed inline URL is missing its canonical fragment");
|
|
17941
|
+
const encoded = parsed.hash.slice(prefix.length);
|
|
17942
|
+
if (encoded.length === 0 || parsed.hash !== `${prefix}${encoded}`) throw new TypeError("sealed inline URL fragment is not canonical");
|
|
18312
17943
|
let payloadBytes;
|
|
18313
17944
|
try {
|
|
18314
|
-
payloadBytes = fromBase64Url(
|
|
17945
|
+
payloadBytes = fromBase64Url(encoded);
|
|
18315
17946
|
} catch {
|
|
18316
|
-
throw new TypeError("inline payload is not canonical base64url");
|
|
17947
|
+
throw new TypeError("sealed inline payload is not canonical base64url");
|
|
18317
17948
|
}
|
|
18318
|
-
if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("inline payload is too large");
|
|
17949
|
+
if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("sealed inline payload is too large");
|
|
17950
|
+
const payloadText = new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes);
|
|
18319
17951
|
let value;
|
|
18320
17952
|
try {
|
|
18321
|
-
value = JSON.parse(
|
|
17953
|
+
value = JSON.parse(payloadText);
|
|
18322
17954
|
} catch {
|
|
18323
|
-
throw new TypeError("inline payload is not valid JSON");
|
|
17955
|
+
throw new TypeError("sealed inline payload is not valid JSON");
|
|
18324
17956
|
}
|
|
18325
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
const keys = Object.keys(record2);
|
|
18329
|
-
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");
|
|
17957
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || canonicalize2(value) !== payloadText) throw new TypeError("sealed inline payload is not canonical JSON");
|
|
17958
|
+
const record = value;
|
|
17959
|
+
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");
|
|
18330
17960
|
let ciphertext;
|
|
18331
|
-
try {
|
|
18332
|
-
ciphertext = fromBase64Url(record2.c);
|
|
18333
|
-
} catch {
|
|
18334
|
-
throw new TypeError("inline ciphertext is not canonical base64url");
|
|
18335
|
-
}
|
|
18336
|
-
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
|
|
18337
|
-
assertCanonicalCid(record2.cid);
|
|
18338
|
-
if (record2.k !== void 0 && typeof record2.k !== "string") throw new TypeError("inline key must be base64url");
|
|
18339
17961
|
let key32;
|
|
18340
17962
|
try {
|
|
18341
|
-
|
|
17963
|
+
ciphertext = fromBase64Url(record.c);
|
|
17964
|
+
key32 = fromBase64Url(record.k);
|
|
18342
17965
|
} catch {
|
|
18343
|
-
throw new TypeError("inline
|
|
17966
|
+
throw new TypeError("sealed inline payload has invalid base64url material");
|
|
18344
17967
|
}
|
|
18345
|
-
if (
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18350
|
-
return { kind: "compact", ...parseShareUrl(url, options) };
|
|
18351
|
-
}
|
|
18352
|
-
function boundedBytes(value, limit, code3) {
|
|
18353
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("fetch-failed", "share byte limit is invalid");
|
|
18354
|
-
if (!(value instanceof Uint8Array)) throw new ShareReceiveError("fetch-failed", "registry returned invalid bytes");
|
|
18355
|
-
if (value.byteLength > limit) throw new ShareReceiveError(code3, "share blob exceeds the configured byte limit");
|
|
18356
|
-
return value;
|
|
18357
|
-
}
|
|
18358
|
-
async function readResponseBytes(response, limit, tooLargeCode) {
|
|
18359
|
-
const contentLength = response.headers.get("content-length");
|
|
18360
|
-
if (contentLength !== null && Number.isSafeInteger(Number(contentLength)) && Number(contentLength) > limit) {
|
|
18361
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
18362
|
-
}
|
|
18363
|
-
if (response.body === null) {
|
|
18364
|
-
return boundedBytes(new Uint8Array(await response.arrayBuffer()), limit, tooLargeCode);
|
|
18365
|
-
}
|
|
18366
|
-
const reader = response.body.getReader();
|
|
18367
|
-
const chunks = [];
|
|
18368
|
-
let total = 0;
|
|
18369
|
-
try {
|
|
18370
|
-
for (; ; ) {
|
|
18371
|
-
const part = await reader.read();
|
|
18372
|
-
if (part.done) break;
|
|
18373
|
-
total += part.value.byteLength;
|
|
18374
|
-
if (total > limit) {
|
|
18375
|
-
await reader.cancel();
|
|
18376
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
18377
|
-
}
|
|
18378
|
-
chunks.push(part.value);
|
|
18379
|
-
}
|
|
18380
|
-
} finally {
|
|
18381
|
-
reader.releaseLock();
|
|
18382
|
-
}
|
|
18383
|
-
const bytes3 = new Uint8Array(total);
|
|
18384
|
-
let offset = 0;
|
|
18385
|
-
for (const chunk of chunks) {
|
|
18386
|
-
bytes3.set(chunk, offset);
|
|
18387
|
-
offset += chunk.byteLength;
|
|
18388
|
-
}
|
|
18389
|
-
return bytes3;
|
|
18390
|
-
}
|
|
18391
|
-
function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
|
|
18392
|
-
if (options.fetchBlob !== void 0) return async (input) => {
|
|
18393
|
-
try {
|
|
18394
|
-
options.signal?.throwIfAborted();
|
|
18395
|
-
const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
18396
|
-
options.signal?.throwIfAborted();
|
|
18397
|
-
return boundedBytes(bytes3, limit, tooLargeCode);
|
|
18398
|
-
} catch (error) {
|
|
18399
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
18400
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
18401
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
18402
|
-
}
|
|
18403
|
-
};
|
|
18404
|
-
if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "a registry fetch adapter is required");
|
|
18405
|
-
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
18406
|
-
const base33 = options.registryBaseUrl.replace(/\/+$/, "");
|
|
18407
|
-
return async ({ cid: cid2 }) => {
|
|
18408
|
-
try {
|
|
18409
|
-
options.signal?.throwIfAborted();
|
|
18410
|
-
const response = await fetchFn(`${base33}/ipfs/${cid2}?format=raw`, {
|
|
18411
|
-
headers: { accept: "application/vnd.ipld.raw" },
|
|
18412
|
-
redirect: "error",
|
|
18413
|
-
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
18414
|
-
});
|
|
18415
|
-
options.signal?.throwIfAborted();
|
|
18416
|
-
if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
|
|
18417
|
-
const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
|
|
18418
|
-
options.signal?.throwIfAborted();
|
|
18419
|
-
return bytes3;
|
|
18420
|
-
} catch (error) {
|
|
18421
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
18422
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
18423
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
18424
|
-
}
|
|
18425
|
-
};
|
|
17968
|
+
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("sealed inline ciphertext is outside the allowed size");
|
|
17969
|
+
if (key32.byteLength !== KEY_LENGTH2) throw new TypeError("sealed inline key must be 32 bytes");
|
|
17970
|
+
assertCanonicalCid(record.cid);
|
|
17971
|
+
if (await computeCid(ciphertext) !== record.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
|
|
17972
|
+
return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
|
|
18426
17973
|
}
|
|
18427
17974
|
function metadataFor(envelope, origin) {
|
|
18428
|
-
|
|
18429
|
-
const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
18430
|
-
return {
|
|
18431
|
-
protocol: "tinycloud-share",
|
|
18432
|
-
version: 1,
|
|
18433
|
-
shareId: envelope.shareId,
|
|
18434
|
-
origin,
|
|
18435
|
-
target: { ...envelope.target, kind: targetKind3 },
|
|
18436
|
-
resource: envelope.resource,
|
|
18437
|
-
actions: envelope.actions,
|
|
18438
|
-
expiresAt: envelope.expiry,
|
|
18439
|
-
display: {
|
|
18440
|
-
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
18441
|
-
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
18442
|
-
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
18443
|
-
},
|
|
18444
|
-
...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
|
|
18445
|
-
};
|
|
18446
|
-
}
|
|
17975
|
+
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
|
|
18447
17976
|
return {
|
|
18448
17977
|
protocol: "tinycloud-share",
|
|
18449
17978
|
version: 1,
|
|
18450
17979
|
shareId: envelope.shareId,
|
|
18451
17980
|
origin,
|
|
18452
|
-
target: { ...envelope.target, kind
|
|
18453
|
-
resource: envelope.
|
|
18454
|
-
actions: [
|
|
17981
|
+
target: { ...envelope.target, kind },
|
|
17982
|
+
resource: { ...envelope.resource },
|
|
17983
|
+
actions: [...envelope.actions],
|
|
18455
17984
|
expiresAt: envelope.expiry,
|
|
18456
17985
|
display: {
|
|
18457
17986
|
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
18458
17987
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
18459
17988
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
18460
|
-
}
|
|
18461
|
-
...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
|
|
17989
|
+
}
|
|
18462
17990
|
};
|
|
18463
17991
|
}
|
|
18464
|
-
function
|
|
18465
|
-
|
|
18466
|
-
|
|
18467
|
-
const value = JSON.parse(text);
|
|
18468
|
-
if (canonicalize2(value) !== text) throw new Error("share envelope is not canonical JSON");
|
|
18469
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
18470
|
-
const version2 = value.version;
|
|
18471
|
-
if (version2 === 3) return shareEnvelopeV3Schema.parse(value);
|
|
18472
|
-
if (version2 === 2) return shareEnvelopeV2Schema.parse(value);
|
|
18473
|
-
}
|
|
18474
|
-
return shareEnvelopeSchema.parse(value);
|
|
18475
|
-
} catch {
|
|
18476
|
-
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
18477
|
-
}
|
|
18478
|
-
}
|
|
18479
|
-
async function resolveShareEnvelope(link2, options = {}) {
|
|
17992
|
+
async function resolvePolicyShare(link2, options) {
|
|
17993
|
+
options.signal?.throwIfAborted();
|
|
17994
|
+
let url;
|
|
18480
17995
|
let parsed;
|
|
18481
17996
|
try {
|
|
18482
|
-
|
|
17997
|
+
url = new URL(link2);
|
|
17998
|
+
parsed = await parseSealedInlineShareUrl(link2, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
|
|
18483
17999
|
} catch {
|
|
18484
18000
|
throw new ShareReceiveError("invalid-link", "share link format is invalid");
|
|
18485
18001
|
}
|
|
18002
|
+
const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
18003
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed policy envelope exceeds the configured byte limit");
|
|
18004
|
+
if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "sealed envelope bytes do not match the link CID");
|
|
18005
|
+
options.signal?.throwIfAborted();
|
|
18006
|
+
let envelope;
|
|
18486
18007
|
try {
|
|
18487
|
-
|
|
18488
|
-
const
|
|
18489
|
-
|
|
18490
|
-
|
|
18491
|
-
let plaintext;
|
|
18492
|
-
try {
|
|
18493
|
-
plaintext = parsed.key32 === void 0 ? sealed : await open(sealed, parsed.key32);
|
|
18494
|
-
} catch {
|
|
18495
|
-
throw new ShareReceiveError("decrypt-failed", "share envelope could not be opened");
|
|
18496
|
-
}
|
|
18497
|
-
return { envelope: parseEnvelope(plaintext), origin: url.origin, cid: parsed.ciphertextCid, kind: parsed.kind };
|
|
18498
|
-
} finally {
|
|
18499
|
-
parsed.key32?.fill(0);
|
|
18500
|
-
}
|
|
18501
|
-
}
|
|
18502
|
-
function actionName(value) {
|
|
18503
|
-
return value === "read" ? "tinycloud.kv/get" : value === "list" ? "tinycloud.kv/list" : value === "edit" ? "tinycloud.kv/put" : value;
|
|
18504
|
-
}
|
|
18505
|
-
function resourceContains(outer, inner) {
|
|
18506
|
-
if (outer.kind === "exact") return inner.kind === "exact" && outer.path === inner.path;
|
|
18507
|
-
const prefix = outer.path.endsWith("/") ? outer.path : `${outer.path}/`;
|
|
18508
|
-
return inner.path === outer.path || inner.path.startsWith(prefix);
|
|
18509
|
-
}
|
|
18510
|
-
function policyEvidenceMatches(envelope, evidence) {
|
|
18511
|
-
const registeredActions = new Set(evidence.actions.map(actionName));
|
|
18512
|
-
return evidence.policyCid === (envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "") && evidence.signerDid === envelope.signature.signerDid && evidence.registrationCid.length > 0 && evidence.shareId === envelope.shareId && canonicalize2(evidence.recipientMatcher) === canonicalize2(envelope.recipientMatcher) && canonicalize2(evidence.target) === canonicalize2(envelope.target) && resourceContains(evidence.resource, envelope.resource) && envelope.actions.every((action) => registeredActions.has(actionName(action))) && canonicalize2(evidence.contentSource) === canonicalize2(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);
|
|
18513
|
-
}
|
|
18514
|
-
async function verifyV2Envelope(envelope, linkOrigin, options) {
|
|
18515
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
18516
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
18517
|
-
}
|
|
18518
|
-
try {
|
|
18519
|
-
if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
18008
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(await open(parsed.ciphertext, parsed.key32));
|
|
18009
|
+
const value = JSON.parse(encoded);
|
|
18010
|
+
if (canonicalize2(value) !== encoded) throw new Error("non-canonical envelope");
|
|
18011
|
+
envelope = shareEnvelopeV3Schema.parse(value);
|
|
18520
18012
|
} catch {
|
|
18521
|
-
throw new ShareReceiveError("
|
|
18522
|
-
}
|
|
18523
|
-
const expiry = Date.parse(envelope.expiry);
|
|
18524
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
18525
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
18526
|
-
if (envelope.authorizationTarget.kind === "policy") {
|
|
18527
|
-
if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "an external policy authority is required");
|
|
18528
|
-
let evidence;
|
|
18529
|
-
try {
|
|
18530
|
-
evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
|
|
18531
|
-
} catch {
|
|
18532
|
-
evidence = void 0;
|
|
18533
|
-
}
|
|
18534
|
-
if (evidence === void 0 || !policyEvidenceMatches(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "share policy authority evidence is not bound to the envelope");
|
|
18535
|
-
if (options.trustedSignerDid !== void 0 && (evidence.signerDid !== options.trustedSignerDid || envelope.signature.signerDid !== options.trustedSignerDid)) {
|
|
18536
|
-
throw new ShareReceiveError("signature-invalid", "share signer is not trusted");
|
|
18537
|
-
}
|
|
18538
|
-
} else if (envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
|
|
18539
|
-
throw new ShareReceiveError("capability-invalid", "recipient authorization does not match the signed recipient");
|
|
18540
|
-
} else {
|
|
18541
|
-
if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
|
|
18542
|
-
}
|
|
18543
|
-
}
|
|
18544
|
-
async function verifyV3Envelope(envelope, linkOrigin, options) {
|
|
18545
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
18546
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
18013
|
+
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
18547
18014
|
}
|
|
18548
18015
|
try {
|
|
18549
18016
|
if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
|
|
18550
18017
|
} catch {
|
|
18551
18018
|
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
18552
18019
|
}
|
|
18020
|
+
options.signal?.throwIfAborted();
|
|
18553
18021
|
const expiry = Date.parse(envelope.expiry);
|
|
18554
18022
|
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
18555
18023
|
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
18556
|
-
|
|
18557
|
-
|
|
18558
|
-
if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
|
|
18559
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
18560
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
18561
|
-
}
|
|
18562
|
-
throw new ShareReceiveError("unsupported-target", "share policy target is not supported", { reason: "policy-target" });
|
|
18563
|
-
}
|
|
18564
|
-
async function verifyBearerEnvelope(envelope, linkOrigin, options = {}) {
|
|
18565
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
18566
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
18567
|
-
}
|
|
18568
|
-
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" });
|
|
18569
|
-
if (envelope.target.resource.kind !== "exact") throw new ShareReceiveError("unsupported-target", "this receive path only handles exact resources", { reason: "prefix-resource" });
|
|
18570
|
-
const expiry = Date.parse(envelope.expiry);
|
|
18571
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
18572
|
-
try {
|
|
18573
|
-
if (!await verifyEnvelope(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
18574
|
-
} catch {
|
|
18575
|
-
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
18576
|
-
}
|
|
18577
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
18578
|
-
const capability = checkBearerDelegation(envelope, options.now === void 0 ? {} : { now: options.now });
|
|
18579
|
-
if (!capability.ok) throw new ShareReceiveError("capability-invalid", "share delegation does not authorize the target");
|
|
18580
|
-
}
|
|
18581
|
-
async function verifyResolved(envelope, linkOrigin, options) {
|
|
18582
|
-
await verifyBearerEnvelope(envelope, linkOrigin, options);
|
|
18024
|
+
options.onResolvedAddressedEnvelope?.(envelope, parsed.ciphertextCid);
|
|
18025
|
+
return { envelope, origin: url.origin, cid: parsed.ciphertextCid };
|
|
18583
18026
|
}
|
|
18584
18027
|
async function inspectShare(link2, options = {}) {
|
|
18585
|
-
const resolved = await
|
|
18586
|
-
|
|
18587
|
-
else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
18588
|
-
else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
18589
|
-
else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
18590
|
-
if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18591
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
|
|
18028
|
+
const resolved = await resolvePolicyShare(link2, options);
|
|
18029
|
+
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
|
|
18592
18030
|
}
|
|
18593
18031
|
async function receiveShare(link2, options = {}) {
|
|
18594
|
-
const resolved = await
|
|
18595
|
-
|
|
18596
|
-
await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
18597
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18598
|
-
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
18599
|
-
}
|
|
18600
|
-
if (resolved.envelope.version === 2) {
|
|
18601
|
-
await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
18602
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18603
|
-
const method = resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
|
|
18604
|
-
if (options.authorization === void 0) return { state: "authorization-required", method };
|
|
18605
|
-
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 } });
|
|
18606
|
-
if (result.state === "authorization-required") return result;
|
|
18607
|
-
if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "share authorization was denied");
|
|
18608
|
-
if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
|
|
18609
|
-
throw new ShareReceiveError("content-integrity-failed", "share authorization returned an unsigned content result");
|
|
18610
|
-
}
|
|
18611
|
-
const authorized = result.value;
|
|
18612
|
-
try {
|
|
18613
|
-
if (!await options.authorization.verifyResult({ envelope: resolved.envelope, value: authorized, proof: authorized.proof })) throw new Error("node proof");
|
|
18614
|
-
} catch {
|
|
18615
|
-
throw new ShareReceiveError("content-integrity-failed", "node authorization proof is invalid");
|
|
18616
|
-
}
|
|
18617
|
-
const bytes4 = authorized.bytes.slice();
|
|
18618
|
-
const maxBytes = options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES;
|
|
18619
|
-
if (bytes4.byteLength > maxBytes) throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
18620
|
-
const digestBytes3 = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes4));
|
|
18621
|
-
const bodyDigest = toBase64Url(digestBytes3);
|
|
18622
|
-
if (authorized.bodyDigest !== bodyDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content digest does not match");
|
|
18623
|
-
if (authorized.contentSourceDigest !== resolved.envelope.contentSourceDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content source does not match");
|
|
18624
|
-
const binding = authorized.binding;
|
|
18625
|
-
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");
|
|
18626
|
-
if (resolved.envelope.metadata.byteLength !== void 0 && resolved.envelope.metadata.byteLength !== bytes4.byteLength) {
|
|
18627
|
-
throw new ShareReceiveError("content-integrity-failed", "authorized content length does not match");
|
|
18628
|
-
}
|
|
18629
|
-
let text2;
|
|
18630
|
-
try {
|
|
18631
|
-
text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes4);
|
|
18632
|
-
} catch {
|
|
18633
|
-
}
|
|
18634
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes4, ...text2 === void 0 ? {} : { text: text2 } };
|
|
18635
|
-
}
|
|
18636
|
-
if (resolved.envelope.authorizationTarget.kind === "policy") {
|
|
18637
|
-
await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
|
|
18638
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18639
|
-
return { state: "authorization-required", method: "email-claim" };
|
|
18640
|
-
}
|
|
18641
|
-
await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
18642
|
-
let bytes3 = new Uint8Array();
|
|
18643
|
-
if (resolved.envelope.content !== void 0) {
|
|
18644
|
-
bytes3 = await registryFetcher(
|
|
18645
|
-
options,
|
|
18646
|
-
(options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES) + CONTENT_SEALED_OVERHEAD,
|
|
18647
|
-
"max-bytes-exceeded",
|
|
18648
|
-
"content"
|
|
18649
|
-
)({ origin: resolved.origin, cid: resolved.envelope.content.cid });
|
|
18650
|
-
if (await computeCid(bytes3) !== resolved.envelope.content.cid) throw new ShareReceiveError("content-integrity-failed", "shared content CID does not match");
|
|
18651
|
-
const key = fromBase64Url(resolved.envelope.content.key);
|
|
18652
|
-
try {
|
|
18653
|
-
bytes3 = await open(bytes3, key);
|
|
18654
|
-
} catch {
|
|
18655
|
-
throw new ShareReceiveError("content-integrity-failed", "shared content could not be opened");
|
|
18656
|
-
} finally {
|
|
18657
|
-
key.fill(0);
|
|
18658
|
-
}
|
|
18659
|
-
if (bytes3.byteLength > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) {
|
|
18660
|
-
throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
18661
|
-
}
|
|
18662
|
-
}
|
|
18663
|
-
let text;
|
|
18664
|
-
if (bytes3.byteLength !== 0) {
|
|
18665
|
-
try {
|
|
18666
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
|
|
18667
|
-
} catch {
|
|
18668
|
-
}
|
|
18669
|
-
}
|
|
18670
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes3, ...text === void 0 ? {} : { text } };
|
|
18032
|
+
const resolved = await resolvePolicyShare(link2, options);
|
|
18033
|
+
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
18671
18034
|
}
|
|
18672
18035
|
function redactPublishedShare(result) {
|
|
18673
18036
|
return {
|
|
18674
18037
|
protocol: "tinycloud-share",
|
|
18675
18038
|
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
18676
|
-
link: {
|
|
18039
|
+
link: { ...result.link },
|
|
18677
18040
|
metadata: {
|
|
18678
18041
|
protocol: "tinycloud-share",
|
|
18679
18042
|
version: 1,
|
|
@@ -18683,242 +18046,9 @@ function redactPublishedShare(result) {
|
|
|
18683
18046
|
resource: { ...result.metadata.resource },
|
|
18684
18047
|
actions: [...result.metadata.actions],
|
|
18685
18048
|
expiresAt: result.metadata.expiresAt,
|
|
18686
|
-
display: { ...result.metadata.display }
|
|
18687
|
-
...result.metadata.content === void 0 ? {} : { content: { cid: result.metadata.content.cid } }
|
|
18688
|
-
},
|
|
18689
|
-
registryDeleteAfter: result.registryDeleteAfter
|
|
18690
|
-
};
|
|
18691
|
-
}
|
|
18692
|
-
function assertSafeFilename(filename) {
|
|
18693
|
-
if (filename.length === 0 || filename === "." || filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(filename)) {
|
|
18694
|
-
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
18695
|
-
}
|
|
18696
|
-
}
|
|
18697
|
-
function assertOrigin(origin, label) {
|
|
18698
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
18699
|
-
throw new SharePublishError("invalid-argument", `${label} must be a canonical HTTPS origin`);
|
|
18700
|
-
}
|
|
18701
|
-
}
|
|
18702
|
-
async function snapshotSource(source, limit) {
|
|
18703
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
18704
|
-
throw new SharePublishError("invalid-argument", "maxBytes must be a positive safe integer");
|
|
18705
|
-
}
|
|
18706
|
-
if (source instanceof Uint8Array) {
|
|
18707
|
-
if (source.byteLength > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
18708
|
-
return source.slice();
|
|
18709
|
-
}
|
|
18710
|
-
const chunks = [];
|
|
18711
|
-
let total = 0;
|
|
18712
|
-
try {
|
|
18713
|
-
for await (const chunk of source) {
|
|
18714
|
-
if (!(chunk instanceof Uint8Array)) throw new SharePublishError("invalid-argument", "share source yielded invalid bytes");
|
|
18715
|
-
total += chunk.byteLength;
|
|
18716
|
-
if (total > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
18717
|
-
chunks.push(chunk);
|
|
18718
|
-
}
|
|
18719
|
-
} catch (error) {
|
|
18720
|
-
if (error instanceof SharePublishError) throw error;
|
|
18721
|
-
throw new SharePublishError("upload-failed", "share source could not be read");
|
|
18722
|
-
}
|
|
18723
|
-
const bytes3 = new Uint8Array(total);
|
|
18724
|
-
let offset = 0;
|
|
18725
|
-
for (const chunk of chunks) {
|
|
18726
|
-
bytes3.set(chunk, offset);
|
|
18727
|
-
offset += chunk.byteLength;
|
|
18728
|
-
}
|
|
18729
|
-
return bytes3;
|
|
18730
|
-
}
|
|
18731
|
-
function assertTarget(target) {
|
|
18732
|
-
if (target.kind !== "bearer") {
|
|
18733
|
-
throw new SharePublishError("unsupported-target", "this release publishes bearer shares only");
|
|
18734
|
-
}
|
|
18735
|
-
}
|
|
18736
|
-
function assertRegistryUrl(value, allowInsecure) {
|
|
18737
|
-
let url;
|
|
18738
|
-
try {
|
|
18739
|
-
url = new URL(value);
|
|
18740
|
-
} catch {
|
|
18741
|
-
throw new SharePublishError("invalid-argument", "registry URL is invalid");
|
|
18742
|
-
}
|
|
18743
|
-
if (url.username || url.password || url.search || url.hash) throw new SharePublishError("invalid-argument", "registry URL must not contain credentials, query, or fragment");
|
|
18744
|
-
if (!allowInsecure && url.protocol !== "https:") throw new SharePublishError("invalid-argument", "production registry uploads require HTTPS");
|
|
18745
|
-
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");
|
|
18746
|
-
return url;
|
|
18747
|
-
}
|
|
18748
|
-
async function defaultUpload(options, input) {
|
|
18749
|
-
if (options.registryBaseUrl === void 0) throw new SharePublishError("upload-auth-required", "an authenticated share registry is required");
|
|
18750
|
-
const base33 = assertRegistryUrl(options.registryBaseUrl, options.allowInsecureRegistry === true).toString().replace(/\/$/, "");
|
|
18751
|
-
const localInsecure = options.allowInsecureRegistry === true && (new URL(base33).hostname === "127.0.0.1" || new URL(base33).hostname === "localhost");
|
|
18752
|
-
if (options.authorizeUpload === void 0 && !localInsecure) {
|
|
18753
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization is required");
|
|
18754
|
-
}
|
|
18755
|
-
let authorization;
|
|
18756
|
-
if (options.authorizeUpload !== void 0) {
|
|
18757
|
-
try {
|
|
18758
|
-
authorization = await options.authorizeUpload(input);
|
|
18759
|
-
} catch {
|
|
18760
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
18761
|
-
}
|
|
18762
|
-
}
|
|
18763
|
-
if (authorization !== void 0 && options.authorizationOrigin !== void 0) {
|
|
18764
|
-
let authorizationUrl;
|
|
18765
|
-
try {
|
|
18766
|
-
authorizationUrl = new URL(options.authorizationOrigin);
|
|
18767
|
-
} catch {
|
|
18768
|
-
throw new SharePublishError("invalid-argument", "upload authorization origin is invalid");
|
|
18769
|
-
}
|
|
18770
|
-
if (authorizationUrl.origin !== new URL(base33).origin) throw new SharePublishError("upload-auth-required", "upload authorization is scoped to another origin");
|
|
18771
|
-
}
|
|
18772
|
-
const headers = new Headers({
|
|
18773
|
-
"content-type": "application/vnd.ipld.raw",
|
|
18774
|
-
"if-none-match": "*",
|
|
18775
|
-
"x-delete-after": input.deleteAfter
|
|
18776
|
-
});
|
|
18777
|
-
if (typeof authorization === "string") {
|
|
18778
|
-
headers.set("x-tinycloud-upload-attestation", authorization);
|
|
18779
|
-
try {
|
|
18780
|
-
const attestation = JSON.parse(authorization);
|
|
18781
|
-
if (attestation.retention === void 0) throw new Error("retention");
|
|
18782
|
-
headers.set("x-tinycloud-retention", canonicalize2(attestation.retention));
|
|
18783
|
-
} catch {
|
|
18784
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
18785
|
-
}
|
|
18786
|
-
} else if (authorization !== void 0) {
|
|
18787
|
-
new Headers(authorization).forEach((value, key) => headers.set(key, value));
|
|
18788
|
-
}
|
|
18789
|
-
let response;
|
|
18790
|
-
try {
|
|
18791
|
-
response = await (options.fetchFn ?? globalThis.fetch)(`${base33}/blobs`, {
|
|
18792
|
-
method: "POST",
|
|
18793
|
-
headers,
|
|
18794
|
-
body: input.blob,
|
|
18795
|
-
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
18796
|
-
redirect: "error",
|
|
18797
|
-
referrerPolicy: "no-referrer"
|
|
18798
|
-
});
|
|
18799
|
-
} catch {
|
|
18800
|
-
throw new SharePublishError("upload-failed", "share registry is unavailable");
|
|
18801
|
-
}
|
|
18802
|
-
if (response.status === 401 || response.status === 403) throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
18803
|
-
if (!response.ok) throw new SharePublishError("upload-failed", "share registry rejected the encrypted blob");
|
|
18804
|
-
let body;
|
|
18805
|
-
try {
|
|
18806
|
-
body = await response.json();
|
|
18807
|
-
} catch {
|
|
18808
|
-
throw new SharePublishError("upload-failed", "share registry returned an invalid response");
|
|
18809
|
-
}
|
|
18810
|
-
const record2 = typeof body === "object" && body !== null ? body : {};
|
|
18811
|
-
if (record2.cid !== input.cid || typeof record2.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share registry returned an unexpected CID or retention value");
|
|
18812
|
-
return { cid: input.cid, deleteAfter: record2.deleteAfter };
|
|
18813
|
-
}
|
|
18814
|
-
async function uploadShareBlob(options, input) {
|
|
18815
|
-
const result = await (options.uploadBlob ?? ((value) => defaultUpload(options, value)))(input);
|
|
18816
|
-
if (result.cid !== input.cid || typeof result.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share uploader returned an invalid result");
|
|
18817
|
-
return result;
|
|
18818
|
-
}
|
|
18819
|
-
async function publishShare(options) {
|
|
18820
|
-
const target = options.target ?? { kind: "bearer" };
|
|
18821
|
-
assertTarget(target);
|
|
18822
|
-
assertSafeFilename(options.filename);
|
|
18823
|
-
assertOrigin(options.origin, "share origin");
|
|
18824
|
-
if (options.mediaType !== void 0 && options.mediaType.length === 0) throw new SharePublishError("invalid-argument", "mediaType must not be empty");
|
|
18825
|
-
const maxBytes = options.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
18826
|
-
if (maxBytes > SHARE_CONTENT_LIMIT) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the maximum 100 MiB limit");
|
|
18827
|
-
const content = await snapshotSource(options.source, maxBytes);
|
|
18828
|
-
if (content.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
18829
|
-
if (options.allowBinary !== true) {
|
|
18830
|
-
try {
|
|
18831
|
-
new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
18832
|
-
} catch {
|
|
18833
|
-
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
18834
|
-
}
|
|
18835
|
-
}
|
|
18836
|
-
const nowMs2 = options.now?.() ?? Date.now();
|
|
18837
|
-
const expiresAt = options.expiresAt ?? new Date(nowMs2 + DEFAULT_SHARE_LIFETIME_MS);
|
|
18838
|
-
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs2) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
18839
|
-
const expiry = expiresAt.toISOString();
|
|
18840
|
-
const nodeAudience = options.nodeAudience ?? "did:web:node.tinycloud.xyz";
|
|
18841
|
-
const spaceId = options.spaceId ?? "bearer";
|
|
18842
|
-
const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
|
|
18843
|
-
const path = `shares/${shareId}/${options.filename}`;
|
|
18844
|
-
const sessionPrivateKey = ed25519.utils.randomPrivateKey();
|
|
18845
|
-
const senderPrivateKey = ed25519.utils.randomPrivateKey();
|
|
18846
|
-
let contentKey;
|
|
18847
|
-
let envelopeKey;
|
|
18848
|
-
try {
|
|
18849
|
-
const sessionPublicKey = ed25519.getPublicKey(sessionPrivateKey);
|
|
18850
|
-
const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
|
|
18851
|
-
const delegation = mintBearerDelegation({
|
|
18852
|
-
issuerPrivateKey: senderPrivateKey,
|
|
18853
|
-
audienceDid: sessionDid,
|
|
18854
|
-
resourceUri: bearerResourceUri(options.origin, spaceId, path),
|
|
18855
|
-
expiresAtSeconds: Math.ceil(expiresAt.getTime() / 1e3)
|
|
18856
|
-
});
|
|
18857
|
-
contentKey = generateKey();
|
|
18858
|
-
const sealedContent = await seal(content, contentKey);
|
|
18859
|
-
const unsigned = {
|
|
18860
|
-
version: 1,
|
|
18861
|
-
shareId,
|
|
18862
|
-
delegation,
|
|
18863
|
-
authorizationTarget: {
|
|
18864
|
-
kind: "bearerKey",
|
|
18865
|
-
sessionJwk: { kty: "OKP", crv: "Ed25519", x: toBase64Url(sessionPublicKey), d: toBase64Url(sessionPrivateKey) }
|
|
18866
|
-
},
|
|
18867
|
-
target: { origin: options.origin, nodeAudience, spaceId, resource: { kind: "exact", path } },
|
|
18868
|
-
display: { filename: options.filename },
|
|
18869
|
-
expiry,
|
|
18870
|
-
content: { cid: sealedContent.cid, key: toBase64Url(contentKey) }
|
|
18871
|
-
};
|
|
18872
|
-
const envelope = signEnvelope(unsigned, senderPrivateKey);
|
|
18873
|
-
envelopeKey = generateKey();
|
|
18874
|
-
const sealedEnvelope = await seal(new TextEncoder().encode(canonicalize2(envelope)), envelopeKey);
|
|
18875
|
-
let inlineUrl;
|
|
18876
|
-
if (options.inline === true) {
|
|
18877
|
-
try {
|
|
18878
|
-
inlineUrl = await encodeInlineShareUrl({ origin: options.origin, ciphertext: sealedEnvelope.blob, key32: envelopeKey });
|
|
18879
|
-
} catch (error) {
|
|
18880
|
-
throw new SharePublishError("inline-too-large", error instanceof Error ? error.message : "inline share is too large");
|
|
18881
|
-
}
|
|
18882
|
-
}
|
|
18883
|
-
const contentUpload = await uploadShareBlob(options, { blob: sealedContent.blob, cid: sealedContent.cid, deleteAfter: expiry, contentLength: sealedContent.blob.byteLength });
|
|
18884
|
-
let url;
|
|
18885
|
-
let kind;
|
|
18886
|
-
let retention = contentUpload.deleteAfter;
|
|
18887
|
-
if (options.inline === true) {
|
|
18888
|
-
if (inlineUrl === void 0) throw new SharePublishError("inline-too-large", "inline share is too large");
|
|
18889
|
-
url = inlineUrl;
|
|
18890
|
-
kind = "inline";
|
|
18891
|
-
} else {
|
|
18892
|
-
const envelopeUpload = await uploadShareBlob(options, { blob: sealedEnvelope.blob, cid: sealedEnvelope.cid, deleteAfter: expiry, contentLength: sealedEnvelope.blob.byteLength });
|
|
18893
|
-
url = encodeShareUrl({ origin: options.origin, ciphertextCid: envelopeUpload.cid, key32: envelopeKey });
|
|
18894
|
-
kind = "compact";
|
|
18895
|
-
retention = envelopeUpload.deleteAfter;
|
|
18049
|
+
display: { ...result.metadata.display }
|
|
18896
18050
|
}
|
|
18897
|
-
|
|
18898
|
-
protocol: "tinycloud-share",
|
|
18899
|
-
version: 1,
|
|
18900
|
-
shareId,
|
|
18901
|
-
origin: options.origin,
|
|
18902
|
-
target: { kind: "bearer", origin: options.origin, nodeAudience, spaceId },
|
|
18903
|
-
resource: { kind: "exact", path },
|
|
18904
|
-
actions: ["read"],
|
|
18905
|
-
expiresAt: expiry,
|
|
18906
|
-
display: { filename: options.filename },
|
|
18907
|
-
content: { cid: sealedContent.cid }
|
|
18908
|
-
};
|
|
18909
|
-
const result = { protocol: "tinycloud-share", version: SHARE_PUBLISH_RESULT_VERSION, link: { kind, cid: sealedEnvelope.cid }, metadata, registryDeleteAfter: retention };
|
|
18910
|
-
Object.defineProperty(result, "toJSON", {
|
|
18911
|
-
enumerable: false,
|
|
18912
|
-
value: () => redactPublishedShare(result)
|
|
18913
|
-
});
|
|
18914
|
-
Object.defineProperty(result, "url", { enumerable: false, value: url });
|
|
18915
|
-
return result;
|
|
18916
|
-
} finally {
|
|
18917
|
-
sessionPrivateKey.fill(0);
|
|
18918
|
-
senderPrivateKey.fill(0);
|
|
18919
|
-
contentKey?.fill(0);
|
|
18920
|
-
envelopeKey?.fill(0);
|
|
18921
|
-
}
|
|
18051
|
+
};
|
|
18922
18052
|
}
|
|
18923
18053
|
function authorizationMethodForTarget(target) {
|
|
18924
18054
|
if (target.kind === "recipientDid") return "openkey-device";
|
|
@@ -19218,8 +18348,8 @@ function targetAuthorizationMethod(target) {
|
|
|
19218
18348
|
}
|
|
19219
18349
|
async function publishTargetShare(input) {
|
|
19220
18350
|
const target = normalizeShareTarget(input.target);
|
|
19221
|
-
if (target.kind === "bearer") return publishShare(input);
|
|
19222
18351
|
if (input.targetAdapter === void 0) {
|
|
18352
|
+
if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
|
|
19223
18353
|
return {
|
|
19224
18354
|
state: "authorization-required",
|
|
19225
18355
|
method: targetAuthorizationMethod(target)
|
|
@@ -19229,9 +18359,9 @@ async function publishTargetShare(input) {
|
|
|
19229
18359
|
const chunks = [];
|
|
19230
18360
|
let size2 = 0;
|
|
19231
18361
|
for await (const chunk of input.source) {
|
|
19232
|
-
if (!(chunk instanceof Uint8Array)) throw new TypeError("
|
|
18362
|
+
if (!(chunk instanceof Uint8Array)) throw new TypeError("share publication source yielded invalid bytes");
|
|
19233
18363
|
size2 += chunk.byteLength;
|
|
19234
|
-
if (size2 > (input.maxBytes ??
|
|
18364
|
+
if (size2 > (input.maxBytes ?? SHARE_CONTENT_LIMIT)) throw new SharePublishError("max-bytes-exceeded", "share publication exceeds maxBytes");
|
|
19235
18365
|
chunks.push(chunk.slice());
|
|
19236
18366
|
}
|
|
19237
18367
|
const bytes3 = new Uint8Array(size2);
|
|
@@ -19242,15 +18372,29 @@ async function publishTargetShare(input) {
|
|
|
19242
18372
|
}
|
|
19243
18373
|
return bytes3;
|
|
19244
18374
|
})();
|
|
18375
|
+
if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
18376
|
+
if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
|
|
18377
|
+
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
18378
|
+
}
|
|
18379
|
+
if (target.kind === "bearer" && input.allowBinary !== true) {
|
|
18380
|
+
try {
|
|
18381
|
+
new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
18382
|
+
} catch {
|
|
18383
|
+
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
18384
|
+
}
|
|
18385
|
+
}
|
|
19245
18386
|
const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
|
|
19246
18387
|
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
19247
18388
|
let totalBytes = 0;
|
|
19248
18389
|
for (const file of files) {
|
|
19249
18390
|
totalBytes += file.bytes.byteLength;
|
|
19250
18391
|
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
19251
|
-
throw new SharePublishError("max-bytes-exceeded", "
|
|
18392
|
+
throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
|
|
19252
18393
|
}
|
|
19253
18394
|
}
|
|
18395
|
+
const nowMs2 = input.now?.() ?? Date.now();
|
|
18396
|
+
const expiresAt = input.expiresAt ?? new Date(nowMs2 + DEFAULT_SHARE_LIFETIME_MS);
|
|
18397
|
+
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs2) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
19254
18398
|
return input.targetAdapter.publish({
|
|
19255
18399
|
source,
|
|
19256
18400
|
filename: input.filename,
|
|
@@ -19259,18 +18403,8 @@ async function publishTargetShare(input) {
|
|
|
19259
18403
|
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
19260
18404
|
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
19261
18405
|
target,
|
|
19262
|
-
expiresAt
|
|
18406
|
+
expiresAt,
|
|
19263
18407
|
origin: input.origin,
|
|
19264
|
-
...input.inline === void 0 ? {} : { inline: input.inline },
|
|
19265
|
-
upload: {
|
|
19266
|
-
...input.registryBaseUrl === void 0 ? {} : { registryBaseUrl: input.registryBaseUrl },
|
|
19267
|
-
...input.fetchFn === void 0 ? {} : { fetchFn: input.fetchFn },
|
|
19268
|
-
...input.authorizeUpload === void 0 ? {} : { authorizeUpload: input.authorizeUpload },
|
|
19269
|
-
...input.authorizationOrigin === void 0 ? {} : { authorizationOrigin: input.authorizationOrigin },
|
|
19270
|
-
...input.credentials === void 0 ? {} : { credentials: input.credentials },
|
|
19271
|
-
...input.allowInsecureRegistry === void 0 ? {} : { allowInsecureRegistry: input.allowInsecureRegistry },
|
|
19272
|
-
...input.uploadBlob === void 0 ? {} : { uploadBlob: input.uploadBlob }
|
|
19273
|
-
},
|
|
19274
18408
|
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
19275
18409
|
});
|
|
19276
18410
|
}
|
|
@@ -19459,8 +18593,8 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
|
|
|
19459
18593
|
...result.metadata.display.filename === void 0 ? {} : { filename: result.metadata.display.filename }
|
|
19460
18594
|
};
|
|
19461
18595
|
}
|
|
19462
|
-
function recipientMatchesShareRecord(
|
|
19463
|
-
const matcher =
|
|
18596
|
+
function recipientMatchesShareRecord(record, recipient) {
|
|
18597
|
+
const matcher = record.recipientMatcher;
|
|
19464
18598
|
if (matcher.kind === "bearer" || matcher.kind === "recipientDid") return false;
|
|
19465
18599
|
if (matcher.kind === "exactEmail") return matcher.value === recipient;
|
|
19466
18600
|
const at = recipient.lastIndexOf("@");
|
|
@@ -19497,65 +18631,80 @@ async function defaultIdempotencyKey(shareId, recipient) {
|
|
|
19497
18631
|
));
|
|
19498
18632
|
return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
|
|
19499
18633
|
}
|
|
19500
|
-
function targetKind2(
|
|
19501
|
-
if (
|
|
19502
|
-
return
|
|
18634
|
+
function targetKind2(record) {
|
|
18635
|
+
if (record.targetKind !== void 0) return record.targetKind;
|
|
18636
|
+
return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
|
|
18637
|
+
}
|
|
18638
|
+
function policyNodeAudience(record) {
|
|
18639
|
+
const envelope = record.deliveryMaterial?.envelope;
|
|
18640
|
+
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
|
|
18641
|
+
const binding = envelope.attestedEnforcerBinding;
|
|
18642
|
+
if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
|
|
18643
|
+
const nodeAudience = binding.nodeAudience;
|
|
18644
|
+
return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
|
|
19503
18645
|
}
|
|
19504
18646
|
async function revokeShare(input) {
|
|
19505
18647
|
const target = targetKind2(input.record);
|
|
19506
|
-
if (target === "bearer") return { state: "retention-only", target, reason: "bearer-capability-cannot-be-revoked" };
|
|
19507
18648
|
if (input.adapter === void 0) return { state: "unsupported", target, reason: "node revocation authority is required", code: "unsupported-target" };
|
|
19508
18649
|
const scope = input.scope ?? "direct";
|
|
19509
18650
|
const delegationCid = scope === "ancestor" ? input.record.ownerDelegationCid : input.record.enforcementDelegationCid;
|
|
19510
18651
|
if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
|
|
19511
|
-
|
|
18652
|
+
if (target === "bearer") {
|
|
18653
|
+
if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
|
|
18654
|
+
await input.adapter.revokeDelegation({ delegationCid, scope });
|
|
18655
|
+
} else {
|
|
18656
|
+
if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
|
|
18657
|
+
if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
|
|
18658
|
+
await input.adapter.revokePolicyRoot({
|
|
18659
|
+
rootCid: delegationCid,
|
|
18660
|
+
targetRole: scope === "ancestor" ? "policy-authority" : "policy-enforcement",
|
|
18661
|
+
ownerDid: input.record.ownerDid,
|
|
18662
|
+
nodeOrigin: input.record.target.origin,
|
|
18663
|
+
nodeAudience: policyNodeAudience(input.record)
|
|
18664
|
+
});
|
|
18665
|
+
}
|
|
19512
18666
|
const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
19513
18667
|
if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
|
|
19514
18668
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
19515
18669
|
}
|
|
19516
|
-
function redactRecord(
|
|
19517
|
-
const matcher =
|
|
19518
|
-
const revealedLink = revealLink ? link2 ??
|
|
18670
|
+
function redactRecord(record, revealLink, link2) {
|
|
18671
|
+
const matcher = record.recipientMatcher;
|
|
18672
|
+
const revealedLink = revealLink ? link2 ?? record.link : void 0;
|
|
19519
18673
|
return {
|
|
19520
|
-
shareId:
|
|
18674
|
+
shareId: record.shareId,
|
|
19521
18675
|
target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
|
|
19522
18676
|
...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
|
|
19523
|
-
expiresAt:
|
|
19524
|
-
revoked:
|
|
18677
|
+
expiresAt: record.expiresAt,
|
|
18678
|
+
revoked: record.revokedAt !== void 0,
|
|
19525
18679
|
...revealedLink === void 0 ? {} : { link: revealedLink }
|
|
19526
18680
|
};
|
|
19527
18681
|
}
|
|
19528
18682
|
async function listShares(storage) {
|
|
19529
18683
|
const records = await storage.list();
|
|
19530
|
-
return records.map((
|
|
18684
|
+
return records.map((record) => redactRecord(record, false));
|
|
19531
18685
|
}
|
|
19532
18686
|
async function showShare(input) {
|
|
19533
|
-
const
|
|
19534
|
-
if (
|
|
19535
|
-
return redactRecord(
|
|
19536
|
-
}
|
|
19537
|
-
function isLegacyShareLink(value) {
|
|
19538
|
-
return value.startsWith("tc1:");
|
|
19539
|
-
}
|
|
19540
|
-
async function receiveLegacyShare(link2, reader) {
|
|
19541
|
-
if (!isLegacyShareLink(link2)) throw new Error("legacy link must use tc1:");
|
|
19542
|
-
return reader.read(link2);
|
|
18687
|
+
const record = await input.storage.get(input.shareId);
|
|
18688
|
+
if (record === void 0) throw new Error("share not found");
|
|
18689
|
+
return redactRecord(record, input.revealLink === true, input.link);
|
|
19543
18690
|
}
|
|
19544
|
-
|
|
19545
|
-
const
|
|
19546
|
-
|
|
18691
|
+
function parseNativeShareUrl(value) {
|
|
18692
|
+
const url = new URL(value);
|
|
18693
|
+
if (url.search || url.pathname !== "/viewer") throw new TypeError("native shares must carry tc1 only in the /viewer URL fragment");
|
|
18694
|
+
const fragment = new URLSearchParams(url.hash.slice(1));
|
|
18695
|
+
const token = fragment.get(NATIVE_SHARE_FRAGMENT_PARAMETER);
|
|
18696
|
+
if (!token || fragment.size !== 1) throw new TypeError("missing native share fragment");
|
|
18697
|
+
return token;
|
|
19547
18698
|
}
|
|
19548
|
-
var __defProp2, __export2, external_exports2, util2, objectUtil2, ZodParsedType2, getParsedType2, ZodIssueCode2, quotelessJson2, ZodError2, errorMap2, en_default2, overrideErrorMap2, makeIssue2, EMPTY_PATH2, ParseStatus2, INVALID2, DIRTY2, OK2, isAborted2, isDirty2, isValid2, isAsync2, errorUtil2, ParseInputLazyPath2, handleResult2, ZodType2, cuidRegex2, cuid2Regex2, ulidRegex2, uuidRegex2, nanoidRegex2, jwtRegex2, durationRegex2, emailRegex2, _emojiRegex2, emojiRegex2, ipv4Regex2, ipv4CidrRegex2, ipv6Regex2, ipv6CidrRegex2, base64Regex2, base64urlRegex2, dateRegexSource2, dateRegex2, ZodString2, ZodNumber2, ZodBigInt2, ZodBoolean2, ZodDate2, ZodSymbol2, ZodUndefined2, ZodNull2, ZodAny2, ZodUnknown2, ZodNever2, ZodVoid2, ZodArray2, ZodObject2, ZodUnion2, getDiscriminator2, ZodDiscriminatedUnion2, ZodIntersection2, ZodTuple2, ZodRecord2, ZodMap2, ZodSet2, ZodFunction2, ZodLazy2, ZodLiteral2, ZodEnum2, ZodNativeEnum2, ZodPromise2, ZodEffects2, ZodOptional2, ZodNullable2, ZodDefault2, ZodCatch2, ZodNaN2, BRAND2, ZodBranded2, ZodPipeline2, ZodReadonly2, late2, ZodFirstPartyTypeKind2, instanceOfType2, stringType2, numberType2, nanType2, bigIntType2, booleanType2, dateType2, symbolType2, undefinedType2, nullType2, anyType2, unknownType2, neverType2, voidType2, arrayType2, objectType2, strictObjectType2, unionType2, discriminatedUnionType2, intersectionType2, tupleType2, recordType2, mapType2, setType2, functionType2, lazyType2, literalType2, enumType2, nativeEnumType2, promiseType2, effectsType2, optionalType2, nullableType2, preprocessType2, pipelineType2, ostring2, onumber2, oboolean2, coerce2, NEVER2, empty, src, _brrp__multiformats_scope_baseX, base_x_default, Encoder, Decoder, ComposedDecoder, Codec, base32, base32upper, base32pad, base32padupper, base32hex, base32hexupper, base32hexpad, base32hexpadupper, base32z, base36, base36upper, base58btc, base58flickr, encode_1, MSB, REST, MSBALL, INT, decode2, MSB$1, REST$1, N1, N2, N3, N4, N5, N6, N7, N8, N9, length, varint, _brrp_varint, varint_default, Digest, cache, CID, DAG_PB_CODE, SHA_256_CODE, cidSymbol, code, SHA256_CODE, base64, base64pad, base64url, base64urlpad, ED25519_MULTICODEC_PREFIX, PUBLIC_KEY_LENGTH, base64UrlString, sessionJwkCommonFields, okpPrivateJwkSchema, ecPrivateJwkSchema, sessionJwkSchema, policyTargetSchema, bearerKeyTargetSchema, recipientDidTargetSchema, authorizationTargetSchema, resourceSelectorSchema, targetSchema, displaySchema, contentPointerSchema, signatureSchema, unsignedShareEnvelopeSchema, shareEnvelopeSchema, recipientMatcherSchema, shareActionSchema, kvContentSourceSchema, sqlContentSourceSchema, contentSourceSchema, v2TargetSchema, shareDecryptionSchema, ownerAuthoritySchema, contentMetadataSchema, unsignedShareEnvelopeV2BaseSchema, unsignedShareEnvelopeV2Schema, shareEnvelopeV2Schema, unifiedResourceSchema, unifiedEncryptionNetworkSchema, unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema, unifiedCapabilitySchema, unifiedContentSourceSchema, unifiedPolicyV1Schema, policyCredentialRequirementV1Schema, unifiedPolicyV2Schema, unifiedPolicySchema, unifiedRootSchema, attestedEnforcerBindingV2Schema, v3TargetSchema, unsignedShareEnvelopeV3BaseSchema, unsignedShareEnvelopeV3Schema, shareEnvelopeV3Schema,
|
|
18699
|
+
var __defProp2, __export2, external_exports2, util2, objectUtil2, ZodParsedType2, getParsedType2, ZodIssueCode2, quotelessJson2, ZodError2, errorMap2, en_default2, overrideErrorMap2, makeIssue2, EMPTY_PATH2, ParseStatus2, INVALID2, DIRTY2, OK2, isAborted2, isDirty2, isValid2, isAsync2, errorUtil2, ParseInputLazyPath2, handleResult2, ZodType2, cuidRegex2, cuid2Regex2, ulidRegex2, uuidRegex2, nanoidRegex2, jwtRegex2, durationRegex2, emailRegex2, _emojiRegex2, emojiRegex2, ipv4Regex2, ipv4CidrRegex2, ipv6Regex2, ipv6CidrRegex2, base64Regex2, base64urlRegex2, dateRegexSource2, dateRegex2, ZodString2, ZodNumber2, ZodBigInt2, ZodBoolean2, ZodDate2, ZodSymbol2, ZodUndefined2, ZodNull2, ZodAny2, ZodUnknown2, ZodNever2, ZodVoid2, ZodArray2, ZodObject2, ZodUnion2, getDiscriminator2, ZodDiscriminatedUnion2, ZodIntersection2, ZodTuple2, ZodRecord2, ZodMap2, ZodSet2, ZodFunction2, ZodLazy2, ZodLiteral2, ZodEnum2, ZodNativeEnum2, ZodPromise2, ZodEffects2, ZodOptional2, ZodNullable2, ZodDefault2, ZodCatch2, ZodNaN2, BRAND2, ZodBranded2, ZodPipeline2, ZodReadonly2, late2, ZodFirstPartyTypeKind2, instanceOfType2, stringType2, numberType2, nanType2, bigIntType2, booleanType2, dateType2, symbolType2, undefinedType2, nullType2, anyType2, unknownType2, neverType2, voidType2, arrayType2, objectType2, strictObjectType2, unionType2, discriminatedUnionType2, intersectionType2, tupleType2, recordType2, mapType2, setType2, functionType2, lazyType2, literalType2, enumType2, nativeEnumType2, promiseType2, effectsType2, optionalType2, nullableType2, preprocessType2, pipelineType2, ostring2, onumber2, oboolean2, coerce2, NEVER2, empty, src, _brrp__multiformats_scope_baseX, base_x_default, Encoder, Decoder, ComposedDecoder, Codec, base32, base32upper, base32pad, base32padupper, base32hex, base32hexupper, base32hexpad, base32hexpadupper, base32z, base36, base36upper, base58btc, base58flickr, encode_1, MSB, REST, MSBALL, INT, decode2, MSB$1, REST$1, N1, N2, N3, N4, N5, N6, N7, N8, N9, length, varint, _brrp_varint, varint_default, Digest, cache, CID, DAG_PB_CODE, SHA_256_CODE, cidSymbol, code, SHA256_CODE, base64, base64pad, base64url, base64urlpad, ED25519_MULTICODEC_PREFIX, PUBLIC_KEY_LENGTH, base64UrlString, sessionJwkCommonFields, okpPrivateJwkSchema, ecPrivateJwkSchema, sessionJwkSchema, policyTargetSchema, bearerKeyTargetSchema, recipientDidTargetSchema, authorizationTargetSchema, resourceSelectorSchema, targetSchema, displaySchema, contentPointerSchema, signatureSchema, unsignedShareEnvelopeSchema, shareEnvelopeSchema, recipientMatcherSchema, shareActionSchema, kvContentSourceSchema, sqlContentSourceSchema, contentSourceSchema, v2TargetSchema, shareDecryptionSchema, ownerAuthoritySchema, contentMetadataSchema, unsignedShareEnvelopeV2BaseSchema, unsignedShareEnvelopeV2Schema, shareEnvelopeV2Schema, unifiedResourceSchema, unifiedEncryptionNetworkSchema, unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema, unifiedCapabilitySchema, unifiedContentSourceSchema, unifiedPolicyV1Schema, policyCredentialRequirementV1Schema, unifiedPolicyV2Schema, unifiedPolicySchema, unifiedRootSchema, attestedEnforcerBindingV2Schema, v3TargetSchema, unsignedShareEnvelopeV3BaseSchema, unsignedShareEnvelopeV3Schema, shareEnvelopeV3Schema, ENVELOPE_AAD_LABEL, SEALED_BLOB_VERSION, AAD, KEY_LENGTH, NONCE_LENGTH, TAG_LENGTH, HEADER_LENGTH, ED25519_VERIFY_OPTS2, ENVELOPE_V3_SIGNATURE_DOMAIN, POLICY_V1_SIGNATURE_DOMAIN, POLICY_V2_SIGNATURE_DOMAIN, CONTENT_SOURCE_V1_DOMAIN, POLICY_CAPABILITY_V1_DOMAIN, NATIVE_PROJECTION_V1_DOMAIN, ATTESTED_ENFORCER_V2_DOMAIN, KEY_LENGTH2, MAX_INLINE_BYTES, SHARE_RESULT_VERSION, DEFAULT_MAX_SEALED_BLOB_BYTES, ShareReceiveError, SHARE_CONTENT_LIMIT, SHARE_PUBLISH_RESULT_VERSION, DEFAULT_SHARE_LIFETIME_MS, SharePublishError, empty2, src2, _brrp__multiformats_scope_baseX2, base_x_default2, Encoder2, Decoder2, ComposedDecoder2, Codec2, base58btc2, base58flickr2, textEncoder, base322, base32upper2, base32pad2, base32padupper2, base32hex2, base32hexupper2, base32hexpad2, base32hexpadupper2, base32z2, base362, base36upper2, encode_12, MSB2, REST2, MSBALL2, INT2, decode6, MSB$12, REST$12, N12, N22, N32, N42, N52, N62, N72, N82, N92, length2, varint2, _brrp_varint2, varint_default2, Digest2, cache2, CID2, DAG_PB_CODE2, SHA_256_CODE2, cidSymbol2, MAX_CONTENT_BYTES, ShareNotifyError, SHARE_V2_PROTOCOL, DOMAIN, PRESENTATION_DOMAIN, SESSION_DOMAIN, INVOCATION_DOMAIN, NATIVE_SHARE_FRAGMENT_PARAMETER;
|
|
19549
18700
|
var init_dist3 = __esm({
|
|
19550
18701
|
"../share-sdk/dist/index.js"() {
|
|
19551
18702
|
"use strict";
|
|
19552
18703
|
init_sha2();
|
|
19553
18704
|
init_ed25519();
|
|
19554
|
-
init_ed25519();
|
|
19555
18705
|
init_blake3();
|
|
19556
18706
|
init_ed25519();
|
|
19557
18707
|
init_sha256();
|
|
19558
|
-
init_ed25519();
|
|
19559
18708
|
__defProp2 = Object.defineProperty;
|
|
19560
18709
|
__export2 = (target, all) => {
|
|
19561
18710
|
for (var name2 in all)
|
|
@@ -19701,10 +18850,10 @@ var init_dist3 = __esm({
|
|
|
19701
18850
|
return obj[e];
|
|
19702
18851
|
});
|
|
19703
18852
|
};
|
|
19704
|
-
util22.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (
|
|
18853
|
+
util22.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object4) => {
|
|
19705
18854
|
const keys = [];
|
|
19706
|
-
for (const key in
|
|
19707
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
18855
|
+
for (const key in object4) {
|
|
18856
|
+
if (Object.prototype.hasOwnProperty.call(object4, key)) {
|
|
19708
18857
|
keys.push(key);
|
|
19709
18858
|
}
|
|
19710
18859
|
}
|
|
@@ -24162,9 +23311,6 @@ var init_dist3 = __esm({
|
|
|
24162
23311
|
}).strict();
|
|
24163
23312
|
unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
|
|
24164
23313
|
shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
|
|
24165
|
-
BEARER_READ_ABILITY = "kv/get";
|
|
24166
|
-
READ_ABILITIES = /* @__PURE__ */ new Set([BEARER_READ_ABILITY]);
|
|
24167
|
-
ED25519_VERIFY_OPTS = { zip215: false };
|
|
24168
23314
|
ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
|
|
24169
23315
|
SEALED_BLOB_VERSION = 1;
|
|
24170
23316
|
AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
|
|
@@ -24173,8 +23319,6 @@ var init_dist3 = __esm({
|
|
|
24173
23319
|
TAG_LENGTH = 16;
|
|
24174
23320
|
HEADER_LENGTH = 1;
|
|
24175
23321
|
ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
24176
|
-
ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
|
|
24177
|
-
ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
24178
23322
|
ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
24179
23323
|
POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
24180
23324
|
POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
@@ -24183,12 +23327,9 @@ var init_dist3 = __esm({
|
|
|
24183
23327
|
NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
24184
23328
|
ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
24185
23329
|
KEY_LENGTH2 = 32;
|
|
24186
|
-
INLINE_PREFIX = "#v=2&p=";
|
|
24187
23330
|
MAX_INLINE_BYTES = 256 * 1024;
|
|
24188
23331
|
SHARE_RESULT_VERSION = 1;
|
|
24189
23332
|
DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
|
|
24190
|
-
DEFAULT_MAX_CONTENT_BLOB_BYTES = DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
24191
|
-
CONTENT_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
24192
23333
|
ShareReceiveError = class extends Error {
|
|
24193
23334
|
code;
|
|
24194
23335
|
details;
|
|
@@ -24198,13 +23339,11 @@ var init_dist3 = __esm({
|
|
|
24198
23339
|
this.code = code3;
|
|
24199
23340
|
this.details = details;
|
|
24200
23341
|
}
|
|
24201
|
-
/** Machine output is deliberately code-only; diagnostics belong on stderr. */
|
|
24202
23342
|
toJSON() {
|
|
24203
23343
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: this.code } };
|
|
24204
23344
|
}
|
|
24205
23345
|
};
|
|
24206
23346
|
SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
|
|
24207
|
-
SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
24208
23347
|
SHARE_PUBLISH_RESULT_VERSION = 1;
|
|
24209
23348
|
DEFAULT_SHARE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
24210
23349
|
SharePublishError = class extends Error {
|
|
@@ -24696,6 +23835,7 @@ var init_dist3 = __esm({
|
|
|
24696
23835
|
PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
24697
23836
|
SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
24698
23837
|
INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
|
|
23838
|
+
NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
|
|
24699
23839
|
}
|
|
24700
23840
|
});
|
|
24701
23841
|
|
|
@@ -25848,16 +24988,26 @@ async function verifyLocationRecord(input) {
|
|
|
25848
24988
|
}
|
|
25849
24989
|
return false;
|
|
25850
24990
|
}
|
|
25851
|
-
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
|
|
24991
|
+
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
|
|
25852
24992
|
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
25853
|
-
const response = await fetchFn(url
|
|
24993
|
+
const response = await fetchFn(url, {
|
|
24994
|
+
redirect: "error",
|
|
24995
|
+
headers: { accept: "application/json" },
|
|
24996
|
+
...signal === void 0 ? {} : { signal }
|
|
24997
|
+
});
|
|
25854
24998
|
if (response.status === 404) {
|
|
25855
24999
|
return null;
|
|
25856
25000
|
}
|
|
25857
25001
|
if (!response.ok) {
|
|
25858
25002
|
throw new Error(`location registry returned HTTP ${response.status}`);
|
|
25859
25003
|
}
|
|
25860
|
-
const
|
|
25004
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
25005
|
+
let body;
|
|
25006
|
+
try {
|
|
25007
|
+
body = JSON.parse(text2);
|
|
25008
|
+
} catch {
|
|
25009
|
+
throw new LocationRecordValidationError("registry response is not JSON");
|
|
25010
|
+
}
|
|
25861
25011
|
if (body.record === void 0) {
|
|
25862
25012
|
throw new LocationRecordValidationError("registry response missing record");
|
|
25863
25013
|
}
|
|
@@ -26033,6 +25183,38 @@ function multiaddrToHttpUrl(input) {
|
|
|
26033
25183
|
}
|
|
26034
25184
|
return uri;
|
|
26035
25185
|
}
|
|
25186
|
+
async function boundedResponseText(response, label, maxBytes) {
|
|
25187
|
+
const declaredLength = response.headers.get("content-length");
|
|
25188
|
+
if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
|
|
25189
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
25190
|
+
}
|
|
25191
|
+
if (response.body === null) return "";
|
|
25192
|
+
const reader = response.body.getReader();
|
|
25193
|
+
const chunks = [];
|
|
25194
|
+
let total = 0;
|
|
25195
|
+
try {
|
|
25196
|
+
while (true) {
|
|
25197
|
+
const { done, value } = await reader.read();
|
|
25198
|
+
if (done) break;
|
|
25199
|
+
if (value === void 0) continue;
|
|
25200
|
+
total += value.byteLength;
|
|
25201
|
+
if (total > maxBytes) {
|
|
25202
|
+
await reader.cancel().catch(() => void 0);
|
|
25203
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
25204
|
+
}
|
|
25205
|
+
chunks.push(value);
|
|
25206
|
+
}
|
|
25207
|
+
} finally {
|
|
25208
|
+
reader.releaseLock();
|
|
25209
|
+
}
|
|
25210
|
+
const bytes2 = new Uint8Array(total);
|
|
25211
|
+
let offset = 0;
|
|
25212
|
+
for (const chunk of chunks) {
|
|
25213
|
+
bytes2.set(chunk, offset);
|
|
25214
|
+
offset += chunk.byteLength;
|
|
25215
|
+
}
|
|
25216
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
25217
|
+
}
|
|
26036
25218
|
function validateSubject(subject) {
|
|
26037
25219
|
if (typeof subject !== "string" || subject.length === 0) {
|
|
26038
25220
|
throw new LocationRecordValidationError(
|
|
@@ -30999,7 +30181,7 @@ function ensureWasm() {
|
|
|
30999
30181
|
wasmInitialized = true;
|
|
31000
30182
|
}
|
|
31001
30183
|
}
|
|
31002
|
-
function
|
|
30184
|
+
function generateKey() {
|
|
31003
30185
|
ensureWasm();
|
|
31004
30186
|
const mgr = new TCWSessionManager();
|
|
31005
30187
|
const keyId = mgr.createSessionKey("cli");
|
|
@@ -31262,7 +30444,7 @@ async function ensureShareDeviceAuthorization(input) {
|
|
|
31262
30444
|
}
|
|
31263
30445
|
let key = await ProfileManager.getKey(input.profileName);
|
|
31264
30446
|
if (!key) {
|
|
31265
|
-
const generated =
|
|
30447
|
+
const generated = generateKey();
|
|
31266
30448
|
key = generated.jwk;
|
|
31267
30449
|
await ProfileManager.setKey(input.profileName, key);
|
|
31268
30450
|
}
|
|
@@ -32324,7 +31506,7 @@ async function rotateAuthKey(profileName, host, options = {}) {
|
|
|
32324
31506
|
return;
|
|
32325
31507
|
}
|
|
32326
31508
|
const { jwk, did } = await withSpinner("Generating session key...", async () => {
|
|
32327
|
-
return
|
|
31509
|
+
return generateKey();
|
|
32328
31510
|
});
|
|
32329
31511
|
await ProfileManager.setKey(profileName, jwk);
|
|
32330
31512
|
await ProfileManager.clearSession(profileName);
|
|
@@ -32407,7 +31589,7 @@ async function handleLocalAuth(profileName, host, options = {}) {
|
|
|
32407
31589
|
const hasKey = await ProfileManager.getKey(profileName);
|
|
32408
31590
|
if (options.forceSessionKey || !hasKey) {
|
|
32409
31591
|
const { jwk, did: generatedSessionDid } = await withSpinner("Generating session key...", async () => {
|
|
32410
|
-
return
|
|
31592
|
+
return generateKey();
|
|
32411
31593
|
});
|
|
32412
31594
|
await ProfileManager.setKey(profileName, jwk);
|
|
32413
31595
|
sessionDid = generatedSessionDid;
|
|
@@ -33122,7 +32304,7 @@ function registerInitCommand(program) {
|
|
|
33122
32304
|
}
|
|
33123
32305
|
await ProfileManager.ensureConfigDir();
|
|
33124
32306
|
const { jwk, did } = await withSpinner("Generating key...", async () => {
|
|
33125
|
-
return
|
|
32307
|
+
return generateKey();
|
|
33126
32308
|
});
|
|
33127
32309
|
await ProfileManager.setKey(profileName, jwk);
|
|
33128
32310
|
const profileConfig = {
|
|
@@ -33642,7 +32824,7 @@ function registerProfileCommand(program) {
|
|
|
33642
32824
|
throw new CLIError("PROFILE_EXISTS", `Profile "${name2}" already exists`, ExitCode.ERROR);
|
|
33643
32825
|
}
|
|
33644
32826
|
await ProfileManager.ensureConfigDir();
|
|
33645
|
-
const { jwk, did } =
|
|
32827
|
+
const { jwk, did } = generateKey();
|
|
33646
32828
|
await ProfileManager.setKey(name2, jwk);
|
|
33647
32829
|
await ProfileManager.setProfile(name2, {
|
|
33648
32830
|
name: name2,
|
|
@@ -34788,7 +33970,6 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
34788
33970
|
|
|
34789
33971
|
// src/commands/share.ts
|
|
34790
33972
|
init_dist3();
|
|
34791
|
-
import { readFile as readFile10 } from "fs/promises";
|
|
34792
33973
|
init_errors();
|
|
34793
33974
|
|
|
34794
33975
|
// src/share/output.ts
|
|
@@ -34950,8 +34131,6 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
34950
34131
|
|
|
34951
34132
|
// src/commands/share.ts
|
|
34952
34133
|
var SHARE_ORIGIN = "https://share.tinycloud.xyz";
|
|
34953
|
-
var DEFAULT_REGISTRY = `${SHARE_ORIGIN}/api/share/link-only/registry`;
|
|
34954
|
-
var DEFAULT_READ_REGISTRY = "https://registry.tinycloud.xyz";
|
|
34955
34134
|
var shareServices = {};
|
|
34956
34135
|
function parseShareTarget(value) {
|
|
34957
34136
|
if (value === "anyone" || value === "bearer") return { kind: "bearer" };
|
|
@@ -34961,26 +34140,11 @@ function parseShareTarget(value) {
|
|
|
34961
34140
|
if (value.includes("@")) return { kind: "email", address: value };
|
|
34962
34141
|
throw new CLIError("INVALID_ARGUMENT", "--to must be anyone, a did:, an email address, or domain:example.com", 2);
|
|
34963
34142
|
}
|
|
34964
|
-
function publishServices(insecureLocalRegistry = false) {
|
|
34965
|
-
return {
|
|
34966
|
-
...shareServices.uploadBlob === void 0 ? {} : { uploadBlob: shareServices.uploadBlob },
|
|
34967
|
-
...shareServices.authorizeUpload === void 0 ? {} : { authorizeUpload: shareServices.authorizeUpload },
|
|
34968
|
-
...shareServices.authorizeUpload === void 0 || insecureLocalRegistry ? {} : { authorizationOrigin: SHARE_ORIGIN },
|
|
34969
|
-
...shareServices.credentials === void 0 ? {} : { credentials: shareServices.credentials },
|
|
34970
|
-
...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn }
|
|
34971
|
-
};
|
|
34972
|
-
}
|
|
34973
|
-
function fetchServices() {
|
|
34974
|
-
return {
|
|
34975
|
-
...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn },
|
|
34976
|
-
...shareServices.trustedPolicyAuthority === void 0 ? {} : { trustedPolicyAuthority: shareServices.trustedPolicyAuthority }
|
|
34977
|
-
};
|
|
34978
|
-
}
|
|
34979
34143
|
function shareCliError(error) {
|
|
34980
34144
|
if (error instanceof CLIError) return error;
|
|
34981
34145
|
if (error instanceof SharePublishError) {
|
|
34982
|
-
const exit = error.code === "
|
|
34983
|
-
const code3 = error.code === "
|
|
34146
|
+
const exit = error.code === "authority-required" ? 3 : error.code === "max-bytes-exceeded" ? 7 : 2;
|
|
34147
|
+
const code3 = error.code === "authority-required" ? "AUTH_REQUIRED" : error.code === "max-bytes-exceeded" ? "MAX_BYTES_EXCEEDED" : "INVALID_ARGUMENT";
|
|
34984
34148
|
return new CLIError(code3, error.message, exit);
|
|
34985
34149
|
}
|
|
34986
34150
|
if (error instanceof ShareReceiveError) {
|
|
@@ -35011,6 +34175,15 @@ function inputUrl(value, stdin) {
|
|
|
35011
34175
|
if (value === void 0 || value.length === 0) throw new CLIError("INVALID_ARGUMENT", "a share URL or - is required", 2);
|
|
35012
34176
|
return Promise.resolve(value);
|
|
35013
34177
|
}
|
|
34178
|
+
async function inspectShareInputOnce(value, stdin, expectedOrigin, dependencies = {}) {
|
|
34179
|
+
const link2 = stdin || value === "-" ? await (dependencies.read ?? readBoundedUrlStdin)() : await inputUrl(value, false);
|
|
34180
|
+
try {
|
|
34181
|
+
parseNativeShareUrl(link2);
|
|
34182
|
+
} catch {
|
|
34183
|
+
return (dependencies.inspect ?? inspectShare)(link2, { expectedOrigin });
|
|
34184
|
+
}
|
|
34185
|
+
throw new CLIError("UNSUPPORTED_LINK", "native bearer links are opaque; receive the link to verify access", 2);
|
|
34186
|
+
}
|
|
35014
34187
|
function jsonOutput(options, command) {
|
|
35015
34188
|
return options.json === true || command.optsWithGlobals().json === true;
|
|
35016
34189
|
}
|
|
@@ -35041,19 +34214,6 @@ function requestedActions(values) {
|
|
|
35041
34214
|
if (actions.some((value) => value !== "read" && value !== "list" && value !== "edit")) throw new CLIError("INVALID_ARGUMENT", "--action must be read, list, or edit", 2);
|
|
35042
34215
|
return [...new Set(actions)];
|
|
35043
34216
|
}
|
|
35044
|
-
async function authorizationProof(options) {
|
|
35045
|
-
const encoded = options.authorizationProofFile === void 0 ? void 0 : await readFile10(options.authorizationProofFile, "utf8").catch(() => {
|
|
35046
|
-
throw new CLIError("INVALID_ARGUMENT", "authorization proof file could not be read", 2);
|
|
35047
|
-
});
|
|
35048
|
-
if (encoded === void 0) return void 0;
|
|
35049
|
-
try {
|
|
35050
|
-
const value = JSON.parse(encoded);
|
|
35051
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("object");
|
|
35052
|
-
return value;
|
|
35053
|
-
} catch {
|
|
35054
|
-
throw new CLIError("INVALID_ARGUMENT", "authorization proof must be a JSON object", 2);
|
|
35055
|
-
}
|
|
35056
|
-
}
|
|
35057
34217
|
function assertAggregateInputLimit(inputs, maxBytes) {
|
|
35058
34218
|
const limit = maxBytes ?? MAX_SHARE_STDIN_BYTES;
|
|
35059
34219
|
let total = 0;
|
|
@@ -35064,10 +34224,9 @@ function assertAggregateInputLimit(inputs, maxBytes) {
|
|
|
35064
34224
|
}
|
|
35065
34225
|
function registerShareCommand(program) {
|
|
35066
34226
|
const share = program.command("share").description("Publish and consume TinyCloud Share links");
|
|
35067
|
-
share.command("publish <files...>").description("Publish one or more bounded files as a Share").option("--name <filename>", "Filename for stdin input").option("--to <target>", "Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Share lifetime", "7d").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--
|
|
34227
|
+
share.command("publish <files...>").description("Publish one or more bounded files as a Share").option("--name <filename>", "Filename for stdin input").option("--to <target>", "Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Share lifetime", "7d").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).action(async (files, options, command) => {
|
|
35068
34228
|
try {
|
|
35069
34229
|
const json = jsonOutput(options, command);
|
|
35070
|
-
if (options.inline && options.compact) throw new CLIError("INVALID_ARGUMENT", "--inline and --compact are mutually exclusive", 2);
|
|
35071
34230
|
const maxBytes = byteLimit(options.maxBytes);
|
|
35072
34231
|
if (files.length === 0 || files.includes("-") && files.length > 1) throw new CLIError("INVALID_ARGUMENT", "stdin must be the only publish input", 2);
|
|
35073
34232
|
const inputs = await Promise.all(files.map((file) => readShareInput(file, files.length === 1 ? options.name : void 0, maxBytes)));
|
|
@@ -35089,13 +34248,9 @@ function registerShareCommand(program) {
|
|
|
35089
34248
|
actions,
|
|
35090
34249
|
expiresAt: expires(options.expires),
|
|
35091
34250
|
origin: options.viewerOrigin,
|
|
35092
|
-
inline: options.inline === true,
|
|
35093
34251
|
...maxBytes === void 0 ? {} : { maxBytes },
|
|
35094
|
-
registryBaseUrl: options.registry,
|
|
35095
|
-
allowInsecureRegistry: options.insecureRegistry === true,
|
|
35096
34252
|
notify: options.notify === true,
|
|
35097
|
-
targetAdapter: shareServices.targetAdapter
|
|
35098
|
-
...publishServices(options.insecureRegistry === true)
|
|
34253
|
+
targetAdapter: shareServices.targetAdapter
|
|
35099
34254
|
});
|
|
35100
34255
|
if ("state" in result) {
|
|
35101
34256
|
if (json) {
|
|
@@ -35117,44 +34272,44 @@ function registerShareCommand(program) {
|
|
|
35117
34272
|
handleError(shareCliError(error));
|
|
35118
34273
|
}
|
|
35119
34274
|
});
|
|
35120
|
-
share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--
|
|
34275
|
+
share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
|
|
35121
34276
|
try {
|
|
35122
34277
|
const json = jsonOutput(options, command);
|
|
35123
|
-
const
|
|
35124
|
-
const result = await inspectShare(link2, { registryBaseUrl: options.registry, expectedOrigin: options.viewerOrigin, ...fetchServices() });
|
|
34278
|
+
const result = await inspectShareInputOnce(url, options.stdin === true, options.viewerOrigin);
|
|
35125
34279
|
if (json) writeJson2(result);
|
|
35126
34280
|
else inspectHuman(result);
|
|
35127
34281
|
} catch (error) {
|
|
35128
34282
|
handleError(shareCliError(error));
|
|
35129
34283
|
}
|
|
35130
34284
|
});
|
|
35131
|
-
share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--
|
|
34285
|
+
share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
|
|
35132
34286
|
try {
|
|
35133
34287
|
const json = jsonOutput(options, command);
|
|
35134
34288
|
if (options.stdout && json) throw new CLIError("INVALID_ARGUMENT", "--stdout and --json are mutually exclusive", 2);
|
|
35135
34289
|
const maxBytes = byteLimit(options.maxBytes);
|
|
35136
34290
|
const link2 = await inputUrl(url, options.stdin === true);
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
|
|
35140
|
-
|
|
34291
|
+
let nativeLink = false;
|
|
34292
|
+
try {
|
|
34293
|
+
parseNativeShareUrl(link2);
|
|
34294
|
+
nativeLink = true;
|
|
34295
|
+
} catch {
|
|
34296
|
+
}
|
|
34297
|
+
if (nativeLink) {
|
|
34298
|
+
if (shareServices.nativeReader === void 0) throw new CLIError("AUTH_REQUIRED", "native TinyCloud receive is not configured", 3);
|
|
34299
|
+
const native = await shareServices.nativeReader(link2);
|
|
34300
|
+
if (maxBytes !== void 0 && native.bytes.byteLength > maxBytes) throw new CLIError("MAX_BYTES_EXCEEDED", "shared content exceeds max-bytes", 7);
|
|
35141
34301
|
if (options.stdout) {
|
|
35142
|
-
process.stdout.write(Buffer.from(bytes));
|
|
34302
|
+
process.stdout.write(Buffer.from(native.bytes));
|
|
35143
34303
|
return;
|
|
35144
34304
|
}
|
|
35145
|
-
const output3 = await writeShareOutput(options.output ?? ".",
|
|
35146
|
-
if (json) writeJson2({ protocol: "tinycloud-share", version: 1,
|
|
34305
|
+
const output3 = await writeShareOutput(options.output ?? ".", native.filename, native.bytes, options.force === true);
|
|
34306
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, path: output3, transport: "native" });
|
|
35147
34307
|
else receiveHuman(output3);
|
|
35148
34308
|
return;
|
|
35149
34309
|
}
|
|
35150
34310
|
const result = await receiveShare(link2, {
|
|
35151
|
-
registryBaseUrl: options.registry,
|
|
35152
34311
|
expectedOrigin: options.viewerOrigin,
|
|
35153
|
-
...
|
|
35154
|
-
...maxBytes === void 0 ? {} : { maxContentBlobBytes: maxBytes },
|
|
35155
|
-
...shareServices.authorization === void 0 ? {} : { authorization: shareServices.authorization },
|
|
35156
|
-
...options.resumeToken === void 0 ? {} : { authorizationResumeToken: options.resumeToken },
|
|
35157
|
-
...proof === void 0 ? {} : { authorizationProof: proof }
|
|
34312
|
+
...maxBytes === void 0 ? {} : { maxContentBlobBytes: maxBytes }
|
|
35158
34313
|
});
|
|
35159
34314
|
if ("state" in result) {
|
|
35160
34315
|
if (json) {
|
|
@@ -35175,50 +34330,6 @@ function registerShareCommand(program) {
|
|
|
35175
34330
|
handleError(shareCliError(error));
|
|
35176
34331
|
}
|
|
35177
34332
|
});
|
|
35178
|
-
share.command("migrate [url]").description("Read a legacy tc1 link and re-mint a modern Share link").option("--stdin", "Read the complete legacy link from stdin").option("--name <filename>", "Filename for the migrated content", "migrated.md").option("--to <target>", "Modern Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Modern share lifetime", "7d").option("--max-bytes <bytes>", "Bound migrated content bytes").option("--inline", "Embed the sealed envelope in the URL fragment").option("--registry <url>", "Authenticated registry upload endpoint", DEFAULT_REGISTRY).option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).option("--insecure-registry", "Allow an explicit localhost HTTP registry for hermetic tests").option("--json", "Print versioned redacted JSON").action(async (url, options, command) => {
|
|
35179
|
-
try {
|
|
35180
|
-
const json = jsonOutput(options, command);
|
|
35181
|
-
if (shareServices.legacyReader === void 0) throw new CLIError("UNSUPPORTED_LINK", "legacy migration requires an installed read-only tc1 adapter", 2);
|
|
35182
|
-
const link2 = await inputUrl(url, options.stdin === true);
|
|
35183
|
-
if (!isLegacyShareLink(link2)) throw new CLIError("UNSUPPORTED_LINK", "only tc1: links can be migrated", 2);
|
|
35184
|
-
const maxBytes = byteLimit(options.maxBytes) ?? MAX_SHARE_STDIN_BYTES;
|
|
35185
|
-
const migrated = await migrateShare({
|
|
35186
|
-
link: link2,
|
|
35187
|
-
reader: shareServices.legacyReader,
|
|
35188
|
-
publish: async (bytes) => {
|
|
35189
|
-
if (bytes.byteLength > maxBytes) throw new SharePublishError("max-bytes-exceeded", "legacy content exceeds the configured byte limit");
|
|
35190
|
-
const result = await publishTargetShare({
|
|
35191
|
-
source: bytes,
|
|
35192
|
-
filename: options.name,
|
|
35193
|
-
mediaType: "text/markdown",
|
|
35194
|
-
target: parseShareTarget(options.to),
|
|
35195
|
-
expiresAt: expires(options.expires),
|
|
35196
|
-
origin: options.viewerOrigin,
|
|
35197
|
-
inline: options.inline === true,
|
|
35198
|
-
registryBaseUrl: options.registry,
|
|
35199
|
-
allowInsecureRegistry: options.insecureRegistry === true,
|
|
35200
|
-
notify: options.notify === true,
|
|
35201
|
-
targetAdapter: shareServices.targetAdapter,
|
|
35202
|
-
...publishServices(options.insecureRegistry === true)
|
|
35203
|
-
});
|
|
35204
|
-
if ("state" in result) throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
|
|
35205
|
-
const record = await rememberPublishedShare(result);
|
|
35206
|
-
if (options.notify === true) {
|
|
35207
|
-
const target = parseShareTarget(options.to);
|
|
35208
|
-
if (target.kind !== "email") throw new CLIError("INVALID_ARGUMENT", "--notify requires an exact email target", 2);
|
|
35209
|
-
if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
|
|
35210
|
-
const delivery = await notifyShare({ shareId: record.shareId, recipient: target.address, record, adapter: shareServices.delivery });
|
|
35211
|
-
if (delivery.state === "partial-failure") process.exitCode = 9;
|
|
35212
|
-
}
|
|
35213
|
-
return result;
|
|
35214
|
-
}
|
|
35215
|
-
});
|
|
35216
|
-
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, migrated: redactPublishedShare(migrated.migrated) });
|
|
35217
|
-
else publishHuman(migrated.migrated);
|
|
35218
|
-
} catch (error) {
|
|
35219
|
-
handleError(shareCliError(error));
|
|
35220
|
-
}
|
|
35221
|
-
});
|
|
35222
34333
|
share.command("list").description("List encrypted sender history without complete bearer URLs").option("--json", "Print versioned redacted JSON").action(async (options, command) => {
|
|
35223
34334
|
try {
|
|
35224
34335
|
const json = jsonOutput(options, command);
|
|
@@ -36127,7 +35238,7 @@ init_profiles();
|
|
|
36127
35238
|
init_formatter();
|
|
36128
35239
|
init_errors();
|
|
36129
35240
|
init_constants();
|
|
36130
|
-
import { readFile as
|
|
35241
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
36131
35242
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
36132
35243
|
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
36133
35244
|
async function readStdin4() {
|
|
@@ -36185,7 +35296,7 @@ function registerVaultCommand(program) {
|
|
|
36185
35296
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
36186
35297
|
}
|
|
36187
35298
|
if (options.file) {
|
|
36188
|
-
putValue = new Uint8Array(await
|
|
35299
|
+
putValue = new Uint8Array(await readFile10(options.file));
|
|
36189
35300
|
} else if (options.stdin) {
|
|
36190
35301
|
putValue = new Uint8Array(await readStdin4());
|
|
36191
35302
|
} else {
|
|
@@ -36304,7 +35415,7 @@ init_profiles();
|
|
|
36304
35415
|
init_formatter();
|
|
36305
35416
|
init_errors();
|
|
36306
35417
|
init_constants();
|
|
36307
|
-
import { readFile as
|
|
35418
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
36308
35419
|
import { writeFile as writeFile9 } from "fs/promises";
|
|
36309
35420
|
var VARIABLES_PREFIX = "variables/";
|
|
36310
35421
|
async function readStdin5() {
|
|
@@ -36405,7 +35516,7 @@ function registerVarsCommand(program) {
|
|
|
36405
35516
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
36406
35517
|
}
|
|
36407
35518
|
if (options.file) {
|
|
36408
|
-
varValue = await
|
|
35519
|
+
varValue = await readFile11(options.file, "utf-8");
|
|
36409
35520
|
} else if (options.stdin) {
|
|
36410
35521
|
varValue = (await readStdin5()).toString("utf-8");
|
|
36411
35522
|
} else {
|