@tinycloud/cli 0.9.1-beta.8 → 1.0.0-beta.10
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 +992 -2680
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +216 -1139
- 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) => {
|
|
@@ -11809,7 +11809,9 @@ function canonicalizeSecretScope(scope) {
|
|
|
11809
11809
|
}
|
|
11810
11810
|
const trimmed = scope.trim();
|
|
11811
11811
|
if (trimmed === "") {
|
|
11812
|
-
throw new Error(
|
|
11812
|
+
throw new Error(
|
|
11813
|
+
"Secret scope must be non-empty; omit scope for global secrets."
|
|
11814
|
+
);
|
|
11813
11815
|
}
|
|
11814
11816
|
const canonical = trimmed.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
11815
11817
|
if (canonical === "") {
|
|
@@ -13305,7 +13307,7 @@ var init_dist2 = __esm({
|
|
|
13305
13307
|
*/
|
|
13306
13308
|
getFullPath(key, prefixOverride) {
|
|
13307
13309
|
const prefix = prefixOverride ?? this._config.prefix ?? "";
|
|
13308
|
-
return prefix ? `${prefix}/${key}` : key;
|
|
13310
|
+
return prefix ? key === "" ? prefix : `${prefix}/${key}` : key;
|
|
13309
13311
|
}
|
|
13310
13312
|
/**
|
|
13311
13313
|
* Get the host URL.
|
|
@@ -17607,17 +17609,6 @@ function fromBase64Url(text) {
|
|
|
17607
17609
|
function utf8Bytes(text) {
|
|
17608
17610
|
return new TextEncoder().encode(text);
|
|
17609
17611
|
}
|
|
17610
|
-
function didKeyFromEd25519PublicKey(publicKey) {
|
|
17611
|
-
if (publicKey.length !== PUBLIC_KEY_LENGTH) {
|
|
17612
|
-
throw new TypeError(
|
|
17613
|
-
`ed25519 public key must be ${PUBLIC_KEY_LENGTH} bytes, got ${publicKey.length}`
|
|
17614
|
-
);
|
|
17615
|
-
}
|
|
17616
|
-
const prefixed = new Uint8Array(ED25519_MULTICODEC_PREFIX.length + publicKey.length);
|
|
17617
|
-
prefixed.set(ED25519_MULTICODEC_PREFIX, 0);
|
|
17618
|
-
prefixed.set(publicKey, ED25519_MULTICODEC_PREFIX.length);
|
|
17619
|
-
return `did:key:${base58btc.encode(prefixed)}`;
|
|
17620
|
-
}
|
|
17621
17612
|
function ed25519PublicKeyFromDidKey(did) {
|
|
17622
17613
|
if (!did.startsWith("did:key:")) {
|
|
17623
17614
|
throw new TypeError(`not a did:key: ${did}`);
|
|
@@ -17629,15 +17620,6 @@ function ed25519PublicKeyFromDidKey(did) {
|
|
|
17629
17620
|
}
|
|
17630
17621
|
return prefixed.slice(ED25519_MULTICODEC_PREFIX.length);
|
|
17631
17622
|
}
|
|
17632
|
-
function getBearerSessionJwk(envelope) {
|
|
17633
|
-
const target = envelope.authorizationTarget;
|
|
17634
|
-
if (target.kind !== "bearerKey") {
|
|
17635
|
-
throw new TypeError(
|
|
17636
|
-
`envelope authorizationTarget is "${target.kind}", not "bearerKey"`
|
|
17637
|
-
);
|
|
17638
|
-
}
|
|
17639
|
-
return target.sessionJwk;
|
|
17640
|
-
}
|
|
17641
17623
|
function decodeBase64UrlOrNull(value) {
|
|
17642
17624
|
try {
|
|
17643
17625
|
return fromBase64Url(value);
|
|
@@ -17695,7 +17677,7 @@ function validateV3Invariants(value, ctx) {
|
|
|
17695
17677
|
if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
|
|
17696
17678
|
ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
|
|
17697
17679
|
}
|
|
17698
|
-
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)) {
|
|
17699
17681
|
ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
17700
17682
|
}
|
|
17701
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) {
|
|
@@ -17710,273 +17692,6 @@ function validateV3Invariants(value, ctx) {
|
|
|
17710
17692
|
ctx.addIssue({ code: external_exports2.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
|
|
17711
17693
|
}
|
|
17712
17694
|
}
|
|
17713
|
-
function decodeJsonSegment(segment) {
|
|
17714
|
-
try {
|
|
17715
|
-
const bytes3 = fromBase64Url(segment);
|
|
17716
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes3));
|
|
17717
|
-
} catch {
|
|
17718
|
-
return null;
|
|
17719
|
-
}
|
|
17720
|
-
}
|
|
17721
|
-
function isJsonObject(value) {
|
|
17722
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17723
|
-
}
|
|
17724
|
-
function parseCapability(value) {
|
|
17725
|
-
if (!isJsonObject(value)) return null;
|
|
17726
|
-
const withUri = value["with"];
|
|
17727
|
-
const can = value["can"];
|
|
17728
|
-
if (typeof withUri !== "string" || withUri.length === 0) return null;
|
|
17729
|
-
if (typeof can !== "string" || can.length === 0) return null;
|
|
17730
|
-
return { with: withUri, can };
|
|
17731
|
-
}
|
|
17732
|
-
function parseCanonicalResourceUri(uri) {
|
|
17733
|
-
const match = /^(https:\/\/[^/\\]+)\/(.*)$/.exec(uri);
|
|
17734
|
-
if (match === null) return null;
|
|
17735
|
-
const [, origin, path] = match;
|
|
17736
|
-
if (!isCanonicalHttpsOrigin(origin)) return null;
|
|
17737
|
-
if (!isCanonicalResourcePath(path)) return null;
|
|
17738
|
-
return { origin, segments: path.split("/") };
|
|
17739
|
-
}
|
|
17740
|
-
function resourceUriCovers(granted, target) {
|
|
17741
|
-
const targetParsed = parseCanonicalResourceUri(target);
|
|
17742
|
-
if (targetParsed === null) return false;
|
|
17743
|
-
const wildcard = granted.endsWith("/*");
|
|
17744
|
-
const grantedParsed = parseCanonicalResourceUri(
|
|
17745
|
-
wildcard ? granted.slice(0, -2) : granted
|
|
17746
|
-
);
|
|
17747
|
-
if (grantedParsed === null) return false;
|
|
17748
|
-
if (grantedParsed.origin !== targetParsed.origin) return false;
|
|
17749
|
-
const grantedSegments = grantedParsed.segments;
|
|
17750
|
-
const targetSegments = targetParsed.segments;
|
|
17751
|
-
const prefixMatches = grantedSegments.every(
|
|
17752
|
-
(segment, index) => segment === targetSegments[index]
|
|
17753
|
-
);
|
|
17754
|
-
if (!prefixMatches) return false;
|
|
17755
|
-
return wildcard ? targetSegments.length > grantedSegments.length : targetSegments.length === grantedSegments.length;
|
|
17756
|
-
}
|
|
17757
|
-
function bearerResourceUri(origin, spaceId, path) {
|
|
17758
|
-
return `${origin}/${spaceId}/${path}`;
|
|
17759
|
-
}
|
|
17760
|
-
function requiredResourceUri(envelope) {
|
|
17761
|
-
const { origin, spaceId, resource } = envelope.target;
|
|
17762
|
-
return bearerResourceUri(origin, spaceId, resource.path);
|
|
17763
|
-
}
|
|
17764
|
-
function checkBearerDelegation(envelope, options = {}) {
|
|
17765
|
-
let sessionDid;
|
|
17766
|
-
try {
|
|
17767
|
-
const jwk = getBearerSessionJwk(envelope);
|
|
17768
|
-
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519") {
|
|
17769
|
-
return {
|
|
17770
|
-
ok: false,
|
|
17771
|
-
detail: `unsupported bearer session key family ${jwk.kty}/${jwk.crv}; this build binds Ed25519 keys only`
|
|
17772
|
-
};
|
|
17773
|
-
}
|
|
17774
|
-
const publicKey = fromBase64Url(jwk.x);
|
|
17775
|
-
const privateKey = fromBase64Url(jwk.d);
|
|
17776
|
-
if (publicKey.length !== 32 || privateKey.length !== 32) {
|
|
17777
|
-
return { ok: false, detail: "bearer session key must contain 32-byte Ed25519 coordinates" };
|
|
17778
|
-
}
|
|
17779
|
-
const derivedPublicKey = ed25519.getPublicKey(privateKey);
|
|
17780
|
-
if (!derivedPublicKey.every((byte, index) => byte === publicKey[index])) {
|
|
17781
|
-
return { ok: false, detail: "bearer session private and public keys do not match" };
|
|
17782
|
-
}
|
|
17783
|
-
sessionDid = didKeyFromEd25519PublicKey(publicKey);
|
|
17784
|
-
} catch (error) {
|
|
17785
|
-
return {
|
|
17786
|
-
ok: false,
|
|
17787
|
-
detail: `could not derive did:key from embedded session key: ${error instanceof Error ? error.message : String(error)}`
|
|
17788
|
-
};
|
|
17789
|
-
}
|
|
17790
|
-
const segments = envelope.delegation.split(".");
|
|
17791
|
-
if (segments.length !== 3 || segments.some((segment) => segment.length === 0)) {
|
|
17792
|
-
return { ok: false, detail: "delegation is not a three-segment JWT-shaped token" };
|
|
17793
|
-
}
|
|
17794
|
-
const [headerSegment, payloadSegment, signatureSegment] = segments;
|
|
17795
|
-
const header = decodeJsonSegment(headerSegment);
|
|
17796
|
-
if (!isJsonObject(header)) {
|
|
17797
|
-
return { ok: false, detail: "delegation header is not a base64url JSON object" };
|
|
17798
|
-
}
|
|
17799
|
-
const payload = decodeJsonSegment(payloadSegment);
|
|
17800
|
-
if (!isJsonObject(payload)) {
|
|
17801
|
-
return { ok: false, detail: "delegation payload is not a base64url JSON object" };
|
|
17802
|
-
}
|
|
17803
|
-
let signatureBytes;
|
|
17804
|
-
try {
|
|
17805
|
-
signatureBytes = fromBase64Url(signatureSegment);
|
|
17806
|
-
if (signatureBytes.length === 0) {
|
|
17807
|
-
return { ok: false, detail: "delegation signature segment is empty" };
|
|
17808
|
-
}
|
|
17809
|
-
} catch {
|
|
17810
|
-
return { ok: false, detail: "delegation signature segment is not base64url" };
|
|
17811
|
-
}
|
|
17812
|
-
if (header["alg"] !== "EdDSA") {
|
|
17813
|
-
return {
|
|
17814
|
-
ok: false,
|
|
17815
|
-
detail: `delegation alg must be EdDSA, got ${JSON.stringify(header["alg"])}`
|
|
17816
|
-
};
|
|
17817
|
-
}
|
|
17818
|
-
const issuer = payload["iss"];
|
|
17819
|
-
if (typeof issuer !== "string" || issuer.length === 0) {
|
|
17820
|
-
return { ok: false, detail: "delegation payload has no issuer (iss) DID" };
|
|
17821
|
-
}
|
|
17822
|
-
let issuerPublicKey;
|
|
17823
|
-
try {
|
|
17824
|
-
issuerPublicKey = ed25519PublicKeyFromDidKey(issuer);
|
|
17825
|
-
} catch (error) {
|
|
17826
|
-
return {
|
|
17827
|
-
ok: false,
|
|
17828
|
-
detail: `delegation issuer is not an ed25519 did:key: ${error instanceof Error ? error.message : String(error)}`
|
|
17829
|
-
};
|
|
17830
|
-
}
|
|
17831
|
-
let signatureValid;
|
|
17832
|
-
try {
|
|
17833
|
-
signatureValid = ed25519.verify(
|
|
17834
|
-
signatureBytes,
|
|
17835
|
-
utf8Bytes(`${headerSegment}.${payloadSegment}`),
|
|
17836
|
-
issuerPublicKey,
|
|
17837
|
-
ED25519_VERIFY_OPTS
|
|
17838
|
-
);
|
|
17839
|
-
} catch {
|
|
17840
|
-
signatureValid = false;
|
|
17841
|
-
}
|
|
17842
|
-
if (!signatureValid) {
|
|
17843
|
-
return { ok: false, detail: "delegation signature does not verify against iss" };
|
|
17844
|
-
}
|
|
17845
|
-
const nowMs2 = options.now?.() ?? Date.now();
|
|
17846
|
-
const exp = payload["exp"];
|
|
17847
|
-
if (typeof exp !== "number" || !Number.isSafeInteger(exp) || exp <= 0) {
|
|
17848
|
-
return { ok: false, detail: "delegation has no valid expiry (exp) claim" };
|
|
17849
|
-
}
|
|
17850
|
-
if (nowMs2 >= exp * 1e3) {
|
|
17851
|
-
return { ok: false, detail: "delegation is expired" };
|
|
17852
|
-
}
|
|
17853
|
-
const nbf = payload["nbf"];
|
|
17854
|
-
if (nbf !== void 0) {
|
|
17855
|
-
if (typeof nbf !== "number" || !Number.isSafeInteger(nbf)) {
|
|
17856
|
-
return { ok: false, detail: "delegation nbf claim is not a valid time" };
|
|
17857
|
-
}
|
|
17858
|
-
if (nowMs2 < nbf * 1e3) {
|
|
17859
|
-
return { ok: false, detail: "delegation is not yet valid (nbf)" };
|
|
17860
|
-
}
|
|
17861
|
-
}
|
|
17862
|
-
const audience = payload["aud"];
|
|
17863
|
-
if (typeof audience !== "string" || audience.length === 0) {
|
|
17864
|
-
return { ok: false, detail: "delegation payload has no audience (aud) DID" };
|
|
17865
|
-
}
|
|
17866
|
-
if (audience !== sessionDid) {
|
|
17867
|
-
return {
|
|
17868
|
-
ok: false,
|
|
17869
|
-
detail: "delegation audience is not the link's embedded session key"
|
|
17870
|
-
};
|
|
17871
|
-
}
|
|
17872
|
-
const att = payload["att"];
|
|
17873
|
-
if (!Array.isArray(att) || att.length === 0) {
|
|
17874
|
-
return { ok: false, detail: "delegation payload has no capabilities (att)" };
|
|
17875
|
-
}
|
|
17876
|
-
const required = requiredResourceUri(envelope);
|
|
17877
|
-
const granted = att.map(parseCapability).some(
|
|
17878
|
-
(capability) => capability !== null && READ_ABILITIES.has(capability.can) && resourceUriCovers(capability.with, required)
|
|
17879
|
-
);
|
|
17880
|
-
if (!granted) {
|
|
17881
|
-
return {
|
|
17882
|
-
ok: false,
|
|
17883
|
-
detail: `delegation grants no read capability covering ${required}`
|
|
17884
|
-
};
|
|
17885
|
-
}
|
|
17886
|
-
return { ok: true, delegateeDid: audience };
|
|
17887
|
-
}
|
|
17888
|
-
function mintBearerDelegation(options) {
|
|
17889
|
-
const { issuerPrivateKey, audienceDid, resourceUri, expiresAtSeconds } = options;
|
|
17890
|
-
if (!audienceDid.startsWith("did:")) {
|
|
17891
|
-
throw new TypeError(`audienceDid must be a DID, got ${audienceDid}`);
|
|
17892
|
-
}
|
|
17893
|
-
if (resourceUri.length === 0) {
|
|
17894
|
-
throw new TypeError("resourceUri must be non-empty");
|
|
17895
|
-
}
|
|
17896
|
-
if (!Number.isSafeInteger(expiresAtSeconds) || expiresAtSeconds <= 0) {
|
|
17897
|
-
throw new TypeError(
|
|
17898
|
-
`expiresAtSeconds must be a positive integer (epoch seconds), got ${expiresAtSeconds}`
|
|
17899
|
-
);
|
|
17900
|
-
}
|
|
17901
|
-
const issuerDid = didKeyFromEd25519PublicKey(
|
|
17902
|
-
ed25519.getPublicKey(issuerPrivateKey)
|
|
17903
|
-
);
|
|
17904
|
-
const header = { alg: "EdDSA", typ: "JWT", ucv: "0.9.1" };
|
|
17905
|
-
const payload = {
|
|
17906
|
-
iss: issuerDid,
|
|
17907
|
-
aud: audienceDid,
|
|
17908
|
-
att: [{ with: resourceUri, can: BEARER_READ_ABILITY }],
|
|
17909
|
-
prf: [],
|
|
17910
|
-
exp: expiresAtSeconds
|
|
17911
|
-
};
|
|
17912
|
-
const signingInput = `${toBase64Url(utf8Bytes(JSON.stringify(header)))}.${toBase64Url(
|
|
17913
|
-
utf8Bytes(JSON.stringify(payload))
|
|
17914
|
-
)}`;
|
|
17915
|
-
const signature = ed25519.sign(utf8Bytes(signingInput), issuerPrivateKey);
|
|
17916
|
-
return `${signingInput}.${toBase64Url(signature)}`;
|
|
17917
|
-
}
|
|
17918
|
-
function assertKey(key32) {
|
|
17919
|
-
if (key32.length !== KEY_LENGTH) {
|
|
17920
|
-
throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
|
|
17921
|
-
}
|
|
17922
|
-
}
|
|
17923
|
-
async function importAesKey(key32, usage) {
|
|
17924
|
-
assertKey(key32);
|
|
17925
|
-
return globalThis.crypto.subtle.importKey(
|
|
17926
|
-
"raw",
|
|
17927
|
-
key32,
|
|
17928
|
-
"AES-GCM",
|
|
17929
|
-
false,
|
|
17930
|
-
[usage]
|
|
17931
|
-
);
|
|
17932
|
-
}
|
|
17933
|
-
function generateKey() {
|
|
17934
|
-
return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
|
17935
|
-
}
|
|
17936
|
-
async function seal(plaintextBytes, key32) {
|
|
17937
|
-
const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
|
|
17938
|
-
const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
|
|
17939
|
-
blob[0] = SEALED_BLOB_VERSION;
|
|
17940
|
-
blob.set(nonce, HEADER_LENGTH);
|
|
17941
|
-
blob.set(ciphertext, HEADER_LENGTH + nonce.length);
|
|
17942
|
-
return { blob, cid: await computeCid(blob) };
|
|
17943
|
-
}
|
|
17944
|
-
async function open(blob, key32) {
|
|
17945
|
-
if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
|
|
17946
|
-
throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
|
|
17947
|
-
}
|
|
17948
|
-
if (blob[0] !== SEALED_BLOB_VERSION) {
|
|
17949
|
-
throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
|
|
17950
|
-
}
|
|
17951
|
-
const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
|
|
17952
|
-
const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
|
|
17953
|
-
return decryptEnvelope(nonce, ciphertext, key32);
|
|
17954
|
-
}
|
|
17955
|
-
async function encryptEnvelope(plaintextBytes, key32) {
|
|
17956
|
-
const key = await importAesKey(key32, "encrypt");
|
|
17957
|
-
const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
|
17958
|
-
const ciphertext = new Uint8Array(
|
|
17959
|
-
await globalThis.crypto.subtle.encrypt(
|
|
17960
|
-
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
17961
|
-
key,
|
|
17962
|
-
plaintextBytes
|
|
17963
|
-
)
|
|
17964
|
-
);
|
|
17965
|
-
return { nonce, ciphertext };
|
|
17966
|
-
}
|
|
17967
|
-
async function decryptEnvelope(nonce, ciphertext, key32) {
|
|
17968
|
-
if (nonce.length !== NONCE_LENGTH) {
|
|
17969
|
-
throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
|
|
17970
|
-
}
|
|
17971
|
-
const key = await importAesKey(key32, "decrypt");
|
|
17972
|
-
return new Uint8Array(
|
|
17973
|
-
await globalThis.crypto.subtle.decrypt(
|
|
17974
|
-
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
17975
|
-
key,
|
|
17976
|
-
ciphertext
|
|
17977
|
-
)
|
|
17978
|
-
);
|
|
17979
|
-
}
|
|
17980
17695
|
function isPlainRecord(value) {
|
|
17981
17696
|
const proto = Object.getPrototypeOf(value);
|
|
17982
17697
|
return proto === Object.prototype || proto === null;
|
|
@@ -18075,22 +17790,6 @@ function assertExactKeys(value, keys, label) {
|
|
|
18075
17790
|
function equal(left, right) {
|
|
18076
17791
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
18077
17792
|
}
|
|
18078
|
-
function signingBytes(unsigned) {
|
|
18079
|
-
const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
|
|
18080
|
-
const body = utf8Bytes(canonicalize2(unsigned));
|
|
18081
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
18082
|
-
bytes3.set(domain);
|
|
18083
|
-
bytes3.set(body, domain.length);
|
|
18084
|
-
return bytes3;
|
|
18085
|
-
}
|
|
18086
|
-
function signingBytesV2(unsigned) {
|
|
18087
|
-
const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
|
|
18088
|
-
const body = utf8Bytes(canonicalize2(unsigned));
|
|
18089
|
-
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
18090
|
-
bytes3.set(domain);
|
|
18091
|
-
bytes3.set(body, domain.length);
|
|
18092
|
-
return bytes3;
|
|
18093
|
-
}
|
|
18094
17793
|
function signingBytesV3(unsigned) {
|
|
18095
17794
|
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
18096
17795
|
const body = utf8Bytes(canonicalize2(unsigned));
|
|
@@ -18099,53 +17798,6 @@ function signingBytesV3(unsigned) {
|
|
|
18099
17798
|
bytes3.set(body, domain.length);
|
|
18100
17799
|
return bytes3;
|
|
18101
17800
|
}
|
|
18102
|
-
function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
|
|
18103
|
-
const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
|
|
18104
|
-
const publicKey = ed25519.getPublicKey(ed25519PrivKey);
|
|
18105
|
-
const signature = ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
|
|
18106
|
-
return {
|
|
18107
|
-
...unsigned,
|
|
18108
|
-
signature: {
|
|
18109
|
-
signerDid: didKeyFromEd25519PublicKey(publicKey),
|
|
18110
|
-
algorithm: "Ed25519",
|
|
18111
|
-
value: toBase64Url(signature)
|
|
18112
|
-
}
|
|
18113
|
-
};
|
|
18114
|
-
}
|
|
18115
|
-
function verifyEnvelopeSignatureOnly(envelope) {
|
|
18116
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
18117
|
-
const { signature, ...unsigned } = parsed;
|
|
18118
|
-
const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
|
|
18119
|
-
return ed25519.verify(
|
|
18120
|
-
fromBase64Url(signature.value),
|
|
18121
|
-
signingBytes(unsigned),
|
|
18122
|
-
publicKey,
|
|
18123
|
-
ED25519_VERIFY_OPTS2
|
|
18124
|
-
);
|
|
18125
|
-
}
|
|
18126
|
-
async function verifyEnvelope(envelope, options) {
|
|
18127
|
-
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
18128
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid) return false;
|
|
18129
|
-
if (!verifyEnvelopeSignatureOnly(parsed)) return false;
|
|
18130
|
-
if (parsed.authorizationTarget.kind === "policy") {
|
|
18131
|
-
const policyBytes = fromBase64Url(parsed.authorizationTarget.policyBytes);
|
|
18132
|
-
if (await computeCid(policyBytes) !== parsed.authorizationTarget.policyCid) {
|
|
18133
|
-
return false;
|
|
18134
|
-
}
|
|
18135
|
-
}
|
|
18136
|
-
return true;
|
|
18137
|
-
}
|
|
18138
|
-
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
18139
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
18140
|
-
const { signature, ...unsigned } = parsed;
|
|
18141
|
-
return ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
18142
|
-
}
|
|
18143
|
-
async function verifyEnvelopeV2(envelope, options) {
|
|
18144
|
-
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
18145
|
-
if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
|
|
18146
|
-
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
18147
|
-
return true;
|
|
18148
|
-
}
|
|
18149
17801
|
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
18150
17802
|
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
18151
17803
|
const { signature, ...unsigned } = parsed;
|
|
@@ -18191,7 +17843,7 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
18191
17843
|
const binding = parsed.attestedEnforcerBinding;
|
|
18192
17844
|
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
18193
17845
|
const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize2({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
18194
|
-
if (binding.
|
|
17846
|
+
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;
|
|
18195
17847
|
try {
|
|
18196
17848
|
const digest3 = sha2562(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize2(unsignedBinding)}`));
|
|
18197
17849
|
if (!ed25519.verify(fromBase64Url(bindingSignature.value), digest3, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
|
|
@@ -18239,77 +17891,18 @@ function assertCanonicalCid(cidString) {
|
|
|
18239
17891
|
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
18240
17892
|
}
|
|
18241
17893
|
}
|
|
18242
|
-
function encodeShareUrl({ origin, ciphertextCid, key32 }) {
|
|
18243
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
18244
|
-
throw new TypeError(`key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
18245
|
-
}
|
|
18246
|
-
assertCanonicalCid(ciphertextCid);
|
|
18247
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
18248
|
-
throw new TypeError(`origin must be a canonical https origin, got ${origin}`);
|
|
18249
|
-
}
|
|
18250
|
-
return `${origin}/s/${ciphertextCid}#k=${toBase64Url(key32)}`;
|
|
18251
|
-
}
|
|
18252
|
-
function parseShareUrl(url, options = {}) {
|
|
18253
|
-
const parsed = new URL(url);
|
|
18254
|
-
if (parsed.protocol !== "https:") {
|
|
18255
|
-
throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
|
|
18256
|
-
}
|
|
18257
|
-
if (parsed.username !== "" || parsed.password !== "") {
|
|
18258
|
-
throw new TypeError("share URL must not carry userinfo");
|
|
18259
|
-
}
|
|
18260
|
-
if (parsed.search !== "") {
|
|
18261
|
-
throw new TypeError("share URL must not have a query string");
|
|
18262
|
-
}
|
|
18263
|
-
if (options.expectedOrigin !== void 0) {
|
|
18264
|
-
if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
|
|
18265
|
-
throw new TypeError(
|
|
18266
|
-
`expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
|
|
18267
|
-
);
|
|
18268
|
-
}
|
|
18269
|
-
if (parsed.origin !== options.expectedOrigin) {
|
|
18270
|
-
throw new TypeError(
|
|
18271
|
-
`share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
|
|
18272
|
-
);
|
|
18273
|
-
}
|
|
18274
|
-
}
|
|
18275
|
-
const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
|
|
18276
|
-
if (!match || match[1] === void 0) {
|
|
18277
|
-
throw new TypeError(`not a share URL path: ${parsed.pathname}`);
|
|
18278
|
-
}
|
|
18279
|
-
const ciphertextCid = match[1];
|
|
18280
|
-
assertCanonicalCid(ciphertextCid);
|
|
18281
|
-
if (!parsed.hash.startsWith("#k=")) {
|
|
18282
|
-
throw new TypeError("share URL is missing the #k= key fragment");
|
|
18283
|
-
}
|
|
18284
|
-
const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
|
|
18285
|
-
if (key32.length !== KEY_LENGTH2) {
|
|
18286
|
-
throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
18287
|
-
}
|
|
18288
|
-
return { ciphertextCid, key32 };
|
|
18289
|
-
}
|
|
18290
|
-
async function encodeInlineShareUrl(parts) {
|
|
18291
|
-
if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
|
|
18292
|
-
if (parts.ciphertext.byteLength === 0 || parts.ciphertext.byteLength > MAX_INLINE_BYTES) throw new RangeError("inline ciphertext is outside the allowed size");
|
|
18293
|
-
if (parts.key32 !== void 0 && parts.key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
|
|
18294
|
-
const ciphertextCid = await computeCid(parts.ciphertext);
|
|
18295
|
-
const payload = canonicalize2({
|
|
18296
|
-
v: 2,
|
|
18297
|
-
c: toBase64Url(parts.ciphertext),
|
|
18298
|
-
cid: ciphertextCid,
|
|
18299
|
-
...parts.key32 === void 0 ? {} : { k: toBase64Url(parts.key32) }
|
|
18300
|
-
});
|
|
18301
|
-
const payloadBytes = new TextEncoder().encode(payload);
|
|
18302
|
-
if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
|
|
18303
|
-
return `${parts.origin}/s/inline${INLINE_PREFIX}${toBase64Url(payloadBytes)}`;
|
|
18304
|
-
}
|
|
18305
17894
|
function parseInlineShareUrl(url, options = {}) {
|
|
18306
17895
|
const parsed = new URL(url);
|
|
18307
|
-
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== ""
|
|
17896
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("inline share URL must be canonical HTTPS without userinfo");
|
|
18308
17897
|
if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
|
|
18309
|
-
if (parsed.pathname !== "/
|
|
17898
|
+
if (parsed.pathname !== "/viewer") throw new TypeError("not a TinyCloud policy share URL");
|
|
17899
|
+
const secretPayload = parsed.search === "" && parsed.hash.startsWith(INLINE_PREFIX) ? parsed.hash.slice(INLINE_PREFIX.length) : void 0;
|
|
17900
|
+
const publicPayload = parsed.hash === "" && parsed.searchParams.size === 1 ? parsed.searchParams.get(PUBLIC_INLINE_PARAMETER) ?? void 0 : void 0;
|
|
17901
|
+
if (secretPayload === void 0 && publicPayload === void 0) throw new TypeError("not a canonical TinyCloud policy share URL");
|
|
17902
|
+
if (publicPayload !== void 0 && parsed.search !== `?${PUBLIC_INLINE_PARAMETER}=${publicPayload}`) throw new TypeError("public inline URL is not canonical");
|
|
18310
17903
|
let payloadBytes;
|
|
18311
17904
|
try {
|
|
18312
|
-
payloadBytes = fromBase64Url(
|
|
17905
|
+
payloadBytes = fromBase64Url(secretPayload ?? publicPayload);
|
|
18313
17906
|
} catch {
|
|
18314
17907
|
throw new TypeError("inline payload is not canonical base64url");
|
|
18315
17908
|
}
|
|
@@ -18322,356 +17915,95 @@ function parseInlineShareUrl(url, options = {}) {
|
|
|
18322
17915
|
}
|
|
18323
17916
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload must be an object");
|
|
18324
17917
|
if (canonicalize2(value) !== new TextDecoder("utf-8").decode(payloadBytes)) throw new TypeError("inline payload is not canonical JSON");
|
|
18325
|
-
const
|
|
18326
|
-
const keys = Object.keys(
|
|
18327
|
-
if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") ||
|
|
17918
|
+
const record = value;
|
|
17919
|
+
const keys = Object.keys(record);
|
|
17920
|
+
if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || publicPayload !== void 0 && (keys.length !== 3 || record.k !== void 0)) throw new TypeError("inline payload has invalid fields");
|
|
18328
17921
|
let ciphertext;
|
|
18329
17922
|
try {
|
|
18330
|
-
ciphertext = fromBase64Url(
|
|
17923
|
+
ciphertext = fromBase64Url(record.c);
|
|
18331
17924
|
} catch {
|
|
18332
17925
|
throw new TypeError("inline ciphertext is not canonical base64url");
|
|
18333
17926
|
}
|
|
18334
17927
|
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
|
|
18335
|
-
assertCanonicalCid(
|
|
18336
|
-
if (
|
|
17928
|
+
assertCanonicalCid(record.cid);
|
|
17929
|
+
if (record.k !== void 0 && typeof record.k !== "string") throw new TypeError("inline key must be base64url");
|
|
18337
17930
|
let key32;
|
|
18338
17931
|
try {
|
|
18339
|
-
key32 =
|
|
17932
|
+
key32 = record.k === void 0 ? void 0 : fromBase64Url(record.k);
|
|
18340
17933
|
} catch {
|
|
18341
17934
|
throw new TypeError("inline key is not canonical base64url");
|
|
18342
17935
|
}
|
|
18343
17936
|
if (key32 !== void 0 && key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
|
|
18344
|
-
return { kind: "inline", ciphertextCid:
|
|
18345
|
-
}
|
|
18346
|
-
function parseCompactOrInlineShareUrl(url, options = {}) {
|
|
18347
|
-
if (new URL(url).pathname === "/s/inline") return parseInlineShareUrl(url, options);
|
|
18348
|
-
return { kind: "compact", ...parseShareUrl(url, options) };
|
|
18349
|
-
}
|
|
18350
|
-
function boundedBytes(value, limit, code3) {
|
|
18351
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("fetch-failed", "share byte limit is invalid");
|
|
18352
|
-
if (!(value instanceof Uint8Array)) throw new ShareReceiveError("fetch-failed", "registry returned invalid bytes");
|
|
18353
|
-
if (value.byteLength > limit) throw new ShareReceiveError(code3, "share blob exceeds the configured byte limit");
|
|
18354
|
-
return value;
|
|
18355
|
-
}
|
|
18356
|
-
async function readResponseBytes(response, limit, tooLargeCode) {
|
|
18357
|
-
const contentLength = response.headers.get("content-length");
|
|
18358
|
-
if (contentLength !== null && Number.isSafeInteger(Number(contentLength)) && Number(contentLength) > limit) {
|
|
18359
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
18360
|
-
}
|
|
18361
|
-
if (response.body === null) {
|
|
18362
|
-
return boundedBytes(new Uint8Array(await response.arrayBuffer()), limit, tooLargeCode);
|
|
18363
|
-
}
|
|
18364
|
-
const reader = response.body.getReader();
|
|
18365
|
-
const chunks = [];
|
|
18366
|
-
let total = 0;
|
|
18367
|
-
try {
|
|
18368
|
-
for (; ; ) {
|
|
18369
|
-
const part = await reader.read();
|
|
18370
|
-
if (part.done) break;
|
|
18371
|
-
total += part.value.byteLength;
|
|
18372
|
-
if (total > limit) {
|
|
18373
|
-
await reader.cancel();
|
|
18374
|
-
throw new ShareReceiveError(tooLargeCode, "share blob exceeds the configured byte limit");
|
|
18375
|
-
}
|
|
18376
|
-
chunks.push(part.value);
|
|
18377
|
-
}
|
|
18378
|
-
} finally {
|
|
18379
|
-
reader.releaseLock();
|
|
18380
|
-
}
|
|
18381
|
-
const bytes3 = new Uint8Array(total);
|
|
18382
|
-
let offset = 0;
|
|
18383
|
-
for (const chunk of chunks) {
|
|
18384
|
-
bytes3.set(chunk, offset);
|
|
18385
|
-
offset += chunk.byteLength;
|
|
18386
|
-
}
|
|
18387
|
-
return bytes3;
|
|
18388
|
-
}
|
|
18389
|
-
function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
|
|
18390
|
-
if (options.fetchBlob !== void 0) return async (input) => {
|
|
18391
|
-
try {
|
|
18392
|
-
options.signal?.throwIfAborted();
|
|
18393
|
-
const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
18394
|
-
options.signal?.throwIfAborted();
|
|
18395
|
-
return boundedBytes(bytes3, limit, tooLargeCode);
|
|
18396
|
-
} catch (error) {
|
|
18397
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
18398
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
18399
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
18400
|
-
}
|
|
18401
|
-
};
|
|
18402
|
-
if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "a registry fetch adapter is required");
|
|
18403
|
-
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
18404
|
-
const base33 = options.registryBaseUrl.replace(/\/+$/, "");
|
|
18405
|
-
return async ({ cid: cid2 }) => {
|
|
18406
|
-
try {
|
|
18407
|
-
options.signal?.throwIfAborted();
|
|
18408
|
-
const response = await fetchFn(`${base33}/ipfs/${cid2}?format=raw`, {
|
|
18409
|
-
headers: { accept: "application/vnd.ipld.raw" },
|
|
18410
|
-
redirect: "error",
|
|
18411
|
-
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
18412
|
-
});
|
|
18413
|
-
options.signal?.throwIfAborted();
|
|
18414
|
-
if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
|
|
18415
|
-
const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
|
|
18416
|
-
options.signal?.throwIfAborted();
|
|
18417
|
-
return bytes3;
|
|
18418
|
-
} catch (error) {
|
|
18419
|
-
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
18420
|
-
if (error instanceof ShareReceiveError) throw error;
|
|
18421
|
-
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
18422
|
-
}
|
|
18423
|
-
};
|
|
17937
|
+
return { kind: "inline", ciphertextCid: record.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
|
|
18424
17938
|
}
|
|
18425
17939
|
function metadataFor(envelope, origin) {
|
|
18426
|
-
|
|
18427
|
-
const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
18428
|
-
return {
|
|
18429
|
-
protocol: "tinycloud-share",
|
|
18430
|
-
version: 1,
|
|
18431
|
-
shareId: envelope.shareId,
|
|
18432
|
-
origin,
|
|
18433
|
-
target: { ...envelope.target, kind: targetKind3 },
|
|
18434
|
-
resource: envelope.resource,
|
|
18435
|
-
actions: envelope.actions,
|
|
18436
|
-
expiresAt: envelope.expiry,
|
|
18437
|
-
display: {
|
|
18438
|
-
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
18439
|
-
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
18440
|
-
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
18441
|
-
},
|
|
18442
|
-
...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
|
|
18443
|
-
};
|
|
18444
|
-
}
|
|
17940
|
+
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
|
|
18445
17941
|
return {
|
|
18446
17942
|
protocol: "tinycloud-share",
|
|
18447
17943
|
version: 1,
|
|
18448
17944
|
shareId: envelope.shareId,
|
|
18449
17945
|
origin,
|
|
18450
|
-
target: { ...envelope.target, kind
|
|
18451
|
-
resource: envelope.
|
|
18452
|
-
actions: [
|
|
17946
|
+
target: { ...envelope.target, kind },
|
|
17947
|
+
resource: { ...envelope.resource },
|
|
17948
|
+
actions: [...envelope.actions],
|
|
18453
17949
|
expiresAt: envelope.expiry,
|
|
18454
17950
|
display: {
|
|
18455
17951
|
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
18456
17952
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
18457
17953
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
18458
|
-
}
|
|
18459
|
-
...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
|
|
17954
|
+
}
|
|
18460
17955
|
};
|
|
18461
17956
|
}
|
|
18462
|
-
function
|
|
18463
|
-
|
|
18464
|
-
|
|
18465
|
-
const value = JSON.parse(text);
|
|
18466
|
-
if (canonicalize2(value) !== text) throw new Error("share envelope is not canonical JSON");
|
|
18467
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
18468
|
-
const version2 = value.version;
|
|
18469
|
-
if (version2 === 3) return shareEnvelopeV3Schema.parse(value);
|
|
18470
|
-
if (version2 === 2) return shareEnvelopeV2Schema.parse(value);
|
|
18471
|
-
}
|
|
18472
|
-
return shareEnvelopeSchema.parse(value);
|
|
18473
|
-
} catch {
|
|
18474
|
-
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
18475
|
-
}
|
|
18476
|
-
}
|
|
18477
|
-
async function resolveShareEnvelope(link2, options = {}) {
|
|
17957
|
+
async function resolvePolicyShare(link2, options) {
|
|
17958
|
+
options.signal?.throwIfAborted();
|
|
17959
|
+
let url;
|
|
18478
17960
|
let parsed;
|
|
18479
17961
|
try {
|
|
18480
|
-
|
|
17962
|
+
url = new URL(link2);
|
|
17963
|
+
if (url.pathname !== "/viewer" || url.hash !== "" || url.searchParams.size !== 1 || !url.searchParams.has("tc2")) throw new Error("not public Policy/v3");
|
|
17964
|
+
parsed = parseInlineShareUrl(link2, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
|
|
17965
|
+
if (parsed.key32 !== void 0) throw new Error("secret inline transport is retired");
|
|
18481
17966
|
} catch {
|
|
18482
17967
|
throw new ShareReceiveError("invalid-link", "share link format is invalid");
|
|
18483
17968
|
}
|
|
17969
|
+
const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
17970
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "public policy envelope exceeds the configured byte limit");
|
|
17971
|
+
if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "public envelope bytes do not match the link CID");
|
|
17972
|
+
options.signal?.throwIfAborted();
|
|
17973
|
+
let envelope;
|
|
18484
17974
|
try {
|
|
18485
|
-
|
|
18486
|
-
const
|
|
18487
|
-
|
|
18488
|
-
|
|
18489
|
-
let plaintext;
|
|
18490
|
-
try {
|
|
18491
|
-
plaintext = parsed.key32 === void 0 ? sealed : await open(sealed, parsed.key32);
|
|
18492
|
-
} catch {
|
|
18493
|
-
throw new ShareReceiveError("decrypt-failed", "share envelope could not be opened");
|
|
18494
|
-
}
|
|
18495
|
-
return { envelope: parseEnvelope(plaintext), origin: url.origin, cid: parsed.ciphertextCid, kind: parsed.kind };
|
|
18496
|
-
} finally {
|
|
18497
|
-
parsed.key32?.fill(0);
|
|
18498
|
-
}
|
|
18499
|
-
}
|
|
18500
|
-
function actionName(value) {
|
|
18501
|
-
return value === "read" ? "tinycloud.kv/get" : value === "list" ? "tinycloud.kv/list" : value === "edit" ? "tinycloud.kv/put" : value;
|
|
18502
|
-
}
|
|
18503
|
-
function resourceContains(outer, inner) {
|
|
18504
|
-
if (outer.kind === "exact") return inner.kind === "exact" && outer.path === inner.path;
|
|
18505
|
-
const prefix = outer.path.endsWith("/") ? outer.path : `${outer.path}/`;
|
|
18506
|
-
return inner.path === outer.path || inner.path.startsWith(prefix);
|
|
18507
|
-
}
|
|
18508
|
-
function policyEvidenceMatches(envelope, evidence) {
|
|
18509
|
-
const registeredActions = new Set(evidence.actions.map(actionName));
|
|
18510
|
-
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);
|
|
18511
|
-
}
|
|
18512
|
-
async function verifyV2Envelope(envelope, linkOrigin, options) {
|
|
18513
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
18514
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
18515
|
-
}
|
|
18516
|
-
try {
|
|
18517
|
-
if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
17975
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(parsed.ciphertext);
|
|
17976
|
+
const value = JSON.parse(encoded);
|
|
17977
|
+
if (canonicalize2(value) !== encoded) throw new Error("non-canonical envelope");
|
|
17978
|
+
envelope = shareEnvelopeV3Schema.parse(value);
|
|
18518
17979
|
} catch {
|
|
18519
|
-
throw new ShareReceiveError("
|
|
18520
|
-
}
|
|
18521
|
-
const expiry = Date.parse(envelope.expiry);
|
|
18522
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
18523
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
18524
|
-
if (envelope.authorizationTarget.kind === "policy") {
|
|
18525
|
-
if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "an external policy authority is required");
|
|
18526
|
-
let evidence;
|
|
18527
|
-
try {
|
|
18528
|
-
evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
|
|
18529
|
-
} catch {
|
|
18530
|
-
evidence = void 0;
|
|
18531
|
-
}
|
|
18532
|
-
if (evidence === void 0 || !policyEvidenceMatches(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "share policy authority evidence is not bound to the envelope");
|
|
18533
|
-
if (options.trustedSignerDid !== void 0 && (evidence.signerDid !== options.trustedSignerDid || envelope.signature.signerDid !== options.trustedSignerDid)) {
|
|
18534
|
-
throw new ShareReceiveError("signature-invalid", "share signer is not trusted");
|
|
18535
|
-
}
|
|
18536
|
-
} else if (envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
|
|
18537
|
-
throw new ShareReceiveError("capability-invalid", "recipient authorization does not match the signed recipient");
|
|
18538
|
-
} else {
|
|
18539
|
-
if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
|
|
18540
|
-
}
|
|
18541
|
-
}
|
|
18542
|
-
async function verifyV3Envelope(envelope, linkOrigin, options) {
|
|
18543
|
-
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
18544
|
-
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
17980
|
+
throw new ShareReceiveError("envelope-invalid", "share envelope is invalid");
|
|
18545
17981
|
}
|
|
18546
17982
|
try {
|
|
18547
17983
|
if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
|
|
18548
17984
|
} catch {
|
|
18549
17985
|
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
18550
17986
|
}
|
|
17987
|
+
options.signal?.throwIfAborted();
|
|
18551
17988
|
const expiry = Date.parse(envelope.expiry);
|
|
18552
17989
|
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
18553
17990
|
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
18554
|
-
|
|
18555
|
-
|
|
18556
|
-
if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
|
|
18557
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
18558
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
18559
|
-
}
|
|
18560
|
-
throw new ShareReceiveError("unsupported-target", "share policy target is not supported", { reason: "policy-target" });
|
|
18561
|
-
}
|
|
18562
|
-
async function verifyBearerEnvelope(envelope, linkOrigin, options = {}) {
|
|
18563
|
-
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
18564
|
-
throw new ShareReceiveError("origin-mismatch", "share origin does not match the trusted origin");
|
|
18565
|
-
}
|
|
18566
|
-
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" });
|
|
18567
|
-
if (envelope.target.resource.kind !== "exact") throw new ShareReceiveError("unsupported-target", "this receive path only handles exact resources", { reason: "prefix-resource" });
|
|
18568
|
-
const expiry = Date.parse(envelope.expiry);
|
|
18569
|
-
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
18570
|
-
try {
|
|
18571
|
-
if (!await verifyEnvelope(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
18572
|
-
} catch {
|
|
18573
|
-
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
18574
|
-
}
|
|
18575
|
-
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
18576
|
-
const capability = checkBearerDelegation(envelope, options.now === void 0 ? {} : { now: options.now });
|
|
18577
|
-
if (!capability.ok) throw new ShareReceiveError("capability-invalid", "share delegation does not authorize the target");
|
|
18578
|
-
}
|
|
18579
|
-
async function verifyResolved(envelope, linkOrigin, options) {
|
|
18580
|
-
await verifyBearerEnvelope(envelope, linkOrigin, options);
|
|
17991
|
+
options.onResolvedAddressedEnvelope?.(envelope, parsed.ciphertextCid);
|
|
17992
|
+
return { envelope, origin: url.origin, cid: parsed.ciphertextCid };
|
|
18581
17993
|
}
|
|
18582
17994
|
async function inspectShare(link2, options = {}) {
|
|
18583
|
-
const resolved = await
|
|
18584
|
-
|
|
18585
|
-
else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
18586
|
-
else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
18587
|
-
else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
18588
|
-
if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18589
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
|
|
17995
|
+
const resolved = await resolvePolicyShare(link2, options);
|
|
17996
|
+
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
|
|
18590
17997
|
}
|
|
18591
17998
|
async function receiveShare(link2, options = {}) {
|
|
18592
|
-
const resolved = await
|
|
18593
|
-
|
|
18594
|
-
await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
18595
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18596
|
-
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
18597
|
-
}
|
|
18598
|
-
if (resolved.envelope.version === 2) {
|
|
18599
|
-
await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
18600
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18601
|
-
const method = resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
|
|
18602
|
-
if (options.authorization === void 0) return { state: "authorization-required", method };
|
|
18603
|
-
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 } });
|
|
18604
|
-
if (result.state === "authorization-required") return result;
|
|
18605
|
-
if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "share authorization was denied");
|
|
18606
|
-
if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
|
|
18607
|
-
throw new ShareReceiveError("content-integrity-failed", "share authorization returned an unsigned content result");
|
|
18608
|
-
}
|
|
18609
|
-
const authorized = result.value;
|
|
18610
|
-
try {
|
|
18611
|
-
if (!await options.authorization.verifyResult({ envelope: resolved.envelope, value: authorized, proof: authorized.proof })) throw new Error("node proof");
|
|
18612
|
-
} catch {
|
|
18613
|
-
throw new ShareReceiveError("content-integrity-failed", "node authorization proof is invalid");
|
|
18614
|
-
}
|
|
18615
|
-
const bytes4 = authorized.bytes.slice();
|
|
18616
|
-
const maxBytes = options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES;
|
|
18617
|
-
if (bytes4.byteLength > maxBytes) throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
18618
|
-
const digestBytes3 = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes4));
|
|
18619
|
-
const bodyDigest = toBase64Url(digestBytes3);
|
|
18620
|
-
if (authorized.bodyDigest !== bodyDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content digest does not match");
|
|
18621
|
-
if (authorized.contentSourceDigest !== resolved.envelope.contentSourceDigest) throw new ShareReceiveError("content-integrity-failed", "authorized content source does not match");
|
|
18622
|
-
const binding = authorized.binding;
|
|
18623
|
-
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");
|
|
18624
|
-
if (resolved.envelope.metadata.byteLength !== void 0 && resolved.envelope.metadata.byteLength !== bytes4.byteLength) {
|
|
18625
|
-
throw new ShareReceiveError("content-integrity-failed", "authorized content length does not match");
|
|
18626
|
-
}
|
|
18627
|
-
let text2;
|
|
18628
|
-
try {
|
|
18629
|
-
text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes4);
|
|
18630
|
-
} catch {
|
|
18631
|
-
}
|
|
18632
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes4, ...text2 === void 0 ? {} : { text: text2 } };
|
|
18633
|
-
}
|
|
18634
|
-
if (resolved.envelope.authorizationTarget.kind === "policy") {
|
|
18635
|
-
await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
|
|
18636
|
-
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
18637
|
-
return { state: "authorization-required", method: "email-claim" };
|
|
18638
|
-
}
|
|
18639
|
-
await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
18640
|
-
let bytes3 = new Uint8Array();
|
|
18641
|
-
if (resolved.envelope.content !== void 0) {
|
|
18642
|
-
bytes3 = await registryFetcher(
|
|
18643
|
-
options,
|
|
18644
|
-
(options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES) + CONTENT_SEALED_OVERHEAD,
|
|
18645
|
-
"max-bytes-exceeded",
|
|
18646
|
-
"content"
|
|
18647
|
-
)({ origin: resolved.origin, cid: resolved.envelope.content.cid });
|
|
18648
|
-
if (await computeCid(bytes3) !== resolved.envelope.content.cid) throw new ShareReceiveError("content-integrity-failed", "shared content CID does not match");
|
|
18649
|
-
const key = fromBase64Url(resolved.envelope.content.key);
|
|
18650
|
-
try {
|
|
18651
|
-
bytes3 = await open(bytes3, key);
|
|
18652
|
-
} catch {
|
|
18653
|
-
throw new ShareReceiveError("content-integrity-failed", "shared content could not be opened");
|
|
18654
|
-
} finally {
|
|
18655
|
-
key.fill(0);
|
|
18656
|
-
}
|
|
18657
|
-
if (bytes3.byteLength > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) {
|
|
18658
|
-
throw new ShareReceiveError("max-bytes-exceeded", "shared content exceeds the configured byte limit");
|
|
18659
|
-
}
|
|
18660
|
-
}
|
|
18661
|
-
let text;
|
|
18662
|
-
if (bytes3.byteLength !== 0) {
|
|
18663
|
-
try {
|
|
18664
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
|
|
18665
|
-
} catch {
|
|
18666
|
-
}
|
|
18667
|
-
}
|
|
18668
|
-
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind }, bytes: bytes3, ...text === void 0 ? {} : { text } };
|
|
17999
|
+
const resolved = await resolvePolicyShare(link2, options);
|
|
18000
|
+
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
18669
18001
|
}
|
|
18670
18002
|
function redactPublishedShare(result) {
|
|
18671
18003
|
return {
|
|
18672
18004
|
protocol: "tinycloud-share",
|
|
18673
18005
|
version: SHARE_PUBLISH_RESULT_VERSION,
|
|
18674
|
-
link: {
|
|
18006
|
+
link: { ...result.link },
|
|
18675
18007
|
metadata: {
|
|
18676
18008
|
protocol: "tinycloud-share",
|
|
18677
18009
|
version: 1,
|
|
@@ -18681,242 +18013,9 @@ function redactPublishedShare(result) {
|
|
|
18681
18013
|
resource: { ...result.metadata.resource },
|
|
18682
18014
|
actions: [...result.metadata.actions],
|
|
18683
18015
|
expiresAt: result.metadata.expiresAt,
|
|
18684
|
-
display: { ...result.metadata.display }
|
|
18685
|
-
...result.metadata.content === void 0 ? {} : { content: { cid: result.metadata.content.cid } }
|
|
18686
|
-
},
|
|
18687
|
-
registryDeleteAfter: result.registryDeleteAfter
|
|
18688
|
-
};
|
|
18689
|
-
}
|
|
18690
|
-
function assertSafeFilename(filename) {
|
|
18691
|
-
if (filename.length === 0 || filename === "." || filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(filename)) {
|
|
18692
|
-
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
18693
|
-
}
|
|
18694
|
-
}
|
|
18695
|
-
function assertOrigin(origin, label) {
|
|
18696
|
-
if (!isCanonicalHttpsOrigin(origin)) {
|
|
18697
|
-
throw new SharePublishError("invalid-argument", `${label} must be a canonical HTTPS origin`);
|
|
18698
|
-
}
|
|
18699
|
-
}
|
|
18700
|
-
async function snapshotSource(source, limit) {
|
|
18701
|
-
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
18702
|
-
throw new SharePublishError("invalid-argument", "maxBytes must be a positive safe integer");
|
|
18703
|
-
}
|
|
18704
|
-
if (source instanceof Uint8Array) {
|
|
18705
|
-
if (source.byteLength > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
18706
|
-
return source.slice();
|
|
18707
|
-
}
|
|
18708
|
-
const chunks = [];
|
|
18709
|
-
let total = 0;
|
|
18710
|
-
try {
|
|
18711
|
-
for await (const chunk of source) {
|
|
18712
|
-
if (!(chunk instanceof Uint8Array)) throw new SharePublishError("invalid-argument", "share source yielded invalid bytes");
|
|
18713
|
-
total += chunk.byteLength;
|
|
18714
|
-
if (total > limit) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the configured byte limit");
|
|
18715
|
-
chunks.push(chunk);
|
|
18716
|
-
}
|
|
18717
|
-
} catch (error) {
|
|
18718
|
-
if (error instanceof SharePublishError) throw error;
|
|
18719
|
-
throw new SharePublishError("upload-failed", "share source could not be read");
|
|
18720
|
-
}
|
|
18721
|
-
const bytes3 = new Uint8Array(total);
|
|
18722
|
-
let offset = 0;
|
|
18723
|
-
for (const chunk of chunks) {
|
|
18724
|
-
bytes3.set(chunk, offset);
|
|
18725
|
-
offset += chunk.byteLength;
|
|
18726
|
-
}
|
|
18727
|
-
return bytes3;
|
|
18728
|
-
}
|
|
18729
|
-
function assertTarget(target) {
|
|
18730
|
-
if (target.kind !== "bearer") {
|
|
18731
|
-
throw new SharePublishError("unsupported-target", "this release publishes bearer shares only");
|
|
18732
|
-
}
|
|
18733
|
-
}
|
|
18734
|
-
function assertRegistryUrl(value, allowInsecure) {
|
|
18735
|
-
let url;
|
|
18736
|
-
try {
|
|
18737
|
-
url = new URL(value);
|
|
18738
|
-
} catch {
|
|
18739
|
-
throw new SharePublishError("invalid-argument", "registry URL is invalid");
|
|
18740
|
-
}
|
|
18741
|
-
if (url.username || url.password || url.search || url.hash) throw new SharePublishError("invalid-argument", "registry URL must not contain credentials, query, or fragment");
|
|
18742
|
-
if (!allowInsecure && url.protocol !== "https:") throw new SharePublishError("invalid-argument", "production registry uploads require HTTPS");
|
|
18743
|
-
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");
|
|
18744
|
-
return url;
|
|
18745
|
-
}
|
|
18746
|
-
async function defaultUpload(options, input) {
|
|
18747
|
-
if (options.registryBaseUrl === void 0) throw new SharePublishError("upload-auth-required", "an authenticated share registry is required");
|
|
18748
|
-
const base33 = assertRegistryUrl(options.registryBaseUrl, options.allowInsecureRegistry === true).toString().replace(/\/$/, "");
|
|
18749
|
-
const localInsecure = options.allowInsecureRegistry === true && (new URL(base33).hostname === "127.0.0.1" || new URL(base33).hostname === "localhost");
|
|
18750
|
-
if (options.authorizeUpload === void 0 && !localInsecure) {
|
|
18751
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization is required");
|
|
18752
|
-
}
|
|
18753
|
-
let authorization;
|
|
18754
|
-
if (options.authorizeUpload !== void 0) {
|
|
18755
|
-
try {
|
|
18756
|
-
authorization = await options.authorizeUpload(input);
|
|
18757
|
-
} catch {
|
|
18758
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
18759
|
-
}
|
|
18760
|
-
}
|
|
18761
|
-
if (authorization !== void 0 && options.authorizationOrigin !== void 0) {
|
|
18762
|
-
let authorizationUrl;
|
|
18763
|
-
try {
|
|
18764
|
-
authorizationUrl = new URL(options.authorizationOrigin);
|
|
18765
|
-
} catch {
|
|
18766
|
-
throw new SharePublishError("invalid-argument", "upload authorization origin is invalid");
|
|
18767
|
-
}
|
|
18768
|
-
if (authorizationUrl.origin !== new URL(base33).origin) throw new SharePublishError("upload-auth-required", "upload authorization is scoped to another origin");
|
|
18769
|
-
}
|
|
18770
|
-
const headers = new Headers({
|
|
18771
|
-
"content-type": "application/vnd.ipld.raw",
|
|
18772
|
-
"if-none-match": "*",
|
|
18773
|
-
"x-delete-after": input.deleteAfter
|
|
18774
|
-
});
|
|
18775
|
-
if (typeof authorization === "string") {
|
|
18776
|
-
headers.set("x-tinycloud-upload-attestation", authorization);
|
|
18777
|
-
try {
|
|
18778
|
-
const attestation = JSON.parse(authorization);
|
|
18779
|
-
if (attestation.retention === void 0) throw new Error("retention");
|
|
18780
|
-
headers.set("x-tinycloud-retention", canonicalize2(attestation.retention));
|
|
18781
|
-
} catch {
|
|
18782
|
-
throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
18783
|
-
}
|
|
18784
|
-
} else if (authorization !== void 0) {
|
|
18785
|
-
new Headers(authorization).forEach((value, key) => headers.set(key, value));
|
|
18786
|
-
}
|
|
18787
|
-
let response;
|
|
18788
|
-
try {
|
|
18789
|
-
response = await (options.fetchFn ?? globalThis.fetch)(`${base33}/blobs`, {
|
|
18790
|
-
method: "POST",
|
|
18791
|
-
headers,
|
|
18792
|
-
body: input.blob,
|
|
18793
|
-
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
18794
|
-
redirect: "error",
|
|
18795
|
-
referrerPolicy: "no-referrer"
|
|
18796
|
-
});
|
|
18797
|
-
} catch {
|
|
18798
|
-
throw new SharePublishError("upload-failed", "share registry is unavailable");
|
|
18799
|
-
}
|
|
18800
|
-
if (response.status === 401 || response.status === 403) throw new SharePublishError("upload-auth-required", "share upload authorization was rejected");
|
|
18801
|
-
if (!response.ok) throw new SharePublishError("upload-failed", "share registry rejected the encrypted blob");
|
|
18802
|
-
let body;
|
|
18803
|
-
try {
|
|
18804
|
-
body = await response.json();
|
|
18805
|
-
} catch {
|
|
18806
|
-
throw new SharePublishError("upload-failed", "share registry returned an invalid response");
|
|
18807
|
-
}
|
|
18808
|
-
const record2 = typeof body === "object" && body !== null ? body : {};
|
|
18809
|
-
if (record2.cid !== input.cid || typeof record2.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share registry returned an unexpected CID or retention value");
|
|
18810
|
-
return { cid: input.cid, deleteAfter: record2.deleteAfter };
|
|
18811
|
-
}
|
|
18812
|
-
async function uploadShareBlob(options, input) {
|
|
18813
|
-
const result = await (options.uploadBlob ?? ((value) => defaultUpload(options, value)))(input);
|
|
18814
|
-
if (result.cid !== input.cid || typeof result.deleteAfter !== "string") throw new SharePublishError("upload-failed", "share uploader returned an invalid result");
|
|
18815
|
-
return result;
|
|
18816
|
-
}
|
|
18817
|
-
async function publishShare(options) {
|
|
18818
|
-
const target = options.target ?? { kind: "bearer" };
|
|
18819
|
-
assertTarget(target);
|
|
18820
|
-
assertSafeFilename(options.filename);
|
|
18821
|
-
assertOrigin(options.origin, "share origin");
|
|
18822
|
-
if (options.mediaType !== void 0 && options.mediaType.length === 0) throw new SharePublishError("invalid-argument", "mediaType must not be empty");
|
|
18823
|
-
const maxBytes = options.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
18824
|
-
if (maxBytes > SHARE_CONTENT_LIMIT) throw new SharePublishError("max-bytes-exceeded", "share content exceeds the maximum 100 MiB limit");
|
|
18825
|
-
const content = await snapshotSource(options.source, maxBytes);
|
|
18826
|
-
if (content.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
18827
|
-
if (options.allowBinary !== true) {
|
|
18828
|
-
try {
|
|
18829
|
-
new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
18830
|
-
} catch {
|
|
18831
|
-
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
18832
|
-
}
|
|
18833
|
-
}
|
|
18834
|
-
const nowMs2 = options.now?.() ?? Date.now();
|
|
18835
|
-
const expiresAt = options.expiresAt ?? new Date(nowMs2 + DEFAULT_SHARE_LIFETIME_MS);
|
|
18836
|
-
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs2) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
18837
|
-
const expiry = expiresAt.toISOString();
|
|
18838
|
-
const nodeAudience = options.nodeAudience ?? "did:web:node.tinycloud.xyz";
|
|
18839
|
-
const spaceId = options.spaceId ?? "bearer";
|
|
18840
|
-
const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
|
|
18841
|
-
const path = `shares/${shareId}/${options.filename}`;
|
|
18842
|
-
const sessionPrivateKey = ed25519.utils.randomPrivateKey();
|
|
18843
|
-
const senderPrivateKey = ed25519.utils.randomPrivateKey();
|
|
18844
|
-
let contentKey;
|
|
18845
|
-
let envelopeKey;
|
|
18846
|
-
try {
|
|
18847
|
-
const sessionPublicKey = ed25519.getPublicKey(sessionPrivateKey);
|
|
18848
|
-
const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
|
|
18849
|
-
const delegation = mintBearerDelegation({
|
|
18850
|
-
issuerPrivateKey: senderPrivateKey,
|
|
18851
|
-
audienceDid: sessionDid,
|
|
18852
|
-
resourceUri: bearerResourceUri(options.origin, spaceId, path),
|
|
18853
|
-
expiresAtSeconds: Math.ceil(expiresAt.getTime() / 1e3)
|
|
18854
|
-
});
|
|
18855
|
-
contentKey = generateKey();
|
|
18856
|
-
const sealedContent = await seal(content, contentKey);
|
|
18857
|
-
const unsigned = {
|
|
18858
|
-
version: 1,
|
|
18859
|
-
shareId,
|
|
18860
|
-
delegation,
|
|
18861
|
-
authorizationTarget: {
|
|
18862
|
-
kind: "bearerKey",
|
|
18863
|
-
sessionJwk: { kty: "OKP", crv: "Ed25519", x: toBase64Url(sessionPublicKey), d: toBase64Url(sessionPrivateKey) }
|
|
18864
|
-
},
|
|
18865
|
-
target: { origin: options.origin, nodeAudience, spaceId, resource: { kind: "exact", path } },
|
|
18866
|
-
display: { filename: options.filename },
|
|
18867
|
-
expiry,
|
|
18868
|
-
content: { cid: sealedContent.cid, key: toBase64Url(contentKey) }
|
|
18869
|
-
};
|
|
18870
|
-
const envelope = signEnvelope(unsigned, senderPrivateKey);
|
|
18871
|
-
envelopeKey = generateKey();
|
|
18872
|
-
const sealedEnvelope = await seal(new TextEncoder().encode(canonicalize2(envelope)), envelopeKey);
|
|
18873
|
-
let inlineUrl;
|
|
18874
|
-
if (options.inline === true) {
|
|
18875
|
-
try {
|
|
18876
|
-
inlineUrl = await encodeInlineShareUrl({ origin: options.origin, ciphertext: sealedEnvelope.blob, key32: envelopeKey });
|
|
18877
|
-
} catch (error) {
|
|
18878
|
-
throw new SharePublishError("inline-too-large", error instanceof Error ? error.message : "inline share is too large");
|
|
18879
|
-
}
|
|
18880
|
-
}
|
|
18881
|
-
const contentUpload = await uploadShareBlob(options, { blob: sealedContent.blob, cid: sealedContent.cid, deleteAfter: expiry, contentLength: sealedContent.blob.byteLength });
|
|
18882
|
-
let url;
|
|
18883
|
-
let kind;
|
|
18884
|
-
let retention = contentUpload.deleteAfter;
|
|
18885
|
-
if (options.inline === true) {
|
|
18886
|
-
if (inlineUrl === void 0) throw new SharePublishError("inline-too-large", "inline share is too large");
|
|
18887
|
-
url = inlineUrl;
|
|
18888
|
-
kind = "inline";
|
|
18889
|
-
} else {
|
|
18890
|
-
const envelopeUpload = await uploadShareBlob(options, { blob: sealedEnvelope.blob, cid: sealedEnvelope.cid, deleteAfter: expiry, contentLength: sealedEnvelope.blob.byteLength });
|
|
18891
|
-
url = encodeShareUrl({ origin: options.origin, ciphertextCid: envelopeUpload.cid, key32: envelopeKey });
|
|
18892
|
-
kind = "compact";
|
|
18893
|
-
retention = envelopeUpload.deleteAfter;
|
|
18016
|
+
display: { ...result.metadata.display }
|
|
18894
18017
|
}
|
|
18895
|
-
|
|
18896
|
-
protocol: "tinycloud-share",
|
|
18897
|
-
version: 1,
|
|
18898
|
-
shareId,
|
|
18899
|
-
origin: options.origin,
|
|
18900
|
-
target: { kind: "bearer", origin: options.origin, nodeAudience, spaceId },
|
|
18901
|
-
resource: { kind: "exact", path },
|
|
18902
|
-
actions: ["read"],
|
|
18903
|
-
expiresAt: expiry,
|
|
18904
|
-
display: { filename: options.filename },
|
|
18905
|
-
content: { cid: sealedContent.cid }
|
|
18906
|
-
};
|
|
18907
|
-
const result = { protocol: "tinycloud-share", version: SHARE_PUBLISH_RESULT_VERSION, link: { kind, cid: sealedEnvelope.cid }, metadata, registryDeleteAfter: retention };
|
|
18908
|
-
Object.defineProperty(result, "toJSON", {
|
|
18909
|
-
enumerable: false,
|
|
18910
|
-
value: () => redactPublishedShare(result)
|
|
18911
|
-
});
|
|
18912
|
-
Object.defineProperty(result, "url", { enumerable: false, value: url });
|
|
18913
|
-
return result;
|
|
18914
|
-
} finally {
|
|
18915
|
-
sessionPrivateKey.fill(0);
|
|
18916
|
-
senderPrivateKey.fill(0);
|
|
18917
|
-
contentKey?.fill(0);
|
|
18918
|
-
envelopeKey?.fill(0);
|
|
18919
|
-
}
|
|
18018
|
+
};
|
|
18920
18019
|
}
|
|
18921
18020
|
function authorizationMethodForTarget(target) {
|
|
18922
18021
|
if (target.kind === "recipientDid") return "openkey-device";
|
|
@@ -19216,8 +18315,8 @@ function targetAuthorizationMethod(target) {
|
|
|
19216
18315
|
}
|
|
19217
18316
|
async function publishTargetShare(input) {
|
|
19218
18317
|
const target = normalizeShareTarget(input.target);
|
|
19219
|
-
if (target.kind === "bearer") return publishShare(input);
|
|
19220
18318
|
if (input.targetAdapter === void 0) {
|
|
18319
|
+
if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
|
|
19221
18320
|
return {
|
|
19222
18321
|
state: "authorization-required",
|
|
19223
18322
|
method: targetAuthorizationMethod(target)
|
|
@@ -19227,9 +18326,9 @@ async function publishTargetShare(input) {
|
|
|
19227
18326
|
const chunks = [];
|
|
19228
18327
|
let size2 = 0;
|
|
19229
18328
|
for await (const chunk of input.source) {
|
|
19230
|
-
if (!(chunk instanceof Uint8Array)) throw new TypeError("
|
|
18329
|
+
if (!(chunk instanceof Uint8Array)) throw new TypeError("share publication source yielded invalid bytes");
|
|
19231
18330
|
size2 += chunk.byteLength;
|
|
19232
|
-
if (size2 > (input.maxBytes ??
|
|
18331
|
+
if (size2 > (input.maxBytes ?? SHARE_CONTENT_LIMIT)) throw new SharePublishError("max-bytes-exceeded", "share publication exceeds maxBytes");
|
|
19233
18332
|
chunks.push(chunk.slice());
|
|
19234
18333
|
}
|
|
19235
18334
|
const bytes3 = new Uint8Array(size2);
|
|
@@ -19240,15 +18339,29 @@ async function publishTargetShare(input) {
|
|
|
19240
18339
|
}
|
|
19241
18340
|
return bytes3;
|
|
19242
18341
|
})();
|
|
18342
|
+
if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
|
|
18343
|
+
if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
|
|
18344
|
+
throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
|
|
18345
|
+
}
|
|
18346
|
+
if (target.kind === "bearer" && input.allowBinary !== true) {
|
|
18347
|
+
try {
|
|
18348
|
+
new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
18349
|
+
} catch {
|
|
18350
|
+
throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
|
|
18351
|
+
}
|
|
18352
|
+
}
|
|
19243
18353
|
const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
|
|
19244
18354
|
const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
|
|
19245
18355
|
let totalBytes = 0;
|
|
19246
18356
|
for (const file of files) {
|
|
19247
18357
|
totalBytes += file.bytes.byteLength;
|
|
19248
18358
|
if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
|
|
19249
|
-
throw new SharePublishError("max-bytes-exceeded", "
|
|
18359
|
+
throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
|
|
19250
18360
|
}
|
|
19251
18361
|
}
|
|
18362
|
+
const nowMs2 = input.now?.() ?? Date.now();
|
|
18363
|
+
const expiresAt = input.expiresAt ?? new Date(nowMs2 + DEFAULT_SHARE_LIFETIME_MS);
|
|
18364
|
+
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs2) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
|
|
19252
18365
|
return input.targetAdapter.publish({
|
|
19253
18366
|
source,
|
|
19254
18367
|
filename: input.filename,
|
|
@@ -19257,18 +18370,8 @@ async function publishTargetShare(input) {
|
|
|
19257
18370
|
...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
|
|
19258
18371
|
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
19259
18372
|
target,
|
|
19260
|
-
expiresAt
|
|
18373
|
+
expiresAt,
|
|
19261
18374
|
origin: input.origin,
|
|
19262
|
-
...input.inline === void 0 ? {} : { inline: input.inline },
|
|
19263
|
-
upload: {
|
|
19264
|
-
...input.registryBaseUrl === void 0 ? {} : { registryBaseUrl: input.registryBaseUrl },
|
|
19265
|
-
...input.fetchFn === void 0 ? {} : { fetchFn: input.fetchFn },
|
|
19266
|
-
...input.authorizeUpload === void 0 ? {} : { authorizeUpload: input.authorizeUpload },
|
|
19267
|
-
...input.authorizationOrigin === void 0 ? {} : { authorizationOrigin: input.authorizationOrigin },
|
|
19268
|
-
...input.credentials === void 0 ? {} : { credentials: input.credentials },
|
|
19269
|
-
...input.allowInsecureRegistry === void 0 ? {} : { allowInsecureRegistry: input.allowInsecureRegistry },
|
|
19270
|
-
...input.uploadBlob === void 0 ? {} : { uploadBlob: input.uploadBlob }
|
|
19271
|
-
},
|
|
19272
18375
|
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
19273
18376
|
});
|
|
19274
18377
|
}
|
|
@@ -19457,8 +18560,8 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
|
|
|
19457
18560
|
...result.metadata.display.filename === void 0 ? {} : { filename: result.metadata.display.filename }
|
|
19458
18561
|
};
|
|
19459
18562
|
}
|
|
19460
|
-
function recipientMatchesShareRecord(
|
|
19461
|
-
const matcher =
|
|
18563
|
+
function recipientMatchesShareRecord(record, recipient) {
|
|
18564
|
+
const matcher = record.recipientMatcher;
|
|
19462
18565
|
if (matcher.kind === "bearer" || matcher.kind === "recipientDid") return false;
|
|
19463
18566
|
if (matcher.kind === "exactEmail") return matcher.value === recipient;
|
|
19464
18567
|
const at = recipient.lastIndexOf("@");
|
|
@@ -19495,65 +18598,80 @@ async function defaultIdempotencyKey(shareId, recipient) {
|
|
|
19495
18598
|
));
|
|
19496
18599
|
return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
|
|
19497
18600
|
}
|
|
19498
|
-
function targetKind2(
|
|
19499
|
-
if (
|
|
19500
|
-
return
|
|
18601
|
+
function targetKind2(record) {
|
|
18602
|
+
if (record.targetKind !== void 0) return record.targetKind;
|
|
18603
|
+
return record.recipientMatcher.kind === "exactEmail" ? "email" : record.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
|
|
18604
|
+
}
|
|
18605
|
+
function policyNodeAudience(record) {
|
|
18606
|
+
const envelope = record.deliveryMaterial?.envelope;
|
|
18607
|
+
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record.target.nodeAudience;
|
|
18608
|
+
const binding = envelope.attestedEnforcerBinding;
|
|
18609
|
+
if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record.target.nodeAudience;
|
|
18610
|
+
const nodeAudience = binding.nodeAudience;
|
|
18611
|
+
return typeof nodeAudience === "string" ? nodeAudience : record.target.nodeAudience;
|
|
19501
18612
|
}
|
|
19502
18613
|
async function revokeShare(input) {
|
|
19503
18614
|
const target = targetKind2(input.record);
|
|
19504
|
-
if (target === "bearer") return { state: "retention-only", target, reason: "bearer-capability-cannot-be-revoked" };
|
|
19505
18615
|
if (input.adapter === void 0) return { state: "unsupported", target, reason: "node revocation authority is required", code: "unsupported-target" };
|
|
19506
18616
|
const scope = input.scope ?? "direct";
|
|
19507
18617
|
const delegationCid = scope === "ancestor" ? input.record.ownerDelegationCid : input.record.enforcementDelegationCid;
|
|
19508
18618
|
if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
|
|
19509
|
-
|
|
18619
|
+
if (target === "bearer") {
|
|
18620
|
+
if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
|
|
18621
|
+
await input.adapter.revokeDelegation({ delegationCid, scope });
|
|
18622
|
+
} else {
|
|
18623
|
+
if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
|
|
18624
|
+
if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
|
|
18625
|
+
await input.adapter.revokePolicyRoot({
|
|
18626
|
+
rootCid: delegationCid,
|
|
18627
|
+
targetRole: scope === "ancestor" ? "policy-authority" : "policy-enforcement",
|
|
18628
|
+
ownerDid: input.record.ownerDid,
|
|
18629
|
+
nodeOrigin: input.record.target.origin,
|
|
18630
|
+
nodeAudience: policyNodeAudience(input.record)
|
|
18631
|
+
});
|
|
18632
|
+
}
|
|
19510
18633
|
const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
19511
18634
|
if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
|
|
19512
18635
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
19513
18636
|
}
|
|
19514
|
-
function redactRecord(
|
|
19515
|
-
const matcher =
|
|
19516
|
-
const revealedLink = revealLink ? link2 ??
|
|
18637
|
+
function redactRecord(record, revealLink, link2) {
|
|
18638
|
+
const matcher = record.recipientMatcher;
|
|
18639
|
+
const revealedLink = revealLink ? link2 ?? record.link : void 0;
|
|
19517
18640
|
return {
|
|
19518
|
-
shareId:
|
|
18641
|
+
shareId: record.shareId,
|
|
19519
18642
|
target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
|
|
19520
18643
|
...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
|
|
19521
|
-
expiresAt:
|
|
19522
|
-
revoked:
|
|
18644
|
+
expiresAt: record.expiresAt,
|
|
18645
|
+
revoked: record.revokedAt !== void 0,
|
|
19523
18646
|
...revealedLink === void 0 ? {} : { link: revealedLink }
|
|
19524
18647
|
};
|
|
19525
18648
|
}
|
|
19526
18649
|
async function listShares(storage) {
|
|
19527
18650
|
const records = await storage.list();
|
|
19528
|
-
return records.map((
|
|
18651
|
+
return records.map((record) => redactRecord(record, false));
|
|
19529
18652
|
}
|
|
19530
18653
|
async function showShare(input) {
|
|
19531
|
-
const
|
|
19532
|
-
if (
|
|
19533
|
-
return redactRecord(
|
|
19534
|
-
}
|
|
19535
|
-
function isLegacyShareLink(value) {
|
|
19536
|
-
return value.startsWith("tc1:");
|
|
19537
|
-
}
|
|
19538
|
-
async function receiveLegacyShare(link2, reader) {
|
|
19539
|
-
if (!isLegacyShareLink(link2)) throw new Error("legacy link must use tc1:");
|
|
19540
|
-
return reader.read(link2);
|
|
18654
|
+
const record = await input.storage.get(input.shareId);
|
|
18655
|
+
if (record === void 0) throw new Error("share not found");
|
|
18656
|
+
return redactRecord(record, input.revealLink === true, input.link);
|
|
19541
18657
|
}
|
|
19542
|
-
|
|
19543
|
-
const
|
|
19544
|
-
|
|
18658
|
+
function parseNativeShareUrl(value) {
|
|
18659
|
+
const url = new URL(value);
|
|
18660
|
+
if (url.search || url.pathname !== "/viewer") throw new TypeError("native shares must carry tc1 only in the /viewer URL fragment");
|
|
18661
|
+
const fragment = new URLSearchParams(url.hash.slice(1));
|
|
18662
|
+
const token = fragment.get(NATIVE_SHARE_FRAGMENT_PARAMETER);
|
|
18663
|
+
if (!token || fragment.size !== 1) throw new TypeError("missing native share fragment");
|
|
18664
|
+
return token;
|
|
19545
18665
|
}
|
|
19546
|
-
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,
|
|
18666
|
+
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, AAD, 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, INLINE_PREFIX, PUBLIC_INLINE_PARAMETER, 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;
|
|
19547
18667
|
var init_dist3 = __esm({
|
|
19548
18668
|
"../share-sdk/dist/index.js"() {
|
|
19549
18669
|
"use strict";
|
|
19550
18670
|
init_sha2();
|
|
19551
18671
|
init_ed25519();
|
|
19552
|
-
init_ed25519();
|
|
19553
18672
|
init_blake3();
|
|
19554
18673
|
init_ed25519();
|
|
19555
18674
|
init_sha256();
|
|
19556
|
-
init_ed25519();
|
|
19557
18675
|
__defProp2 = Object.defineProperty;
|
|
19558
18676
|
__export2 = (target, all) => {
|
|
19559
18677
|
for (var name2 in all)
|
|
@@ -24160,19 +23278,9 @@ var init_dist3 = __esm({
|
|
|
24160
23278
|
}).strict();
|
|
24161
23279
|
unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
|
|
24162
23280
|
shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
|
|
24163
|
-
BEARER_READ_ABILITY = "kv/get";
|
|
24164
|
-
READ_ABILITIES = /* @__PURE__ */ new Set([BEARER_READ_ABILITY]);
|
|
24165
|
-
ED25519_VERIFY_OPTS = { zip215: false };
|
|
24166
23281
|
ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
|
|
24167
|
-
SEALED_BLOB_VERSION = 1;
|
|
24168
23282
|
AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
|
|
24169
|
-
KEY_LENGTH = 32;
|
|
24170
|
-
NONCE_LENGTH = 12;
|
|
24171
|
-
TAG_LENGTH = 16;
|
|
24172
|
-
HEADER_LENGTH = 1;
|
|
24173
23283
|
ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
24174
|
-
ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
|
|
24175
|
-
ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
24176
23284
|
ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
24177
23285
|
POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
24178
23286
|
POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
@@ -24181,12 +23289,11 @@ var init_dist3 = __esm({
|
|
|
24181
23289
|
NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
24182
23290
|
ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
24183
23291
|
KEY_LENGTH2 = 32;
|
|
24184
|
-
INLINE_PREFIX = "#
|
|
23292
|
+
INLINE_PREFIX = "#tc2=";
|
|
23293
|
+
PUBLIC_INLINE_PARAMETER = "tc2";
|
|
24185
23294
|
MAX_INLINE_BYTES = 256 * 1024;
|
|
24186
23295
|
SHARE_RESULT_VERSION = 1;
|
|
24187
23296
|
DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
|
|
24188
|
-
DEFAULT_MAX_CONTENT_BLOB_BYTES = DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
24189
|
-
CONTENT_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
24190
23297
|
ShareReceiveError = class extends Error {
|
|
24191
23298
|
code;
|
|
24192
23299
|
details;
|
|
@@ -24196,13 +23303,11 @@ var init_dist3 = __esm({
|
|
|
24196
23303
|
this.code = code3;
|
|
24197
23304
|
this.details = details;
|
|
24198
23305
|
}
|
|
24199
|
-
/** Machine output is deliberately code-only; diagnostics belong on stderr. */
|
|
24200
23306
|
toJSON() {
|
|
24201
23307
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: this.code } };
|
|
24202
23308
|
}
|
|
24203
23309
|
};
|
|
24204
23310
|
SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
|
|
24205
|
-
SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
24206
23311
|
SHARE_PUBLISH_RESULT_VERSION = 1;
|
|
24207
23312
|
DEFAULT_SHARE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
24208
23313
|
SharePublishError = class extends Error {
|
|
@@ -24694,6 +23799,7 @@ var init_dist3 = __esm({
|
|
|
24694
23799
|
PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
24695
23800
|
SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
24696
23801
|
INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
|
|
23802
|
+
NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
|
|
24697
23803
|
}
|
|
24698
23804
|
});
|
|
24699
23805
|
|
|
@@ -25846,16 +24952,26 @@ async function verifyLocationRecord(input) {
|
|
|
25846
24952
|
}
|
|
25847
24953
|
return false;
|
|
25848
24954
|
}
|
|
25849
|
-
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
|
|
24955
|
+
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
|
|
25850
24956
|
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
25851
|
-
const response = await fetchFn(url
|
|
24957
|
+
const response = await fetchFn(url, {
|
|
24958
|
+
redirect: "error",
|
|
24959
|
+
headers: { accept: "application/json" },
|
|
24960
|
+
...signal === void 0 ? {} : { signal }
|
|
24961
|
+
});
|
|
25852
24962
|
if (response.status === 404) {
|
|
25853
24963
|
return null;
|
|
25854
24964
|
}
|
|
25855
24965
|
if (!response.ok) {
|
|
25856
24966
|
throw new Error(`location registry returned HTTP ${response.status}`);
|
|
25857
24967
|
}
|
|
25858
|
-
const
|
|
24968
|
+
const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
|
|
24969
|
+
let body;
|
|
24970
|
+
try {
|
|
24971
|
+
body = JSON.parse(text2);
|
|
24972
|
+
} catch {
|
|
24973
|
+
throw new LocationRecordValidationError("registry response is not JSON");
|
|
24974
|
+
}
|
|
25859
24975
|
if (body.record === void 0) {
|
|
25860
24976
|
throw new LocationRecordValidationError("registry response missing record");
|
|
25861
24977
|
}
|
|
@@ -26031,6 +25147,38 @@ function multiaddrToHttpUrl(input) {
|
|
|
26031
25147
|
}
|
|
26032
25148
|
return uri;
|
|
26033
25149
|
}
|
|
25150
|
+
async function boundedResponseText(response, label, maxBytes) {
|
|
25151
|
+
const declaredLength = response.headers.get("content-length");
|
|
25152
|
+
if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
|
|
25153
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
25154
|
+
}
|
|
25155
|
+
if (response.body === null) return "";
|
|
25156
|
+
const reader = response.body.getReader();
|
|
25157
|
+
const chunks = [];
|
|
25158
|
+
let total = 0;
|
|
25159
|
+
try {
|
|
25160
|
+
while (true) {
|
|
25161
|
+
const { done, value } = await reader.read();
|
|
25162
|
+
if (done) break;
|
|
25163
|
+
if (value === void 0) continue;
|
|
25164
|
+
total += value.byteLength;
|
|
25165
|
+
if (total > maxBytes) {
|
|
25166
|
+
await reader.cancel().catch(() => void 0);
|
|
25167
|
+
throw new LocationRecordValidationError(`${label} response is too large`);
|
|
25168
|
+
}
|
|
25169
|
+
chunks.push(value);
|
|
25170
|
+
}
|
|
25171
|
+
} finally {
|
|
25172
|
+
reader.releaseLock();
|
|
25173
|
+
}
|
|
25174
|
+
const bytes2 = new Uint8Array(total);
|
|
25175
|
+
let offset = 0;
|
|
25176
|
+
for (const chunk of chunks) {
|
|
25177
|
+
bytes2.set(chunk, offset);
|
|
25178
|
+
offset += chunk.byteLength;
|
|
25179
|
+
}
|
|
25180
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
25181
|
+
}
|
|
26034
25182
|
function validateSubject(subject) {
|
|
26035
25183
|
if (typeof subject !== "string" || subject.length === 0) {
|
|
26036
25184
|
throw new LocationRecordValidationError(
|
|
@@ -29244,7 +28392,7 @@ var init_profiles = __esm({
|
|
|
29244
28392
|
// src/commands/account.ts
|
|
29245
28393
|
init_profiles();
|
|
29246
28394
|
init_constants();
|
|
29247
|
-
import
|
|
28395
|
+
import open from "open";
|
|
29248
28396
|
import { readFile as readFile3 } from "fs/promises";
|
|
29249
28397
|
|
|
29250
28398
|
// src/lib/sdk.ts
|
|
@@ -30615,7 +29763,7 @@ function registerAccountCommand(program) {
|
|
|
30615
29763
|
billing.command(name2).description(`${name2 === "status" ? "Show" : "Open"} account billing page`).option("--open", "Open account.tinycloud.xyz in your browser").action(async (options) => {
|
|
30616
29764
|
try {
|
|
30617
29765
|
if (options.open) {
|
|
30618
|
-
await
|
|
29766
|
+
await open(ACCOUNT_BILLING_URL);
|
|
30619
29767
|
}
|
|
30620
29768
|
outputJson({ url: ACCOUNT_BILLING_URL, opened: Boolean(options.open) });
|
|
30621
29769
|
} catch (error) {
|
|
@@ -30907,8 +30055,8 @@ async function callbackFlow(did, options = {}) {
|
|
|
30907
30055
|
}
|
|
30908
30056
|
if (openBrowser) {
|
|
30909
30057
|
try {
|
|
30910
|
-
const
|
|
30911
|
-
await
|
|
30058
|
+
const open3 = (await import("open")).default;
|
|
30059
|
+
await open3(authUrl);
|
|
30912
30060
|
} catch {
|
|
30913
30061
|
server.close();
|
|
30914
30062
|
throw new Error("Failed to open browser");
|
|
@@ -30997,7 +30145,7 @@ function ensureWasm() {
|
|
|
30997
30145
|
wasmInitialized = true;
|
|
30998
30146
|
}
|
|
30999
30147
|
}
|
|
31000
|
-
function
|
|
30148
|
+
function generateKey() {
|
|
31001
30149
|
ensureWasm();
|
|
31002
30150
|
const mgr = new TCWSessionManager();
|
|
31003
30151
|
const keyId = mgr.createSessionKey("cli");
|
|
@@ -31260,7 +30408,7 @@ async function ensureShareDeviceAuthorization(input) {
|
|
|
31260
30408
|
}
|
|
31261
30409
|
let key = await ProfileManager.getKey(input.profileName);
|
|
31262
30410
|
if (!key) {
|
|
31263
|
-
const generated =
|
|
30411
|
+
const generated = generateKey();
|
|
31264
30412
|
key = generated.jwk;
|
|
31265
30413
|
await ProfileManager.setKey(input.profileName, key);
|
|
31266
30414
|
}
|
|
@@ -32322,7 +31470,7 @@ async function rotateAuthKey(profileName, host, options = {}) {
|
|
|
32322
31470
|
return;
|
|
32323
31471
|
}
|
|
32324
31472
|
const { jwk, did } = await withSpinner("Generating session key...", async () => {
|
|
32325
|
-
return
|
|
31473
|
+
return generateKey();
|
|
32326
31474
|
});
|
|
32327
31475
|
await ProfileManager.setKey(profileName, jwk);
|
|
32328
31476
|
await ProfileManager.clearSession(profileName);
|
|
@@ -32405,7 +31553,7 @@ async function handleLocalAuth(profileName, host, options = {}) {
|
|
|
32405
31553
|
const hasKey = await ProfileManager.getKey(profileName);
|
|
32406
31554
|
if (options.forceSessionKey || !hasKey) {
|
|
32407
31555
|
const { jwk, did: generatedSessionDid } = await withSpinner("Generating session key...", async () => {
|
|
32408
|
-
return
|
|
31556
|
+
return generateKey();
|
|
32409
31557
|
});
|
|
32410
31558
|
await ProfileManager.setKey(profileName, jwk);
|
|
32411
31559
|
sessionDid = generatedSessionDid;
|
|
@@ -33120,7 +32268,7 @@ function registerInitCommand(program) {
|
|
|
33120
32268
|
}
|
|
33121
32269
|
await ProfileManager.ensureConfigDir();
|
|
33122
32270
|
const { jwk, did } = await withSpinner("Generating key...", async () => {
|
|
33123
|
-
return
|
|
32271
|
+
return generateKey();
|
|
33124
32272
|
});
|
|
33125
32273
|
await ProfileManager.setKey(profileName, jwk);
|
|
33126
32274
|
const profileConfig = {
|
|
@@ -33640,7 +32788,7 @@ function registerProfileCommand(program) {
|
|
|
33640
32788
|
throw new CLIError("PROFILE_EXISTS", `Profile "${name2}" already exists`, ExitCode.ERROR);
|
|
33641
32789
|
}
|
|
33642
32790
|
await ProfileManager.ensureConfigDir();
|
|
33643
|
-
const { jwk, did } =
|
|
32791
|
+
const { jwk, did } = generateKey();
|
|
33644
32792
|
await ProfileManager.setKey(name2, jwk);
|
|
33645
32793
|
await ProfileManager.setProfile(name2, {
|
|
33646
32794
|
name: name2,
|
|
@@ -34775,8 +33923,8 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
34775
33923
|
});
|
|
34776
33924
|
secrets.command("manage").description("Open the TinyCloud Secrets Manager in your browser").action(async () => {
|
|
34777
33925
|
try {
|
|
34778
|
-
const
|
|
34779
|
-
await
|
|
33926
|
+
const open3 = (await import("open")).default;
|
|
33927
|
+
await open3("https://secrets.tinycloud.xyz");
|
|
34780
33928
|
outputJson({ opened: "https://secrets.tinycloud.xyz" });
|
|
34781
33929
|
} catch (error) {
|
|
34782
33930
|
handleError(error);
|
|
@@ -34786,7 +33934,6 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
34786
33934
|
|
|
34787
33935
|
// src/commands/share.ts
|
|
34788
33936
|
init_dist3();
|
|
34789
|
-
import { readFile as readFile10 } from "fs/promises";
|
|
34790
33937
|
init_errors();
|
|
34791
33938
|
|
|
34792
33939
|
// src/share/output.ts
|
|
@@ -34826,7 +33973,7 @@ function receiveJson(result, path) {
|
|
|
34826
33973
|
|
|
34827
33974
|
// src/share/io.ts
|
|
34828
33975
|
import { constants } from "fs";
|
|
34829
|
-
import { lstat, mkdir as mkdir3, mkdtemp, open as
|
|
33976
|
+
import { lstat, mkdir as mkdir3, mkdtemp, open as open2, readFile as readFile9, realpath, stat as stat2, link, rename as rename2, rm as rm2, unlink } from "fs/promises";
|
|
34830
33977
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
34831
33978
|
import { basename as basename2, join as join6, resolve as resolve2, sep } from "path";
|
|
34832
33979
|
var MAX_SHARE_STDIN_BYTES = 100 * 1024 * 1024;
|
|
@@ -34894,7 +34041,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
34894
34041
|
const outputDirectory = resolve2(directory);
|
|
34895
34042
|
await assertDirectory(outputDirectory);
|
|
34896
34043
|
const safeName = safeFilename(filename);
|
|
34897
|
-
const directoryHandle = await
|
|
34044
|
+
const directoryHandle = await open2(outputDirectory, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0));
|
|
34898
34045
|
const stableDirectory = await realpath(outputDirectory);
|
|
34899
34046
|
const outputPath = join6(stableDirectory, safeName);
|
|
34900
34047
|
const directoryIdentity = await directoryHandle.stat();
|
|
@@ -34918,7 +34065,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
34918
34065
|
if (error.code !== "ENOENT") throw error;
|
|
34919
34066
|
}
|
|
34920
34067
|
temporaryPath = stagingPath;
|
|
34921
|
-
handle = await
|
|
34068
|
+
handle = await open2(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 384);
|
|
34922
34069
|
await handle.writeFile(bytes);
|
|
34923
34070
|
await handle.close();
|
|
34924
34071
|
handle = void 0;
|
|
@@ -34948,8 +34095,6 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
34948
34095
|
|
|
34949
34096
|
// src/commands/share.ts
|
|
34950
34097
|
var SHARE_ORIGIN = "https://share.tinycloud.xyz";
|
|
34951
|
-
var DEFAULT_REGISTRY = `${SHARE_ORIGIN}/api/share/link-only/registry`;
|
|
34952
|
-
var DEFAULT_READ_REGISTRY = "https://registry.tinycloud.xyz";
|
|
34953
34098
|
var shareServices = {};
|
|
34954
34099
|
function parseShareTarget(value) {
|
|
34955
34100
|
if (value === "anyone" || value === "bearer") return { kind: "bearer" };
|
|
@@ -34959,26 +34104,11 @@ function parseShareTarget(value) {
|
|
|
34959
34104
|
if (value.includes("@")) return { kind: "email", address: value };
|
|
34960
34105
|
throw new CLIError("INVALID_ARGUMENT", "--to must be anyone, a did:, an email address, or domain:example.com", 2);
|
|
34961
34106
|
}
|
|
34962
|
-
function publishServices(insecureLocalRegistry = false) {
|
|
34963
|
-
return {
|
|
34964
|
-
...shareServices.uploadBlob === void 0 ? {} : { uploadBlob: shareServices.uploadBlob },
|
|
34965
|
-
...shareServices.authorizeUpload === void 0 ? {} : { authorizeUpload: shareServices.authorizeUpload },
|
|
34966
|
-
...shareServices.authorizeUpload === void 0 || insecureLocalRegistry ? {} : { authorizationOrigin: SHARE_ORIGIN },
|
|
34967
|
-
...shareServices.credentials === void 0 ? {} : { credentials: shareServices.credentials },
|
|
34968
|
-
...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn }
|
|
34969
|
-
};
|
|
34970
|
-
}
|
|
34971
|
-
function fetchServices() {
|
|
34972
|
-
return {
|
|
34973
|
-
...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn },
|
|
34974
|
-
...shareServices.trustedPolicyAuthority === void 0 ? {} : { trustedPolicyAuthority: shareServices.trustedPolicyAuthority }
|
|
34975
|
-
};
|
|
34976
|
-
}
|
|
34977
34107
|
function shareCliError(error) {
|
|
34978
34108
|
if (error instanceof CLIError) return error;
|
|
34979
34109
|
if (error instanceof SharePublishError) {
|
|
34980
|
-
const exit = error.code === "
|
|
34981
|
-
const code3 = error.code === "
|
|
34110
|
+
const exit = error.code === "authority-required" ? 3 : error.code === "max-bytes-exceeded" ? 7 : 2;
|
|
34111
|
+
const code3 = error.code === "authority-required" ? "AUTH_REQUIRED" : error.code === "max-bytes-exceeded" ? "MAX_BYTES_EXCEEDED" : "INVALID_ARGUMENT";
|
|
34982
34112
|
return new CLIError(code3, error.message, exit);
|
|
34983
34113
|
}
|
|
34984
34114
|
if (error instanceof ShareReceiveError) {
|
|
@@ -35009,6 +34139,15 @@ function inputUrl(value, stdin) {
|
|
|
35009
34139
|
if (value === void 0 || value.length === 0) throw new CLIError("INVALID_ARGUMENT", "a share URL or - is required", 2);
|
|
35010
34140
|
return Promise.resolve(value);
|
|
35011
34141
|
}
|
|
34142
|
+
async function inspectShareInputOnce(value, stdin, expectedOrigin, dependencies = {}) {
|
|
34143
|
+
const link2 = stdin || value === "-" ? await (dependencies.read ?? readBoundedUrlStdin)() : await inputUrl(value, false);
|
|
34144
|
+
try {
|
|
34145
|
+
parseNativeShareUrl(link2);
|
|
34146
|
+
} catch {
|
|
34147
|
+
return (dependencies.inspect ?? inspectShare)(link2, { expectedOrigin });
|
|
34148
|
+
}
|
|
34149
|
+
throw new CLIError("UNSUPPORTED_LINK", "native bearer links are opaque; receive the link to verify access", 2);
|
|
34150
|
+
}
|
|
35012
34151
|
function jsonOutput(options, command) {
|
|
35013
34152
|
return options.json === true || command.optsWithGlobals().json === true;
|
|
35014
34153
|
}
|
|
@@ -35039,19 +34178,6 @@ function requestedActions(values) {
|
|
|
35039
34178
|
if (actions.some((value) => value !== "read" && value !== "list" && value !== "edit")) throw new CLIError("INVALID_ARGUMENT", "--action must be read, list, or edit", 2);
|
|
35040
34179
|
return [...new Set(actions)];
|
|
35041
34180
|
}
|
|
35042
|
-
async function authorizationProof(options) {
|
|
35043
|
-
const encoded = options.authorizationProofFile === void 0 ? void 0 : await readFile10(options.authorizationProofFile, "utf8").catch(() => {
|
|
35044
|
-
throw new CLIError("INVALID_ARGUMENT", "authorization proof file could not be read", 2);
|
|
35045
|
-
});
|
|
35046
|
-
if (encoded === void 0) return void 0;
|
|
35047
|
-
try {
|
|
35048
|
-
const value = JSON.parse(encoded);
|
|
35049
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("object");
|
|
35050
|
-
return value;
|
|
35051
|
-
} catch {
|
|
35052
|
-
throw new CLIError("INVALID_ARGUMENT", "authorization proof must be a JSON object", 2);
|
|
35053
|
-
}
|
|
35054
|
-
}
|
|
35055
34181
|
function assertAggregateInputLimit(inputs, maxBytes) {
|
|
35056
34182
|
const limit = maxBytes ?? MAX_SHARE_STDIN_BYTES;
|
|
35057
34183
|
let total = 0;
|
|
@@ -35062,10 +34188,9 @@ function assertAggregateInputLimit(inputs, maxBytes) {
|
|
|
35062
34188
|
}
|
|
35063
34189
|
function registerShareCommand(program) {
|
|
35064
34190
|
const share = program.command("share").description("Publish and consume TinyCloud Share links");
|
|
35065
|
-
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("--
|
|
34191
|
+
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) => {
|
|
35066
34192
|
try {
|
|
35067
34193
|
const json = jsonOutput(options, command);
|
|
35068
|
-
if (options.inline && options.compact) throw new CLIError("INVALID_ARGUMENT", "--inline and --compact are mutually exclusive", 2);
|
|
35069
34194
|
const maxBytes = byteLimit(options.maxBytes);
|
|
35070
34195
|
if (files.length === 0 || files.includes("-") && files.length > 1) throw new CLIError("INVALID_ARGUMENT", "stdin must be the only publish input", 2);
|
|
35071
34196
|
const inputs = await Promise.all(files.map((file) => readShareInput(file, files.length === 1 ? options.name : void 0, maxBytes)));
|
|
@@ -35087,13 +34212,9 @@ function registerShareCommand(program) {
|
|
|
35087
34212
|
actions,
|
|
35088
34213
|
expiresAt: expires(options.expires),
|
|
35089
34214
|
origin: options.viewerOrigin,
|
|
35090
|
-
inline: options.inline === true,
|
|
35091
34215
|
...maxBytes === void 0 ? {} : { maxBytes },
|
|
35092
|
-
registryBaseUrl: options.registry,
|
|
35093
|
-
allowInsecureRegistry: options.insecureRegistry === true,
|
|
35094
34216
|
notify: options.notify === true,
|
|
35095
|
-
targetAdapter: shareServices.targetAdapter
|
|
35096
|
-
...publishServices(options.insecureRegistry === true)
|
|
34217
|
+
targetAdapter: shareServices.targetAdapter
|
|
35097
34218
|
});
|
|
35098
34219
|
if ("state" in result) {
|
|
35099
34220
|
if (json) {
|
|
@@ -35115,44 +34236,44 @@ function registerShareCommand(program) {
|
|
|
35115
34236
|
handleError(shareCliError(error));
|
|
35116
34237
|
}
|
|
35117
34238
|
});
|
|
35118
|
-
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("--
|
|
34239
|
+
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) => {
|
|
35119
34240
|
try {
|
|
35120
34241
|
const json = jsonOutput(options, command);
|
|
35121
|
-
const
|
|
35122
|
-
const result = await inspectShare(link2, { registryBaseUrl: options.registry, expectedOrigin: options.viewerOrigin, ...fetchServices() });
|
|
34242
|
+
const result = await inspectShareInputOnce(url, options.stdin === true, options.viewerOrigin);
|
|
35123
34243
|
if (json) writeJson2(result);
|
|
35124
34244
|
else inspectHuman(result);
|
|
35125
34245
|
} catch (error) {
|
|
35126
34246
|
handleError(shareCliError(error));
|
|
35127
34247
|
}
|
|
35128
34248
|
});
|
|
35129
|
-
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("--
|
|
34249
|
+
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) => {
|
|
35130
34250
|
try {
|
|
35131
34251
|
const json = jsonOutput(options, command);
|
|
35132
34252
|
if (options.stdout && json) throw new CLIError("INVALID_ARGUMENT", "--stdout and --json are mutually exclusive", 2);
|
|
35133
34253
|
const maxBytes = byteLimit(options.maxBytes);
|
|
35134
34254
|
const link2 = await inputUrl(url, options.stdin === true);
|
|
35135
|
-
|
|
35136
|
-
|
|
35137
|
-
|
|
35138
|
-
|
|
34255
|
+
let nativeLink = false;
|
|
34256
|
+
try {
|
|
34257
|
+
parseNativeShareUrl(link2);
|
|
34258
|
+
nativeLink = true;
|
|
34259
|
+
} catch {
|
|
34260
|
+
}
|
|
34261
|
+
if (nativeLink) {
|
|
34262
|
+
if (shareServices.nativeReader === void 0) throw new CLIError("AUTH_REQUIRED", "native TinyCloud receive is not configured", 3);
|
|
34263
|
+
const native = await shareServices.nativeReader(link2);
|
|
34264
|
+
if (maxBytes !== void 0 && native.bytes.byteLength > maxBytes) throw new CLIError("MAX_BYTES_EXCEEDED", "shared content exceeds max-bytes", 7);
|
|
35139
34265
|
if (options.stdout) {
|
|
35140
|
-
process.stdout.write(Buffer.from(bytes));
|
|
34266
|
+
process.stdout.write(Buffer.from(native.bytes));
|
|
35141
34267
|
return;
|
|
35142
34268
|
}
|
|
35143
|
-
const output3 = await writeShareOutput(options.output ?? ".",
|
|
35144
|
-
if (json) writeJson2({ protocol: "tinycloud-share", version: 1,
|
|
34269
|
+
const output3 = await writeShareOutput(options.output ?? ".", native.filename, native.bytes, options.force === true);
|
|
34270
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, path: output3, transport: "native" });
|
|
35145
34271
|
else receiveHuman(output3);
|
|
35146
34272
|
return;
|
|
35147
34273
|
}
|
|
35148
34274
|
const result = await receiveShare(link2, {
|
|
35149
|
-
registryBaseUrl: options.registry,
|
|
35150
34275
|
expectedOrigin: options.viewerOrigin,
|
|
35151
|
-
...
|
|
35152
|
-
...maxBytes === void 0 ? {} : { maxContentBlobBytes: maxBytes },
|
|
35153
|
-
...shareServices.authorization === void 0 ? {} : { authorization: shareServices.authorization },
|
|
35154
|
-
...options.resumeToken === void 0 ? {} : { authorizationResumeToken: options.resumeToken },
|
|
35155
|
-
...proof === void 0 ? {} : { authorizationProof: proof }
|
|
34276
|
+
...maxBytes === void 0 ? {} : { maxContentBlobBytes: maxBytes }
|
|
35156
34277
|
});
|
|
35157
34278
|
if ("state" in result) {
|
|
35158
34279
|
if (json) {
|
|
@@ -35173,50 +34294,6 @@ function registerShareCommand(program) {
|
|
|
35173
34294
|
handleError(shareCliError(error));
|
|
35174
34295
|
}
|
|
35175
34296
|
});
|
|
35176
|
-
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) => {
|
|
35177
|
-
try {
|
|
35178
|
-
const json = jsonOutput(options, command);
|
|
35179
|
-
if (shareServices.legacyReader === void 0) throw new CLIError("UNSUPPORTED_LINK", "legacy migration requires an installed read-only tc1 adapter", 2);
|
|
35180
|
-
const link2 = await inputUrl(url, options.stdin === true);
|
|
35181
|
-
if (!isLegacyShareLink(link2)) throw new CLIError("UNSUPPORTED_LINK", "only tc1: links can be migrated", 2);
|
|
35182
|
-
const maxBytes = byteLimit(options.maxBytes) ?? MAX_SHARE_STDIN_BYTES;
|
|
35183
|
-
const migrated = await migrateShare({
|
|
35184
|
-
link: link2,
|
|
35185
|
-
reader: shareServices.legacyReader,
|
|
35186
|
-
publish: async (bytes) => {
|
|
35187
|
-
if (bytes.byteLength > maxBytes) throw new SharePublishError("max-bytes-exceeded", "legacy content exceeds the configured byte limit");
|
|
35188
|
-
const result = await publishTargetShare({
|
|
35189
|
-
source: bytes,
|
|
35190
|
-
filename: options.name,
|
|
35191
|
-
mediaType: "text/markdown",
|
|
35192
|
-
target: parseShareTarget(options.to),
|
|
35193
|
-
expiresAt: expires(options.expires),
|
|
35194
|
-
origin: options.viewerOrigin,
|
|
35195
|
-
inline: options.inline === true,
|
|
35196
|
-
registryBaseUrl: options.registry,
|
|
35197
|
-
allowInsecureRegistry: options.insecureRegistry === true,
|
|
35198
|
-
notify: options.notify === true,
|
|
35199
|
-
targetAdapter: shareServices.targetAdapter,
|
|
35200
|
-
...publishServices(options.insecureRegistry === true)
|
|
35201
|
-
});
|
|
35202
|
-
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);
|
|
35203
|
-
const record = await rememberPublishedShare(result);
|
|
35204
|
-
if (options.notify === true) {
|
|
35205
|
-
const target = parseShareTarget(options.to);
|
|
35206
|
-
if (target.kind !== "email") throw new CLIError("INVALID_ARGUMENT", "--notify requires an exact email target", 2);
|
|
35207
|
-
if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
|
|
35208
|
-
const delivery = await notifyShare({ shareId: record.shareId, recipient: target.address, record, adapter: shareServices.delivery });
|
|
35209
|
-
if (delivery.state === "partial-failure") process.exitCode = 9;
|
|
35210
|
-
}
|
|
35211
|
-
return result;
|
|
35212
|
-
}
|
|
35213
|
-
});
|
|
35214
|
-
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, migrated: redactPublishedShare(migrated.migrated) });
|
|
35215
|
-
else publishHuman(migrated.migrated);
|
|
35216
|
-
} catch (error) {
|
|
35217
|
-
handleError(shareCliError(error));
|
|
35218
|
-
}
|
|
35219
|
-
});
|
|
35220
34297
|
share.command("list").description("List encrypted sender history without complete bearer URLs").option("--json", "Print versioned redacted JSON").action(async (options, command) => {
|
|
35221
34298
|
try {
|
|
35222
34299
|
const json = jsonOutput(options, command);
|
|
@@ -36125,7 +35202,7 @@ init_profiles();
|
|
|
36125
35202
|
init_formatter();
|
|
36126
35203
|
init_errors();
|
|
36127
35204
|
init_constants();
|
|
36128
|
-
import { readFile as
|
|
35205
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
36129
35206
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
36130
35207
|
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
36131
35208
|
async function readStdin4() {
|
|
@@ -36183,7 +35260,7 @@ function registerVaultCommand(program) {
|
|
|
36183
35260
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
36184
35261
|
}
|
|
36185
35262
|
if (options.file) {
|
|
36186
|
-
putValue = new Uint8Array(await
|
|
35263
|
+
putValue = new Uint8Array(await readFile10(options.file));
|
|
36187
35264
|
} else if (options.stdin) {
|
|
36188
35265
|
putValue = new Uint8Array(await readStdin4());
|
|
36189
35266
|
} else {
|
|
@@ -36302,7 +35379,7 @@ init_profiles();
|
|
|
36302
35379
|
init_formatter();
|
|
36303
35380
|
init_errors();
|
|
36304
35381
|
init_constants();
|
|
36305
|
-
import { readFile as
|
|
35382
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
36306
35383
|
import { writeFile as writeFile9 } from "fs/promises";
|
|
36307
35384
|
var VARIABLES_PREFIX = "variables/";
|
|
36308
35385
|
async function readStdin5() {
|
|
@@ -36403,7 +35480,7 @@ function registerVarsCommand(program) {
|
|
|
36403
35480
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
36404
35481
|
}
|
|
36405
35482
|
if (options.file) {
|
|
36406
|
-
varValue = await
|
|
35483
|
+
varValue = await readFile11(options.file, "utf-8");
|
|
36407
35484
|
} else if (options.stdin) {
|
|
36408
35485
|
varValue = (await readStdin5()).toString("utf-8");
|
|
36409
35486
|
} else {
|