@tinycloud/cli 1.3.2-beta.0 → 1.3.2-beta.1

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 CHANGED
@@ -28462,6 +28462,9 @@ function throwKvUploadFailure(error) {
28462
28462
  if (rejection) throw new SharePublishAuthorityError({ kind: "storage-full", ...rejection });
28463
28463
  throw new SharePublishAuthorityError({ kind: "upload-failed" });
28464
28464
  }
28465
+ function sessionHoldsEncryptionDecrypt(node, networkId) {
28466
+ return node.hasRuntimePermissions([{ service: "tinycloud.encryption", path: networkId, actions: ["tinycloud.encryption/decrypt"] }]);
28467
+ }
28465
28468
  var DEFAULT_SHARE_ORIGIN = "https://share.tinycloud.xyz";
28466
28469
  var MIN_DOMAIN_DELIVERY_VERSION = "1.17.3";
28467
28470
  function supportsDomainDelivery(version2) {
@@ -28908,7 +28911,7 @@ function createShareAuthorityAdapters(input = {}) {
28908
28911
  };
28909
28912
  const targetAdapter = { async publish(targetInput) {
28910
28913
  if (input.publishTarget !== void 0) return input.publishTarget(targetInput);
28911
- const [config, node] = await Promise.all([publicConfig(), authenticatedNode()]);
28914
+ const node = await authenticatedNode();
28912
28915
  const session = node.restorableSession;
28913
28916
  const ownerSpaceId = session?.spaceId;
28914
28917
  const localKey = !node.isSessionOnly;
@@ -28921,7 +28924,6 @@ function createShareAuthorityAdapters(input = {}) {
28921
28924
  if (ownerSpaceId === void 0 || node.isSessionOnly && (!session?.siwe || !session.signature || sessionExpiresAt === void 0)) {
28922
28925
  throw new SharePublishAuthorityError({ kind: "owner-space-unresolved", localKey, profileName: activeProfileName2 });
28923
28926
  }
28924
- if (targetInput.origin !== config.shareOrigin) throw new SharePublishAuthorityError({ kind: "origin-mismatch" });
28925
28927
  if (node.isSessionOnly && sessionExpiresAt !== void 0) {
28926
28928
  const roundedSessionExpiry = new Date(Math.floor(sessionExpiresAt.getTime() / 1e3) * 1e3);
28927
28929
  if (roundedSessionExpiry.getTime() <= Date.now() + 6e4) {
@@ -28955,6 +28957,12 @@ function createShareAuthorityAdapters(input = {}) {
28955
28957
  profileName: activeProfileName2
28956
28958
  });
28957
28959
  }
28960
+ const addressedEncryptionNetwork = targetInput.target.kind === "bearer" ? void 0 : node.getEncryptionNetworkIdForSpace(ownerSpaceId);
28961
+ if (addressedEncryptionNetwork !== void 0 && node.isSessionOnly && !sessionHoldsEncryptionDecrypt(node, addressedEncryptionNetwork)) {
28962
+ throw new SharePublishAuthorityError({ kind: "decrypt-not-held" });
28963
+ }
28964
+ const config = await publicConfig();
28965
+ if (targetInput.origin !== config.shareOrigin) throw new SharePublishAuthorityError({ kind: "origin-mismatch" });
28958
28966
  const activeNode = await node.activeNodeIdentity();
28959
28967
  if (targetInput.notify === true && targetInput.target.kind === "emailDomain") {
28960
28968
  await assertDomainDeliveryForOrigin(activeNode.origin, "publish");
@@ -29075,7 +29083,7 @@ function createShareAuthorityAdapters(input = {}) {
29075
29083
  const rejected = error instanceof LocationRecordValidationError || error instanceof LocationRegistryHttpError && error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429;
29076
29084
  throw new SharePublishAuthorityError({ kind: rejected ? "registry-rejected" : "registry-unavailable" });
29077
29085
  }
29078
- const encryptionNetwork = node.getEncryptionNetworkIdForSpace(ownerSpaceId);
29086
+ const encryptionNetwork = addressedEncryptionNetwork;
29079
29087
  const encrypted = await node.encryption.encryptToNetwork(encryptionNetwork, file.bytes, { metadata: { contentType: mediaType } });
29080
29088
  if (!encrypted.ok) throw new Error("addressed source encryption was rejected");
29081
29089
  const storedBytes = new TextEncoder().encode(canonicalize3(encrypted.data));
@@ -30644,6 +30652,14 @@ function shareCliError(error, operation = "publish") {
30644
30652
  if (failure.kind === "upload-failed") {
30645
30653
  return new CLIError("UPLOAD_FAILED", "share source upload failed; nothing was shared", 4);
30646
30654
  }
30655
+ if (failure.kind === "decrypt-not-held") {
30656
+ return new CLIError(
30657
+ "PERMISSION_DENIED",
30658
+ "this sign-in can't create email-addressed shares: it has no permission to decrypt with the account's key. Nothing was shared.",
30659
+ 5,
30660
+ { hint: "Share it from https://share.tinycloud.xyz while signed in as the account owner, or ask the owner to share it. A link anyone can open works from this sign-in: --to anyone." }
30661
+ );
30662
+ }
30647
30663
  }
30648
30664
  if (error instanceof ShortLinkError) {
30649
30665
  if (error.code === "not-found") return new CLIError("NOT_FOUND", "this short link has expired, was revoked, or never existed", 4);
@@ -30695,6 +30711,11 @@ function inputUrl(value, stdin) {
30695
30711
  function resolveLink(link4, expectedOrigin) {
30696
30712
  return shareServices.shortLinks?.resolve(link4, expectedOrigin) ?? resolveShortLink(link4, { expectedOrigin });
30697
30713
  }
30714
+ function assertTrustedShareOrigin(link4, expectedOrigin) {
30715
+ if (URL.canParse(link4) && new URL(link4).origin !== expectedOrigin) {
30716
+ throw new ShareReceiveError("origin-mismatch", "share link origin is not the trusted Share origin");
30717
+ }
30718
+ }
30698
30719
  var SHORT_LINK_WARNING = {
30699
30720
  "rate-limited": "the short-link service is rate limiting this client",
30700
30721
  capacity: "the short-link service is at capacity",
@@ -30721,8 +30742,11 @@ async function inspectShareInputOnce(value, stdin, expectedOrigin, dependencies
30721
30742
  try {
30722
30743
  parseNativeShareUrl(link4);
30723
30744
  } catch {
30724
- return (dependencies.inspect ?? inspectShare)(await resolveLink(link4, expectedOrigin), { expectedOrigin });
30745
+ const resolved = await resolveLink(link4, expectedOrigin);
30746
+ assertTrustedShareOrigin(resolved, expectedOrigin);
30747
+ return (dependencies.inspect ?? inspectShare)(resolved, { expectedOrigin });
30725
30748
  }
30749
+ assertTrustedShareOrigin(link4, expectedOrigin);
30726
30750
  throw new CLIError("UNSUPPORTED_LINK", "native bearer links are opaque; receive the link to verify access", 2);
30727
30751
  }
30728
30752
  function jsonOutput(options, command) {
@@ -30945,6 +30969,7 @@ function registerShareCommand(program2) {
30945
30969
  } catch {
30946
30970
  }
30947
30971
  if (nativeLink) {
30972
+ assertTrustedShareOrigin(link4, options.viewerOrigin);
30948
30973
  if (shareServices.nativeReader === void 0) throw new CLIError("AUTH_REQUIRED", "native TinyCloud receive is not configured", 3);
30949
30974
  const native = await shareServices.nativeReader(link4);
30950
30975
  if (maxBytes !== void 0 && native.bytes.byteLength > maxBytes) throw new CLIError("MAX_BYTES_EXCEEDED", "shared content exceeds max-bytes", 7);
@@ -30957,7 +30982,9 @@ function registerShareCommand(program2) {
30957
30982
  else receiveHuman(output2);
30958
30983
  return;
30959
30984
  }
30960
- const result = await receiveShare(await resolveLink(link4, options.viewerOrigin), {
30985
+ const resolved = await resolveLink(link4, options.viewerOrigin);
30986
+ assertTrustedShareOrigin(resolved, options.viewerOrigin);
30987
+ const result = await receiveShare(resolved, {
30961
30988
  expectedOrigin: options.viewerOrigin,
30962
30989
  ...maxBytes === void 0 ? {} : { maxContentBlobBytes: maxBytes }
30963
30990
  });
@@ -30986,7 +31013,7 @@ function registerShareCommand(program2) {
30986
31013
  if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
30987
31014
  const result = await listShares(shareServices.records);
30988
31015
  if (json) writeJson2({ protocol: "tinycloud-share", version: 1, shares: result });
30989
- else process.stdout.write(result.map((item) => `${item.shareId} ${item.target} ${item.expiresAt}`).join("\n") + (result.length ? "\n" : ""));
31016
+ else process.stdout.write(result.map((item) => `${item.shareId} ${item.target} ${item.expiresAt} ${item.revoked ? "revoked" : Date.parse(item.expiresAt) < Date.now() ? "expired" : "active"}`).join("\n") + (result.length ? "\n" : ""));
30990
31017
  } catch (error) {
30991
31018
  return handleError(shareCliError(error));
30992
31019
  }