@tinycloud/cli 1.2.3-beta.1 → 1.2.3-beta.2

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
@@ -15988,7 +15988,7 @@ async function revokeShare(input) {
15988
15988
  if (delegationCid === void 0) return { state: "unsupported", target, reason: "share has no node-enforced delegation receipt", code: "unsupported-target" };
15989
15989
  if (target === "bearer") {
15990
15990
  if (input.adapter.revokeDelegation === void 0) return { state: "unsupported", target, reason: "native delegation revocation authority is required", code: "unsupported-target" };
15991
- await input.adapter.revokeDelegation({ delegationCid, scope });
15991
+ await input.adapter.revokeDelegation({ delegationCid, scope, record: input.record });
15992
15992
  } else {
15993
15993
  if (input.record.ownerDid === void 0) return { state: "unsupported", target, reason: "share has no Policy/v3 owner receipt", code: "unsupported-target" };
15994
15994
  if (input.adapter.revokePolicyRoot === void 0) return { state: "unsupported", target, reason: "Policy/v3 root revocation authority is required", code: "unsupported-target" };
@@ -16000,11 +16000,14 @@ async function revokeShare(input) {
16000
16000
  nodeAudience: policyNodeAudience(input.record)
16001
16001
  });
16002
16002
  }
16003
- const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
16003
+ let revokedAt = input.record.revokedAt ?? (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
16004
16004
  if (input.records?.update !== void 0) {
16005
- await input.records.update(input.record.shareId, (record) => ({ ...record, revokedAt }));
16005
+ const updated = await input.records.update(input.record.shareId, (record) => ({ ...record, revokedAt: record.revokedAt ?? revokedAt }));
16006
+ revokedAt = updated?.revokedAt ?? revokedAt;
16006
16007
  } else if (input.records !== void 0) {
16007
- await input.records.put({ ...input.record, revokedAt });
16008
+ const latest = await input.records.get(input.record.shareId);
16009
+ revokedAt = latest?.revokedAt ?? revokedAt;
16010
+ await input.records.put({ ...latest ?? input.record, revokedAt });
16008
16011
  }
16009
16012
  return { state: "revoked", target, delegationCid, revokedAt };
16010
16013
  }
@@ -18034,7 +18037,7 @@ function registerShareCommand(program2) {
18034
18037
  return handleError(shareCliError(error, "notify"));
18035
18038
  }
18036
18039
  });
18037
- share.command("revoke <id>").description("Revoke addressed shares; report bearer retention honestly").option("--ancestor", "Revoke the owner delegation ancestry").option("--json", "Print versioned JSON").action(async (id, options, command) => {
18040
+ share.command("revoke <id>").description("Revoke a bearer or addressed share this profile created").option("--ancestor", "Revoke the owner delegation ancestry").option("--json", "Print versioned JSON").action(async (id, options, command) => {
18038
18041
  try {
18039
18042
  const json = jsonOutput(options, command);
18040
18043
  if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
@@ -18057,12 +18060,21 @@ function registerShareCommand(program2) {
18057
18060
  init_storage();
18058
18061
  init_profiles();
18059
18062
  init_constants();
18063
+ init_errors();
18060
18064
  init_session_expired();
18061
18065
  import { readFile as readFile4 } from "fs/promises";
18062
18066
  import { join as join8 } from "path";
18063
18067
  import { createHash } from "crypto";
18064
18068
  import { writeJsonAtomic as writeJsonAtomic2 } from "@tinycloud/operations/state";
18065
- import { LocationRecordValidationError, LocationRegistryHttpError, revokePolicyRootV3 } from "@tinycloud/sdk-core";
18069
+ import { withoutBearer } from "@tinycloud/operations/delegation-binding";
18070
+ import {
18071
+ LocationRecordValidationError,
18072
+ LocationRegistryHttpError,
18073
+ parseSignedCompactUcanAttenuation,
18074
+ parseSignedSiweCacao,
18075
+ revokePolicyRootV3,
18076
+ signCompactUcanAuthorization
18077
+ } from "@tinycloud/sdk-core";
18066
18078
  import { extractSiweExpiration, InvalidRestoredSessionError } from "@tinycloud/node-sdk";
18067
18079
  function requiredKvAction(meta) {
18068
18080
  if (meta === null || typeof meta !== "object" || !("requiredAction" in meta)) return void 0;
@@ -18656,9 +18668,80 @@ function createShareAuthorityAdapters(input = {}) {
18656
18668
  return "delivered";
18657
18669
  }) };
18658
18670
  const revocation = {
18659
- revokeDelegation: input.revokeDelegation ?? (async (request) => {
18660
- const result = await (await authenticatedNode()).revokeDelegation(request.delegationCid);
18661
- if (!result.ok) throw new Error("share delegation revocation was rejected");
18671
+ // The node authorizes a revocation signed by the delegation's delegator,
18672
+ // or by the owner of a space it targets. A bearer link is minted by the
18673
+ // session key, or by the owner wallet when it outlives the session, so a
18674
+ // proofless session-key revocation covers the first, and a local owner
18675
+ // key covers both. Without either key (a session-only profile that signed
18676
+ // in again since, for example) the link cannot be revoked here.
18677
+ revokeDelegation: input.revokeDelegation ?? (async ({ delegationCid, record }) => {
18678
+ const node = await authenticatedNode();
18679
+ const activeNode = await node.activeNodeIdentity();
18680
+ if (record.target.origin !== activeNode.origin) throw new Error("share delegation revocation is not bound to the active owner node");
18681
+ const linkUnavailable = new CLIError("SHARE_LINK_UNAVAILABLE", "this share's stored link could not be verified; nothing was revoked", ExitCode.NOT_FOUND);
18682
+ const resource = `${record.target.spaceId}/kv/${record.resource.path}`;
18683
+ let sessionIssued;
18684
+ try {
18685
+ const decoded = node.sharing.decodeLink(parseNativeShareUrl(record.link ?? ""));
18686
+ const linkKey = decoded.keyDid.split("#", 1)[0];
18687
+ const authorization2 = withoutBearer(decoded.delegation.authHeader ?? "");
18688
+ if (authorization2.split(".").length === 3) {
18689
+ const signed = parseSignedCompactUcanAttenuation(authorization2, delegationCid);
18690
+ if (signed.payload.aud.split("#", 1)[0] !== linkKey || signed.payload.att[resource] === void 0) throw linkUnavailable;
18691
+ sessionIssued = signed.payload.iss.split("#", 1)[0] === node.credentialHolderDid;
18692
+ } else {
18693
+ const signed = await parseSignedSiweCacao(authorization2, delegationCid);
18694
+ const owner = /^tinycloud:pkh:(eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):/.exec(record.target.spaceId)?.[1];
18695
+ if (owner === void 0 || signed.issuer.toLowerCase() !== `did:pkh:${owner}`.toLowerCase() || signed.audience !== linkKey || signed.attenuation[resource] === void 0) throw linkUnavailable;
18696
+ sessionIssued = false;
18697
+ }
18698
+ } catch {
18699
+ throw linkUnavailable;
18700
+ }
18701
+ if (delegationCid === node.session?.delegationCid) throw linkUnavailable;
18702
+ let authorization;
18703
+ if (sessionIssued) {
18704
+ const now = Math.floor(Date.now() / 1e3);
18705
+ authorization = (await signCompactUcanAuthorization({
18706
+ issuerDid: node.sessionDid,
18707
+ audienceDid: activeNode.nodeDid,
18708
+ attenuation: { [`urn:cid:${delegationCid}`]: { "tinycloud.delegation/revoke": [{}] } },
18709
+ facts: [{}],
18710
+ proofs: [],
18711
+ notBefore: now,
18712
+ expiresAt: now + 60,
18713
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
18714
+ sign: (bytes) => node.signSessionBytes(bytes)
18715
+ })).authorization;
18716
+ } else if (!node.isSessionOnly) {
18717
+ authorization = await node.signOwnerRevocation(delegationCid);
18718
+ } else {
18719
+ throw new CLIError("SHARE_ISSUER_KEY_UNAVAILABLE", "the key that created this share link is not available to this profile; nothing was revoked", ExitCode.PERMISSION_DENIED);
18720
+ }
18721
+ const response = await fetchFn(`${activeNode.origin}/revoke`, { method: "POST", headers: { Authorization: authorization } });
18722
+ const body = await response.text();
18723
+ if (response.ok) {
18724
+ let receipt;
18725
+ try {
18726
+ receipt = JSON.parse(body);
18727
+ } catch {
18728
+ receipt = void 0;
18729
+ }
18730
+ if (typeof receipt === "object" && receipt !== null && "revoked" in receipt && receipt.revoked === true && "cid" in receipt && receipt.cid === delegationCid) return;
18731
+ throw new CLIError(
18732
+ "SHARE_REVOKE_UNCONFIRMED",
18733
+ `the node answered the share revocation with HTTP ${response.status} but did not confirm it; the link may already be revoked. Check whether it still opens before retrying`,
18734
+ ExitCode.ERROR,
18735
+ { status: response.status }
18736
+ );
18737
+ }
18738
+ const nodeMessage = body.replace(/[\u0000-\u001f\u007f]+/g, " ").trim().slice(0, 200);
18739
+ throw new CLIError(
18740
+ "SHARE_REVOKE_REJECTED",
18741
+ `the node rejected the share revocation (HTTP ${response.status}${nodeMessage === "" ? "" : `: ${nodeMessage}`}); nothing was revoked`,
18742
+ ExitCode.NOT_FOUND,
18743
+ { status: response.status }
18744
+ );
18662
18745
  }),
18663
18746
  revokePolicyRoot: input.revokePolicyRoot ?? (async (request) => {
18664
18747
  const node = await authenticatedNode();