@tinycloud/cli 1.2.3-beta.1 → 1.2.3-beta.3
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 +92 -9
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +8 -5
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +6 -6
- package/skills/courier/SKILL.md +15 -12
- package/skills/tc-cli/AUTH.md +15 -11
- package/skills/tc-cli/INSTALL.md +20 -15
- package/skills/tc-cli/REFERENCE.md +83 -74
- package/skills/tc-cli/SKILL.md +18 -16
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
|
-
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
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();
|