@tinycloud/cli 0.6.0-beta.7 → 0.6.0-beta.9

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
@@ -620,6 +620,25 @@ async function localKeySignIn(options) {
620
620
  // src/auth/browser-auth.ts
621
621
  import { createServer } from "http";
622
622
  import { createInterface } from "readline";
623
+ var PRIVATE_JWK_FIELDS = /* @__PURE__ */ new Set([
624
+ "d",
625
+ "p",
626
+ "q",
627
+ "dp",
628
+ "dq",
629
+ "qi",
630
+ "oth",
631
+ "k"
632
+ ]);
633
+ function publicJwkForDelegation(jwk) {
634
+ const publicJwk = {};
635
+ for (const [key, value] of Object.entries(jwk)) {
636
+ if (!PRIVATE_JWK_FIELDS.has(key)) {
637
+ publicJwk[key] = value;
638
+ }
639
+ }
640
+ return publicJwk;
641
+ }
623
642
  async function startAuthFlow(did, options = {}) {
624
643
  if (options.paste) {
625
644
  return pasteFlow(did, options);
@@ -641,7 +660,9 @@ function buildAuthUrl(did, options = {}) {
641
660
  params.set("callback", options.callback);
642
661
  }
643
662
  if (options.jwk) {
644
- const jwkB64 = Buffer.from(JSON.stringify(options.jwk)).toString("base64url");
663
+ const jwkB64 = Buffer.from(
664
+ JSON.stringify(publicJwkForDelegation(options.jwk))
665
+ ).toString("base64url");
645
666
  params.set("jwk", jwkB64);
646
667
  }
647
668
  if (options.host) {
@@ -1381,6 +1402,15 @@ function registerAuthCommand(program2) {
1381
1402
  handleError(error);
1382
1403
  }
1383
1404
  });
1405
+ auth.command("rotate").description("Rotate the active profile session key").option("--paste", "Use manual paste mode instead of browser callback").action(async (options, cmd) => {
1406
+ try {
1407
+ const globalOpts = cmd.optsWithGlobals();
1408
+ const ctx = await ProfileManager.resolveContext(globalOpts);
1409
+ await rotateAuthKey(ctx.profile, ctx.host, { paste: options.paste });
1410
+ } catch (error) {
1411
+ handleError(error);
1412
+ }
1413
+ });
1384
1414
  auth.command("status").description("Show current authentication state").action(async (_options, cmd) => {
1385
1415
  try {
1386
1416
  const globalOpts = cmd.optsWithGlobals();
@@ -2106,16 +2136,74 @@ function inferDelegationExpiry(data) {
2106
2136
  }
2107
2137
  return new Date(Date.now() + 60 * 60 * 1e3);
2108
2138
  }
2109
- async function handleLocalAuth(profileName, host) {
2139
+ async function rotateAuthKey(profileName, host, options = {}) {
2140
+ const profile = await ProfileManager.getProfile(profileName);
2141
+ const posture = resolveProfilePosture(profile);
2142
+ const oldDid = profile.sessionDid ?? profile.did;
2143
+ if (posture === "delegate-session") {
2144
+ throw new CLIError(
2145
+ "ROTATE_DELEGATE_SESSION_UNSUPPORTED",
2146
+ `Profile "${profileName}" is a delegated session. Request or import a new owner delegation instead of rotating it locally.`,
2147
+ ExitCode.PERMISSION_DENIED
2148
+ );
2149
+ }
2150
+ if (profile.authMethod === "local" || posture === "local-owner-key") {
2151
+ if (!profile.privateKey) {
2152
+ throw new CLIError(
2153
+ "LOCAL_OWNER_KEY_REQUIRED",
2154
+ `Profile "${profileName}" does not have a local owner private key. Run \`tc auth login --method local\` first.`,
2155
+ ExitCode.AUTH_REQUIRED
2156
+ );
2157
+ }
2158
+ await ProfileManager.clearSession(profileName);
2159
+ const result2 = await handleLocalAuth(profileName, host, {
2160
+ emitOutput: false,
2161
+ forceSessionKey: true
2162
+ });
2163
+ outputRotationResult(result2.profile, profileName, oldDid, "local");
2164
+ return;
2165
+ }
2166
+ const { jwk, did } = await withSpinner("Generating session key...", async () => {
2167
+ return generateKey();
2168
+ });
2169
+ await ProfileManager.setKey(profileName, jwk);
2170
+ await ProfileManager.clearSession(profileName);
2171
+ await ProfileManager.setProfile(profileName, {
2172
+ ...profile,
2173
+ host,
2174
+ did,
2175
+ sessionDid: did,
2176
+ posture: profile.posture ?? "owner-openkey",
2177
+ operatorType: profile.operatorType ?? "human",
2178
+ authMethod: "openkey"
2179
+ });
2180
+ const result = await refreshOpenKeySession(profileName, host, {
2181
+ paste: options.paste
2182
+ });
2183
+ outputRotationResult(result.profile, profileName, oldDid, "openkey");
2184
+ }
2185
+ function outputRotationResult(profile, profileName, oldDid, authMethod) {
2186
+ outputJson({
2187
+ rotated: true,
2188
+ profile: profileName,
2189
+ oldDid,
2190
+ did: profile.did,
2191
+ sessionDid: profile.sessionDid ?? null,
2192
+ authMethod,
2193
+ spaceId: profile.spaceId ?? null
2194
+ });
2195
+ }
2196
+ async function handleLocalAuth(profileName, host, options = {}) {
2110
2197
  const profile = await ProfileManager.getProfile(profileName).catch(() => null);
2198
+ const posture = profile ? resolveProfilePosture(profile) : null;
2111
2199
  let privateKey;
2112
2200
  let address;
2113
2201
  let did;
2114
2202
  let sessionDid = profile?.sessionDid;
2115
- if (profile?.authMethod === "local" && profile.privateKey && profile.address) {
2203
+ if ((profile?.authMethod === "local" || posture === "local-owner-key") && profile.privateKey) {
2116
2204
  privateKey = profile.privateKey;
2117
- address = profile.address;
2118
- did = profile.did;
2205
+ address = profile.address ?? await deriveAddress(privateKey);
2206
+ did = profile.did.startsWith("did:pkh:") ? profile.did : addressToDID(address, profile.chainId ?? DEFAULT_CHAIN_ID);
2119
2207
  if (isInteractive()) {
2120
2208
  process.stderr.write(theme.muted("Using existing local key") + "\n");
2121
2209
  process.stderr.write(formatField("Address", address) + "\n");
@@ -2134,7 +2222,7 @@ async function handleLocalAuth(profileName, host) {
2134
2222
  }
2135
2223
  }
2136
2224
  const hasKey = await ProfileManager.getKey(profileName);
2137
- if (!hasKey) {
2225
+ if (options.forceSessionKey || !hasKey) {
2138
2226
  const { jwk, did: generatedSessionDid } = await withSpinner("Generating session key...", async () => {
2139
2227
  return generateKey();
2140
2228
  });
@@ -2159,7 +2247,7 @@ async function handleLocalAuth(profileName, host) {
2159
2247
  signature: sessionResult.signature
2160
2248
  });
2161
2249
  sessionDid = sessionResult.verificationMethod;
2162
- await ProfileManager.setProfile(profileName, {
2250
+ const updatedProfile = {
2163
2251
  ...profile,
2164
2252
  name: profileName,
2165
2253
  host,
@@ -2175,16 +2263,20 @@ async function handleLocalAuth(profileName, host) {
2175
2263
  authMethod: "local",
2176
2264
  privateKey,
2177
2265
  address
2178
- });
2179
- outputJson({
2180
- authenticated: true,
2181
- profile: profileName,
2182
- did,
2183
- sessionDid,
2184
- address,
2185
- spaceId: sessionResult.spaceId,
2186
- authMethod: "local"
2187
- });
2266
+ };
2267
+ await ProfileManager.setProfile(profileName, updatedProfile);
2268
+ if (options.emitOutput ?? true) {
2269
+ outputJson({
2270
+ authenticated: true,
2271
+ profile: profileName,
2272
+ did,
2273
+ sessionDid,
2274
+ address,
2275
+ spaceId: sessionResult.spaceId,
2276
+ authMethod: "local"
2277
+ });
2278
+ }
2279
+ return { profile: updatedProfile, sessionResult };
2188
2280
  }
2189
2281
  async function handleOpenKeyAuth(profileName, host, paste) {
2190
2282
  const { profile, delegationData } = await refreshOpenKeySession(profileName, host, { paste });
@@ -2957,7 +3049,7 @@ _tc_completions() {
2957
3049
  commands="init auth kv space delegation share node profile completion"
2958
3050
 
2959
3051
  case "\${COMP_WORDS[1]}" in
2960
- auth) subcommands="login logout status whoami" ;;
3052
+ auth) subcommands="login logout rotate status whoami" ;;
2961
3053
  kv) subcommands="get put delete list head" ;;
2962
3054
  space) subcommands="list create info switch" ;;
2963
3055
  delegation) subcommands="create list info revoke" ;;
@@ -3007,7 +3099,7 @@ _tc() {
3007
3099
  ;;
3008
3100
  args)
3009
3101
  case $words[1] in
3010
- auth) _values 'subcommand' login logout status whoami ;;
3102
+ auth) _values 'subcommand' login logout rotate status whoami ;;
3011
3103
  kv) _values 'subcommand' get put delete list head ;;
3012
3104
  space) _values 'subcommand' list create info switch ;;
3013
3105
  delegation) _values 'subcommand' create list info revoke ;;
@@ -3042,7 +3134,7 @@ complete -c tc -n "not __fish_seen_subcommand_from $commands" -a profile -d "Pro
3042
3134
  complete -c tc -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
3043
3135
 
3044
3136
  # Subcommands
3045
- complete -c tc -n "__fish_seen_subcommand_from auth" -a "login logout status whoami"
3137
+ complete -c tc -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
3046
3138
  complete -c tc -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
3047
3139
  complete -c tc -n "__fish_seen_subcommand_from space" -a "list create info switch"
3048
3140
  complete -c tc -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
@@ -3241,6 +3333,12 @@ import {
3241
3333
  resolveSecretPath
3242
3334
  } from "@tinycloud/node-sdk";
3243
3335
  var SECRETS_SPACE = "secrets";
3336
+ var SECRET_KV_ABILITIES = {
3337
+ get: "tinycloud.kv/get",
3338
+ put: "tinycloud.kv/put",
3339
+ del: "tinycloud.kv/del",
3340
+ list: "tinycloud.kv/list"
3341
+ };
3244
3342
  async function readStdin4() {
3245
3343
  const chunks = [];
3246
3344
  for await (const chunk of process.stdin) {
@@ -3326,20 +3424,23 @@ function parseDate(value) {
3326
3424
  const date = new Date(value);
3327
3425
  return Number.isNaN(date.getTime()) ? null : date;
3328
3426
  }
3427
+ function secretKvAbility(action) {
3428
+ return SECRET_KV_ABILITIES[action];
3429
+ }
3329
3430
  function secretPermissionEntries(params) {
3330
3431
  const path = params.action === "list" ? resolveSecretListPrefix(params.options) : resolveSecretPath(params.name ?? "", params.options).permissionPaths.vault;
3331
3432
  const permissions = [{
3332
3433
  service: "tinycloud.kv",
3333
3434
  space: SECRETS_SPACE,
3334
3435
  path,
3335
- actions: [params.action],
3436
+ actions: [secretKvAbility(params.action)],
3336
3437
  skipPrefix: true
3337
3438
  }];
3338
3439
  if (params.action === "get") {
3339
3440
  permissions.push({
3340
3441
  service: "tinycloud.encryption",
3341
3442
  path: params.node.getDefaultEncryptionNetworkId(),
3342
- actions: ["decrypt"],
3443
+ actions: ["tinycloud.encryption/decrypt"],
3343
3444
  skipPrefix: true
3344
3445
  });
3345
3446
  }