@tinycloud/cli 1.1.0-beta.13 → 1.1.0-beta.14
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 +90 -11
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +230 -95
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +5 -5
- package/skills/tc-cli/AUTH.md +6 -4
- package/skills/tc-cli/REFERENCE.md +1 -1
package/dist/legacy-entry.js
CHANGED
|
@@ -165,8 +165,14 @@ import ora from "ora";
|
|
|
165
165
|
function outputJson(data) {
|
|
166
166
|
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
167
167
|
}
|
|
168
|
-
function
|
|
169
|
-
|
|
168
|
+
function warningLine(warnings) {
|
|
169
|
+
const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
|
|
170
|
+
const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
|
|
171
|
+
return `${subject} not used (${which}). Approve access again when a command needs it; the old grant expires on its own.`;
|
|
172
|
+
}
|
|
173
|
+
function outputError(code3, message, hint, details = {}) {
|
|
174
|
+
const { meta, warnings = [] } = details;
|
|
175
|
+
if (!shouldOutputJson()) {
|
|
170
176
|
process.stderr.write(
|
|
171
177
|
`${theme.error("\u2717")} ${theme.label(code3)}: ${message}
|
|
172
178
|
`
|
|
@@ -177,20 +183,40 @@ function outputError(code3, message, hint, meta) {
|
|
|
177
183
|
`);
|
|
178
184
|
}
|
|
179
185
|
}
|
|
186
|
+
if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings))}
|
|
187
|
+
`);
|
|
180
188
|
} else {
|
|
181
189
|
const payload = {
|
|
182
190
|
error: { code: code3, message }
|
|
183
191
|
};
|
|
184
192
|
if (hint) payload.error.hint = hint;
|
|
185
193
|
if (meta) payload.error.meta = meta;
|
|
194
|
+
if (warnings.length > 0) payload.error.warnings = warnings;
|
|
186
195
|
process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
|
|
187
196
|
}
|
|
188
197
|
}
|
|
198
|
+
function outputWarnings(warnings) {
|
|
199
|
+
if (warnings.length === 0) return;
|
|
200
|
+
process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings })}
|
|
201
|
+
` : `${theme.warn("!")} ${warningLine(warnings)}
|
|
202
|
+
`);
|
|
203
|
+
}
|
|
204
|
+
function operationWarnings(value) {
|
|
205
|
+
if (!Array.isArray(value)) return [];
|
|
206
|
+
return value.flatMap((warning) => {
|
|
207
|
+
if (warning === null || typeof warning !== "object") return [];
|
|
208
|
+
const { code: code3, reason, grantCid } = warning;
|
|
209
|
+
if (typeof code3 !== "string" || !WARNING_CODE.test(code3) || typeof reason !== "string" || !WARNING_REASON.test(reason)) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
return [{ code: code3, reason, ...typeof grantCid === "string" && DELEGATION_CID.test(grantCid) ? { grantCid } : {} }];
|
|
213
|
+
});
|
|
214
|
+
}
|
|
189
215
|
function isInteractive() {
|
|
190
216
|
return Boolean(process.stdout.isTTY);
|
|
191
217
|
}
|
|
192
218
|
async function withSpinner(label, fn) {
|
|
193
|
-
if (
|
|
219
|
+
if (shouldOutputJson()) {
|
|
194
220
|
return fn();
|
|
195
221
|
}
|
|
196
222
|
const spinner = ora(label).start();
|
|
@@ -204,7 +230,7 @@ async function withSpinner(label, fn) {
|
|
|
204
230
|
}
|
|
205
231
|
}
|
|
206
232
|
function shouldOutputJson() {
|
|
207
|
-
return !isInteractive() || process.argv.includes("--json");
|
|
233
|
+
return !isInteractive() || (globalThis[JSON_REQUESTED] ?? process.argv.includes("--json"));
|
|
208
234
|
}
|
|
209
235
|
function formatField(label, value) {
|
|
210
236
|
if (value === null || value === void 0) return ` ${theme.label(label + ":")} ${theme.muted("\u2014")}`;
|
|
@@ -253,10 +279,15 @@ function formatTimeAgo(date) {
|
|
|
253
279
|
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
254
280
|
return `${Math.floor(seconds / 86400)}d ago`;
|
|
255
281
|
}
|
|
282
|
+
var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
|
|
256
283
|
var init_formatter = __esm({
|
|
257
284
|
"src/output/formatter.ts"() {
|
|
258
285
|
"use strict";
|
|
259
286
|
init_theme();
|
|
287
|
+
WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
|
|
288
|
+
WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
|
|
289
|
+
DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
|
|
290
|
+
JSON_REQUESTED = /* @__PURE__ */ Symbol.for("tinycloud.cli.jsonOutputRequested");
|
|
260
291
|
}
|
|
261
292
|
});
|
|
262
293
|
|
|
@@ -361,11 +392,12 @@ function cliErrorFromService(error, message = error.message) {
|
|
|
361
392
|
return missingPrivateKeyError();
|
|
362
393
|
}
|
|
363
394
|
const missingCapability = validatedCapabilityOf({ ...error, meta }) !== void 0;
|
|
364
|
-
const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability;
|
|
395
|
+
const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability || verdict === void 0 && error.code === "PERMISSION_DENIED";
|
|
396
|
+
const unauthenticated = !denied && (verdict === "unauthenticated" || verdict === void 0 && error.code === "AUTH_REQUIRED");
|
|
365
397
|
return new CLIError(
|
|
366
|
-
denied ? "PERMISSION_DENIED" :
|
|
398
|
+
denied ? "PERMISSION_DENIED" : unauthenticated ? "AUTH_REQUIRED" : error.code,
|
|
367
399
|
message,
|
|
368
|
-
denied ? ExitCode.PERMISSION_DENIED :
|
|
400
|
+
denied ? ExitCode.PERMISSION_DENIED : unauthenticated ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
|
|
369
401
|
meta
|
|
370
402
|
);
|
|
371
403
|
}
|
|
@@ -437,7 +469,10 @@ function handleError(error) {
|
|
|
437
469
|
meta.resource = capability.resource;
|
|
438
470
|
meta.requiredAction = capability.requiredAction;
|
|
439
471
|
}
|
|
440
|
-
outputError(cliError.code, cliError.message, hint,
|
|
472
|
+
outputError(cliError.code, cliError.message, hint, {
|
|
473
|
+
...Object.keys(meta).length ? { meta } : {},
|
|
474
|
+
warnings: operationWarnings(cliError.metadata?.warnings)
|
|
475
|
+
});
|
|
441
476
|
process.exit(cliError.exitCode);
|
|
442
477
|
}
|
|
443
478
|
function buildAuthHint(error) {
|
|
@@ -13471,6 +13506,10 @@ function canonicalOwnerDid(did, label = "--owner") {
|
|
|
13471
13506
|
}
|
|
13472
13507
|
return `did:pkh:eip155:${match[1]}:${ensureEip55(match[2])}`;
|
|
13473
13508
|
}
|
|
13509
|
+
function canonicalNetworkUrn(path) {
|
|
13510
|
+
const match = /^urn:tinycloud:encryption:(did:pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):([^:]*)$/.exec(path);
|
|
13511
|
+
return match ? `urn:tinycloud:encryption:${canonicalOwnerDid(match[1])}:${match[2]}` : path;
|
|
13512
|
+
}
|
|
13474
13513
|
function rawEncryptionOwnerMatches(path, ownerDid) {
|
|
13475
13514
|
const match = /^urn:tinycloud:encryption:(did:pkh:eip155:.+):([a-z0-9][a-z0-9-]*)$/.exec(path);
|
|
13476
13515
|
if (!match) return false;
|
|
@@ -13613,13 +13652,14 @@ async function replayAdditionalDelegations(node, profile, options) {
|
|
|
13613
13652
|
for (const stored of entries) {
|
|
13614
13653
|
const kind = storedDelegationKind(stored);
|
|
13615
13654
|
if (kind === "compact" || kind === "signed-login") {
|
|
13616
|
-
const
|
|
13655
|
+
const replay = await replayStoredDelegation(activator, stored, {
|
|
13617
13656
|
host: options.host,
|
|
13618
13657
|
migrated,
|
|
13619
13658
|
resolveSpace
|
|
13620
13659
|
});
|
|
13621
|
-
if (
|
|
13622
|
-
process.stderr.write(
|
|
13660
|
+
if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
|
|
13661
|
+
process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
|
|
13662
|
+
`);
|
|
13623
13663
|
}
|
|
13624
13664
|
continue;
|
|
13625
13665
|
}
|
|
@@ -13649,6 +13689,14 @@ async function replayAdditionalDelegations(node, profile, options) {
|
|
|
13649
13689
|
function storedAdditionalDelegation(delegation, permissions) {
|
|
13650
13690
|
return { delegation, permissions };
|
|
13651
13691
|
}
|
|
13692
|
+
async function cliGrantRecord(node, delegation, permissions, host) {
|
|
13693
|
+
const { bindCliGrant } = await import("@tinycloud/operations/delegation-binding");
|
|
13694
|
+
return bindCliGrant(
|
|
13695
|
+
node,
|
|
13696
|
+
storedAdditionalDelegation(delegation, permissions),
|
|
13697
|
+
host
|
|
13698
|
+
);
|
|
13699
|
+
}
|
|
13652
13700
|
async function appendGrantHistory(profile, entry) {
|
|
13653
13701
|
await ProfileManager.ensureProfileDir(profile);
|
|
13654
13702
|
const line = JSON.stringify({
|
|
@@ -13803,7 +13851,8 @@ function permissionsFromDelegation(delegation) {
|
|
|
13803
13851
|
}
|
|
13804
13852
|
function compactPermission(permission) {
|
|
13805
13853
|
const service = permission.service;
|
|
13806
|
-
const
|
|
13854
|
+
const fullSpace = permission.space ?? (isRawEncryptionPermission(permission) ? ENCRYPTION_MANIFEST_SPACE2 : "");
|
|
13855
|
+
const space = fullSpace.startsWith("tinycloud:") ? fullSpace.slice(fullSpace.lastIndexOf(":") + 1) : fullSpace;
|
|
13807
13856
|
const actions = permission.actions.map((action) => action.startsWith(`${service}/`) ? action.slice(service.length + 1) : action).join(",");
|
|
13808
13857
|
return `${service}:${space}:${permission.path}:${actions}`;
|
|
13809
13858
|
}
|
|
@@ -13899,6 +13948,35 @@ function serviceFromActions(actions) {
|
|
|
13899
13948
|
return first.includes("/") ? first.slice(0, first.indexOf("/")) : "tinycloud.unknown";
|
|
13900
13949
|
}
|
|
13901
13950
|
|
|
13951
|
+
// src/auth/session-expired.ts
|
|
13952
|
+
init_constants();
|
|
13953
|
+
init_errors();
|
|
13954
|
+
var SESSION_EXPIRED_REASON = "session_expired";
|
|
13955
|
+
function signInAgainHint(profileName, posture) {
|
|
13956
|
+
if (posture === "local-owner-key") {
|
|
13957
|
+
return `Sign in again with: tc --profile ${profileName} auth login --method local`;
|
|
13958
|
+
}
|
|
13959
|
+
if (posture === "delegate-session") {
|
|
13960
|
+
return `Have the owner approve a new scoped login: tc --profile ${profileName} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
|
|
13961
|
+
}
|
|
13962
|
+
return `Sign in again with: tc --profile ${profileName} auth login --method openkey`;
|
|
13963
|
+
}
|
|
13964
|
+
function sessionExpiredError(profileName, posture) {
|
|
13965
|
+
return new CLIError(
|
|
13966
|
+
"AUTH_REQUIRED",
|
|
13967
|
+
`The session for profile "${profileName}" has expired or is no longer valid.`,
|
|
13968
|
+
ExitCode.AUTH_REQUIRED,
|
|
13969
|
+
{ hint: signInAgainHint(profileName, posture), reason: SESSION_EXPIRED_REASON }
|
|
13970
|
+
);
|
|
13971
|
+
}
|
|
13972
|
+
function withSignInHint(error, profileName, posture) {
|
|
13973
|
+
if (error.code !== "AUTH_REQUIRED") return error;
|
|
13974
|
+
return new CLIError(error.code, error.message, error.exitCode, {
|
|
13975
|
+
...error.metadata,
|
|
13976
|
+
hint: signInAgainHint(profileName, posture)
|
|
13977
|
+
});
|
|
13978
|
+
}
|
|
13979
|
+
|
|
13902
13980
|
// src/lib/sdk.ts
|
|
13903
13981
|
function jwkHasPrivateParameter(jwk) {
|
|
13904
13982
|
if (!jwk || typeof jwk !== "object") return false;
|
|
@@ -13925,6 +14003,16 @@ function signerJwkForProfile(profileName, sessionJwk, key) {
|
|
|
13925
14003
|
}
|
|
13926
14004
|
);
|
|
13927
14005
|
}
|
|
14006
|
+
async function restoreProfileSession(node, profileName, profile, sessionData) {
|
|
14007
|
+
try {
|
|
14008
|
+
await node.restoreSession(sessionData);
|
|
14009
|
+
} catch (error) {
|
|
14010
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "AUTH_EXPIRED") {
|
|
14011
|
+
throw sessionExpiredError(profileName, profile === null ? void 0 : resolveProfilePosture(profile));
|
|
14012
|
+
}
|
|
14013
|
+
throw error;
|
|
14014
|
+
}
|
|
14015
|
+
}
|
|
13928
14016
|
async function createSDKInstance(ctx, options) {
|
|
13929
14017
|
const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
|
|
13930
14018
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
@@ -13944,7 +14032,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
13944
14032
|
});
|
|
13945
14033
|
let restoredOwnSession2 = false;
|
|
13946
14034
|
if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
|
|
13947
|
-
await node2.
|
|
14035
|
+
await restoreProfileSession(node2, ctx.profile, profile, {
|
|
13948
14036
|
delegationHeader: session.delegationHeader,
|
|
13949
14037
|
delegationCid: session.delegationCid,
|
|
13950
14038
|
spaceId: session.spaceId,
|
|
@@ -13974,7 +14062,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
13974
14062
|
if (options?.privateKey) {
|
|
13975
14063
|
await node.signIn();
|
|
13976
14064
|
} else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
|
|
13977
|
-
await node.
|
|
14065
|
+
await restoreProfileSession(node, ctx.profile, profile, {
|
|
13978
14066
|
delegationHeader: session.delegationHeader,
|
|
13979
14067
|
delegationCid: session.delegationCid,
|
|
13980
14068
|
spaceId: session.spaceId,
|
|
@@ -14556,6 +14644,41 @@ function validateDelegationCallbackPayload(value) {
|
|
|
14556
14644
|
}
|
|
14557
14645
|
return null;
|
|
14558
14646
|
}
|
|
14647
|
+
async function delegationFromInput(input, did, options) {
|
|
14648
|
+
const trimmed = input.trim();
|
|
14649
|
+
let parsed;
|
|
14650
|
+
if (/^[a-z2-7]{4}-[a-z2-7]{4}$/i.test(trimmed)) {
|
|
14651
|
+
if (!options.jwk) throw new Error("A CLI session key is required to retrieve a delegation.");
|
|
14652
|
+
const apiHost = process.env.TC_OPENKEY_API_HOST ?? options.openkeyApiHost ?? (options.openkeyHost && options.openkeyHost !== DEFAULT_OPENKEY_HOST ? options.openkeyHost : DEFAULT_OPENKEY_DEVICE_API_HOST);
|
|
14653
|
+
const response = await fetch(`${apiHost.replace(/\/$/, "")}/api/delegation-codes/${trimmed.toLowerCase()}`, {
|
|
14654
|
+
redirect: "error"
|
|
14655
|
+
});
|
|
14656
|
+
if (!response.ok) {
|
|
14657
|
+
throw new Error(response.status === 404 ? "Delegation code not found or expired. Use the full code shown in OpenKey instead." : `Could not retrieve delegation code (HTTP ${response.status}).`);
|
|
14658
|
+
}
|
|
14659
|
+
const result = await response.json();
|
|
14660
|
+
parsed = result.delegation;
|
|
14661
|
+
const delegation = parsed;
|
|
14662
|
+
const jwk = delegation?.jwk;
|
|
14663
|
+
const localJwk = publicJwkForDelegation(options.jwk);
|
|
14664
|
+
if (typeof delegation?.verificationMethod !== "string" || delegation.verificationMethod.split("#")[0] !== did.split("#")[0] || !jwk || typeof jwk !== "object" || Array.isArray(jwk) || jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || typeof jwk.x !== "string" || jwk.x !== localJwk.x || [...PRIVATE_JWK_FIELDS].some((field) => field in jwk)) {
|
|
14665
|
+
throw new Error("Delegation is bound to a different CLI key.");
|
|
14666
|
+
}
|
|
14667
|
+
} else {
|
|
14668
|
+
try {
|
|
14669
|
+
parsed = JSON.parse(trimmed);
|
|
14670
|
+
} catch {
|
|
14671
|
+
try {
|
|
14672
|
+
parsed = JSON.parse(Buffer.from(trimmed, "base64").toString("utf-8"));
|
|
14673
|
+
} catch {
|
|
14674
|
+
throw new Error("Invalid delegation code. Expected JSON, base64-encoded JSON, or XXXX-XXXX.");
|
|
14675
|
+
}
|
|
14676
|
+
}
|
|
14677
|
+
}
|
|
14678
|
+
const invalid = validateDelegationCallbackPayload(parsed);
|
|
14679
|
+
if (invalid) throw new Error(`Invalid delegation code: ${invalid}`);
|
|
14680
|
+
return parsed;
|
|
14681
|
+
}
|
|
14559
14682
|
function shouldOpenBrowser(options) {
|
|
14560
14683
|
if (options.noPopup) return false;
|
|
14561
14684
|
const env = process.env.TC_AUTH_NO_POPUP ?? process.env.TC_NO_POPUP;
|
|
@@ -14580,21 +14703,6 @@ async function callbackFlow(did, options = {}) {
|
|
|
14580
14703
|
reject(result.error);
|
|
14581
14704
|
}
|
|
14582
14705
|
}
|
|
14583
|
-
function parsePasteInput(input) {
|
|
14584
|
-
const trimmed = input.trim();
|
|
14585
|
-
let parsed;
|
|
14586
|
-
try {
|
|
14587
|
-
parsed = JSON.parse(trimmed);
|
|
14588
|
-
} catch {
|
|
14589
|
-
const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
|
|
14590
|
-
parsed = JSON.parse(decoded);
|
|
14591
|
-
}
|
|
14592
|
-
const invalid = validateDelegationCallbackPayload(parsed);
|
|
14593
|
-
if (invalid) {
|
|
14594
|
-
throw new Error(`Invalid delegation code: ${invalid}`);
|
|
14595
|
-
}
|
|
14596
|
-
return parsed;
|
|
14597
|
-
}
|
|
14598
14706
|
const server = createServer((req, res) => {
|
|
14599
14707
|
if (req.method === "POST" && req.url === "/callback") {
|
|
14600
14708
|
let body = "";
|
|
@@ -14663,18 +14771,21 @@ async function callbackFlow(did, options = {}) {
|
|
|
14663
14771
|
}
|
|
14664
14772
|
if (process.stdin.isTTY) {
|
|
14665
14773
|
console.error(`
|
|
14666
|
-
If the browser can't connect back, paste the delegation code here:`);
|
|
14774
|
+
If the browser can't connect back, enter the short code or paste the full delegation code here:`);
|
|
14667
14775
|
rl = createInterface({
|
|
14668
14776
|
input: process.stdin,
|
|
14669
14777
|
output: process.stderr
|
|
14670
14778
|
});
|
|
14671
|
-
|
|
14672
|
-
|
|
14779
|
+
let resolving = false;
|
|
14780
|
+
rl.on("line", async (input) => {
|
|
14781
|
+
if (settled || resolving) return;
|
|
14782
|
+
resolving = true;
|
|
14673
14783
|
try {
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14677
|
-
|
|
14784
|
+
settle({ data: await delegationFromInput(input, did, options) });
|
|
14785
|
+
} catch (error) {
|
|
14786
|
+
console.error(error instanceof Error ? error.message : "Invalid delegation code. Try again:");
|
|
14787
|
+
} finally {
|
|
14788
|
+
resolving = false;
|
|
14678
14789
|
}
|
|
14679
14790
|
});
|
|
14680
14791
|
}
|
|
@@ -14701,24 +14812,7 @@ Open this URL in a browser to authenticate:
|
|
|
14701
14812
|
if (answered || input.trim() === "") return;
|
|
14702
14813
|
answered = true;
|
|
14703
14814
|
rl.close();
|
|
14704
|
-
|
|
14705
|
-
try {
|
|
14706
|
-
parsed = JSON.parse(input.trim());
|
|
14707
|
-
} catch {
|
|
14708
|
-
try {
|
|
14709
|
-
const decoded = Buffer.from(input.trim(), "base64").toString("utf-8");
|
|
14710
|
-
parsed = JSON.parse(decoded);
|
|
14711
|
-
} catch {
|
|
14712
|
-
reject(new Error("Invalid delegation code. Expected JSON or base64-encoded JSON."));
|
|
14713
|
-
return;
|
|
14714
|
-
}
|
|
14715
|
-
}
|
|
14716
|
-
const invalid = validateDelegationCallbackPayload(parsed);
|
|
14717
|
-
if (invalid) {
|
|
14718
|
-
reject(new Error(`Invalid delegation code: ${invalid}`));
|
|
14719
|
-
return;
|
|
14720
|
-
}
|
|
14721
|
-
resolve4(parsed);
|
|
14815
|
+
void delegationFromInput(input, did, options).then(resolve4, reject);
|
|
14722
14816
|
});
|
|
14723
14817
|
rl.on("close", () => {
|
|
14724
14818
|
if (answered) return;
|
|
@@ -14733,7 +14827,7 @@ Open this URL in a browser to authenticate:
|
|
|
14733
14827
|
}
|
|
14734
14828
|
));
|
|
14735
14829
|
});
|
|
14736
|
-
rl.setPrompt("
|
|
14830
|
+
rl.setPrompt("Enter short code or paste full delegation code: ");
|
|
14737
14831
|
rl.prompt();
|
|
14738
14832
|
});
|
|
14739
14833
|
}
|
|
@@ -15017,7 +15111,13 @@ function validateLoginPermissions(permissions) {
|
|
|
15017
15111
|
}
|
|
15018
15112
|
}
|
|
15019
15113
|
function scopedLoginPermissions(permissions) {
|
|
15020
|
-
|
|
15114
|
+
return withCapabilitiesRead(permissions, permissions.find((p) => !isRawEncryptionPermission(p)).space ?? "");
|
|
15115
|
+
}
|
|
15116
|
+
function grantRequestPermissions(group, anchorSpace) {
|
|
15117
|
+
const request = group.map((p) => isRawEncryptionPermission(p) ? { ...p, space: ENCRYPTION_MANIFEST_SPACE2, path: canonicalNetworkUrn(p.path) } : p);
|
|
15118
|
+
return withCapabilitiesRead(request, request.find((p) => !isRawEncryptionPermission(p))?.space ?? anchorSpace);
|
|
15119
|
+
}
|
|
15120
|
+
function withCapabilitiesRead(permissions, space) {
|
|
15021
15121
|
const hasRead = permissions.some((p) => p.service === "tinycloud.capabilities" && p.path === "" && p.actions.includes(CAPABILITIES_READ) && normalizePkhIdentifier(p.space ?? "") === normalizePkhIdentifier(space));
|
|
15022
15122
|
return hasRead ? permissions : [{ service: "tinycloud.capabilities", space, path: "", actions: [CAPABILITIES_READ] }, ...permissions];
|
|
15023
15123
|
}
|
|
@@ -15904,6 +16004,7 @@ function registerAuthCommand(program) {
|
|
|
15904
16004
|
"Grant requested TinyCloud permissions from `tc auth request --grant`.",
|
|
15905
16005
|
group
|
|
15906
16006
|
);
|
|
16007
|
+
const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
|
|
15907
16008
|
let delegationData;
|
|
15908
16009
|
if (options.device) {
|
|
15909
16010
|
const approval = await acquireDeviceDelegation({
|
|
@@ -15911,7 +16012,7 @@ function registerAuthCommand(program) {
|
|
|
15911
16012
|
jwk: key,
|
|
15912
16013
|
nodeOrigin: ctx.host,
|
|
15913
16014
|
shareOrigin: DEFAULT_SHARE_ORIGIN,
|
|
15914
|
-
permissions:
|
|
16015
|
+
permissions: request,
|
|
15915
16016
|
expiry: parseRequestedExpiry(expiryOption ?? "7d"),
|
|
15916
16017
|
reason,
|
|
15917
16018
|
expectedOwner: pinnedOwner(profile),
|
|
@@ -15923,17 +16024,17 @@ function registerAuthCommand(program) {
|
|
|
15923
16024
|
delegationData = await startAuthFlow(profile.did, {
|
|
15924
16025
|
jwk: key,
|
|
15925
16026
|
host: ctx.host,
|
|
15926
|
-
permissions:
|
|
16027
|
+
permissions: request,
|
|
15927
16028
|
reason,
|
|
15928
16029
|
openkeyHost,
|
|
15929
16030
|
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
|
|
15930
16031
|
noPopup: options.popup === false
|
|
15931
16032
|
});
|
|
15932
16033
|
}
|
|
15933
|
-
const delegation = portableFromOpenKeyDelegation(delegationData,
|
|
16034
|
+
const delegation = portableFromOpenKeyDelegation(delegationData, request, ctx.host, proof);
|
|
15934
16035
|
grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
|
|
15935
16036
|
}
|
|
15936
|
-
await activateAndStoreOpenKeyGrants(ctx.profile, node, grants, options.manifest ? "manifest" : "cli");
|
|
16037
|
+
await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants, options.manifest ? "manifest" : "cli");
|
|
15937
16038
|
const delegationCids2 = grants.map(({ delegation }) => delegation.cid);
|
|
15938
16039
|
const expiry2 = grants.at(-1)?.delegation.expiry.toISOString();
|
|
15939
16040
|
reportDeclined(declined);
|
|
@@ -15969,8 +16070,7 @@ function registerAuthCommand(program) {
|
|
|
15969
16070
|
for (const delegation of delegations) {
|
|
15970
16071
|
const covering = permissionsFromDelegation(delegation);
|
|
15971
16072
|
localEffective.push(...covering);
|
|
15972
|
-
|
|
15973
|
-
await appendAdditionalDelegation(ctx.profile, stored);
|
|
16073
|
+
await appendAdditionalDelegation(ctx.profile, await cliGrantRecord(node, delegation, covering, ctx.host));
|
|
15974
16074
|
delegationCids.push(delegation.cid);
|
|
15975
16075
|
expiry = delegation.expiry.toISOString();
|
|
15976
16076
|
await appendGrantHistory(ctx.profile, {
|
|
@@ -16392,6 +16492,8 @@ async function importRequestBoundDelegation(ctx, artifact) {
|
|
|
16392
16492
|
{ profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
|
|
16393
16493
|
artifact
|
|
16394
16494
|
);
|
|
16495
|
+
const warnings = operationWarnings(result.warnings);
|
|
16496
|
+
const metadata = warnings.length === 0 ? void 0 : { warnings };
|
|
16395
16497
|
switch (result.status) {
|
|
16396
16498
|
case "ok": {
|
|
16397
16499
|
const output2 = result.output;
|
|
@@ -16404,22 +16506,25 @@ async function importRequestBoundDelegation(ctx, artifact) {
|
|
|
16404
16506
|
permissions: output2.effectivePermissions,
|
|
16405
16507
|
expiry: output2.expiry
|
|
16406
16508
|
});
|
|
16509
|
+
outputWarnings(warnings);
|
|
16407
16510
|
return;
|
|
16408
16511
|
}
|
|
16409
16512
|
case "authority_required":
|
|
16410
16513
|
throw new CLIError(
|
|
16411
16514
|
"AUTHORITY_REQUIRED",
|
|
16412
16515
|
"The active session requires additional authority before importing this delegation.",
|
|
16413
|
-
ExitCode.PERMISSION_DENIED
|
|
16516
|
+
ExitCode.PERMISSION_DENIED,
|
|
16517
|
+
metadata
|
|
16414
16518
|
);
|
|
16415
16519
|
case "setup_required":
|
|
16416
16520
|
throw new CLIError(
|
|
16417
16521
|
"SETUP_REQUIRED",
|
|
16418
16522
|
"The active profile requires setup before importing this delegation.",
|
|
16419
|
-
ExitCode.ERROR
|
|
16523
|
+
ExitCode.ERROR,
|
|
16524
|
+
metadata
|
|
16420
16525
|
);
|
|
16421
16526
|
case "error":
|
|
16422
|
-
throw cliErrorFromService(result.error);
|
|
16527
|
+
throw withSignInHint(cliErrorFromService({ ...result.error, meta: metadata }), ctx.profile, result.context.posture);
|
|
16423
16528
|
}
|
|
16424
16529
|
}
|
|
16425
16530
|
function isStoredDelegationLike(value) {
|
|
@@ -16444,9 +16549,10 @@ function normalizePortableDelegation(delegation) {
|
|
|
16444
16549
|
}
|
|
16445
16550
|
return { ...delegation, expiry };
|
|
16446
16551
|
}
|
|
16447
|
-
async function activateAndStoreOpenKeyGrants(profileName, node, grants, source) {
|
|
16448
|
-
for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
|
|
16552
|
+
async function activateAndStoreOpenKeyGrants(profileName, host, node, grants, source) {
|
|
16449
16553
|
if (grants.length === 0) return;
|
|
16554
|
+
const records = await Promise.all(grants.map(({ delegation, effective }) => cliGrantRecord(node, delegation, effective, host)));
|
|
16555
|
+
for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
|
|
16450
16556
|
await ProfileManager.withLock(profileName, async () => {
|
|
16451
16557
|
for (const { delegation, effective } of grants) {
|
|
16452
16558
|
await appendGrantHistory(profileName, {
|
|
@@ -16456,10 +16562,7 @@ async function activateAndStoreOpenKeyGrants(profileName, node, grants, source)
|
|
|
16456
16562
|
expiry: delegation.expiry.toISOString()
|
|
16457
16563
|
});
|
|
16458
16564
|
}
|
|
16459
|
-
await appendAdditionalDelegations(
|
|
16460
|
-
profileName,
|
|
16461
|
-
grants.map(({ delegation, effective }) => storedAdditionalDelegation(delegation, effective))
|
|
16462
|
-
);
|
|
16565
|
+
await appendAdditionalDelegations(profileName, records);
|
|
16463
16566
|
});
|
|
16464
16567
|
}
|
|
16465
16568
|
async function ensureDelegationAuthority(params) {
|
|
@@ -16482,20 +16585,22 @@ async function ensureDelegationAuthority(params) {
|
|
|
16482
16585
|
expectedOwner: pinnedOwner(params.profile),
|
|
16483
16586
|
expiry: expiryCap
|
|
16484
16587
|
};
|
|
16588
|
+
const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
|
|
16485
16589
|
const grants = [];
|
|
16486
16590
|
for (const group of groupPermissionsBySpace(params.requested)) {
|
|
16591
|
+
const request = grantRequestPermissions(group, anchorSpace);
|
|
16487
16592
|
const delegationData = await acquireOpenKey(params.profile.did, {
|
|
16488
16593
|
jwk: key,
|
|
16489
16594
|
host: params.ctx.host,
|
|
16490
|
-
permissions:
|
|
16595
|
+
permissions: request,
|
|
16491
16596
|
reason: permissionGrantReason(params.reason, group),
|
|
16492
16597
|
openkeyHost,
|
|
16493
16598
|
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
|
|
16494
16599
|
});
|
|
16495
|
-
const delegation = portableFromOpenKeyDelegation(delegationData,
|
|
16600
|
+
const delegation = portableFromOpenKeyDelegation(delegationData, request, params.ctx.host, proof);
|
|
16496
16601
|
grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
|
|
16497
16602
|
}
|
|
16498
|
-
await activateAndStoreOpenKeyGrants(params.ctx.profile, params.node, grants, "cli");
|
|
16603
|
+
await activateAndStoreOpenKeyGrants(params.ctx.profile, params.ctx.host, params.node, grants, "cli");
|
|
16499
16604
|
return;
|
|
16500
16605
|
}
|
|
16501
16606
|
if (isInteractive()) {
|
|
@@ -16517,7 +16622,7 @@ async function ensureDelegationAuthority(params) {
|
|
|
16517
16622
|
const covering = permissionsFromDelegation(delegation);
|
|
16518
16623
|
await appendAdditionalDelegation(
|
|
16519
16624
|
params.ctx.profile,
|
|
16520
|
-
|
|
16625
|
+
await cliGrantRecord(params.node, delegation, covering, params.ctx.host)
|
|
16521
16626
|
);
|
|
16522
16627
|
await appendGrantHistory(params.ctx.profile, {
|
|
16523
16628
|
addedCaps: covering,
|
|
@@ -16649,7 +16754,8 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
|
|
|
16649
16754
|
}
|
|
16650
16755
|
const returnedSpace = data.spaceId;
|
|
16651
16756
|
const effective = session.permissions;
|
|
16652
|
-
const
|
|
16757
|
+
const spaced = effective.filter((permission) => !isRawEncryptionPermission(permission));
|
|
16758
|
+
const primary = spaced.find((permission) => permission.service !== "tinycloud.capabilities") ?? spaced[0] ?? effective[0];
|
|
16653
16759
|
const resources = effective.map((permission) => ({
|
|
16654
16760
|
service: permission.service.slice("tinycloud.".length),
|
|
16655
16761
|
space: isVerifiedRawEncryptionPermission(permission) ? "encryption" : returnedSpace,
|
|
@@ -16669,7 +16775,9 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
|
|
|
16669
16775
|
delegateDID: data.verificationMethod,
|
|
16670
16776
|
ownerAddress: ownerParts[4],
|
|
16671
16777
|
chainId: Number(ownerParts[3]),
|
|
16672
|
-
host
|
|
16778
|
+
host,
|
|
16779
|
+
// Verified above; replay rebuilds the CACAO from it before trusting the grant.
|
|
16780
|
+
siweProof: { siwe: data.siwe, signature: data.signature }
|
|
16673
16781
|
};
|
|
16674
16782
|
}
|
|
16675
16783
|
async function rotateAuthKey(profileName, host, options = {}) {
|
|
@@ -18394,6 +18502,7 @@ async function runSecretOperation(params) {
|
|
|
18394
18502
|
reason: secretPermissionReason(params.action, params.name),
|
|
18395
18503
|
yes: true,
|
|
18396
18504
|
force: true,
|
|
18505
|
+
anchorSpace: params.space ?? SECRETS_SPACE3,
|
|
18397
18506
|
openKeyAcquisition: params.openKeyAcquisition
|
|
18398
18507
|
})
|
|
18399
18508
|
);
|
|
@@ -18478,7 +18587,26 @@ async function runSecretOperationAttempt(label, operation) {
|
|
|
18478
18587
|
throw error;
|
|
18479
18588
|
}
|
|
18480
18589
|
}
|
|
18590
|
+
function withOperationWarnings(error, warnings) {
|
|
18591
|
+
if (warnings === void 0 || warnings.length === 0) return error;
|
|
18592
|
+
const cliError = wrapError(error);
|
|
18593
|
+
cliError.metadata = { ...cliError.metadata, warnings };
|
|
18594
|
+
return cliError;
|
|
18595
|
+
}
|
|
18481
18596
|
async function invokeCanonicalSecretGet(params) {
|
|
18597
|
+
const warnings = /* @__PURE__ */ new Map();
|
|
18598
|
+
const collect = (result) => {
|
|
18599
|
+
for (const warning of result.warnings ?? []) warnings.set(JSON.stringify(warning), warning);
|
|
18600
|
+
return result;
|
|
18601
|
+
};
|
|
18602
|
+
try {
|
|
18603
|
+
const result = await canonicalSecretGet(params, collect);
|
|
18604
|
+
return warnings.size === 0 ? result : { ...result, warnings: [...warnings.values()] };
|
|
18605
|
+
} catch (error) {
|
|
18606
|
+
throw withOperationWarnings(error, [...warnings.values()]);
|
|
18607
|
+
}
|
|
18608
|
+
}
|
|
18609
|
+
async function canonicalSecretGet(params, collect) {
|
|
18482
18610
|
const auth = authOptions(params.options);
|
|
18483
18611
|
let ownerNode;
|
|
18484
18612
|
if (!auth?.privateKey) {
|
|
@@ -18501,10 +18629,10 @@ async function invokeCanonicalSecretGet(params) {
|
|
|
18501
18629
|
...params.scope === void 0 ? {} : { scope: params.scope },
|
|
18502
18630
|
...params.space === void 0 ? {} : { space: params.space }
|
|
18503
18631
|
};
|
|
18504
|
-
const invoke = () => withSpinner(
|
|
18632
|
+
const invoke = async () => collect(await withSpinner(
|
|
18505
18633
|
params.label,
|
|
18506
18634
|
() => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) : invokeOperation2("tinycloud.secrets.get", 1, target, input)
|
|
18507
|
-
);
|
|
18635
|
+
));
|
|
18508
18636
|
let first = await invoke();
|
|
18509
18637
|
if (first.status === "error" && first.error.code === "SESSION_NOT_FOUND" && auth?.privateKey === void 0) {
|
|
18510
18638
|
const profile2 = await ProfileManager.getProfile(params.ctx.profile);
|
|
@@ -18542,6 +18670,7 @@ async function invokeCanonicalSecretGet(params) {
|
|
|
18542
18670
|
reason: secretPermissionReason("get", params.name),
|
|
18543
18671
|
yes: true,
|
|
18544
18672
|
force: true,
|
|
18673
|
+
anchorSpace: params.space ?? SECRETS_SPACE3,
|
|
18545
18674
|
openKeyAcquisition: params.openKeyAcquisition
|
|
18546
18675
|
})
|
|
18547
18676
|
);
|
|
@@ -18553,7 +18682,7 @@ function throwCanonicalSecretGetError(result, name) {
|
|
|
18553
18682
|
throw new CLIError(
|
|
18554
18683
|
"PERMISSION_DENIED",
|
|
18555
18684
|
"Permission denied while reading secret",
|
|
18556
|
-
ExitCode.
|
|
18685
|
+
ExitCode.PERMISSION_DENIED
|
|
18557
18686
|
);
|
|
18558
18687
|
case "setup_required":
|
|
18559
18688
|
throw new CLIError(
|
|
@@ -18575,11 +18704,10 @@ ${result.setup.message}`, setup: result.setup }
|
|
|
18575
18704
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
18576
18705
|
throw new CLIError("NETWORK_ERROR", result.error.message, ExitCode.NETWORK_ERROR);
|
|
18577
18706
|
}
|
|
18578
|
-
|
|
18579
|
-
result.error.code,
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
);
|
|
18707
|
+
if (result.error.code === "PERMISSION_HINT_INVALID") {
|
|
18708
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.PERMISSION_DENIED);
|
|
18709
|
+
}
|
|
18710
|
+
throw withSignInHint(cliErrorFromService(result.error), result.context.profile, result.context.posture);
|
|
18583
18711
|
case "ok":
|
|
18584
18712
|
throw new Error("Expected a failed canonical secret result.");
|
|
18585
18713
|
}
|
|
@@ -19195,19 +19323,26 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
19195
19323
|
openKeyAcquisition
|
|
19196
19324
|
});
|
|
19197
19325
|
if (result.status !== "ok") {
|
|
19198
|
-
|
|
19326
|
+
try {
|
|
19327
|
+
throwCanonicalSecretGetError(result, name);
|
|
19328
|
+
} catch (error) {
|
|
19329
|
+
throw withOperationWarnings(error, result.warnings);
|
|
19330
|
+
}
|
|
19199
19331
|
}
|
|
19200
19332
|
const value = result.output.value;
|
|
19201
|
-
|
|
19202
|
-
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19208
|
-
|
|
19333
|
+
try {
|
|
19334
|
+
if (options.output) {
|
|
19335
|
+
await writeSecretFile(options.output, value);
|
|
19336
|
+
outputJson({ name, written: options.output });
|
|
19337
|
+
} else if (options.raw || options.valueOnly) {
|
|
19338
|
+
process.stdout.write(value);
|
|
19339
|
+
} else {
|
|
19340
|
+
outputJson({ name, value });
|
|
19341
|
+
}
|
|
19342
|
+
} catch (error) {
|
|
19343
|
+
throw withOperationWarnings(error, result.warnings);
|
|
19209
19344
|
}
|
|
19210
|
-
|
|
19345
|
+
outputWarnings(operationWarnings(result.warnings));
|
|
19211
19346
|
} catch (error) {
|
|
19212
19347
|
handleError(error);
|
|
19213
19348
|
}
|