@tinycloud/cli 0.6.0-beta.8 → 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
@@ -1402,6 +1402,15 @@ function registerAuthCommand(program2) {
1402
1402
  handleError(error);
1403
1403
  }
1404
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
+ });
1405
1414
  auth.command("status").description("Show current authentication state").action(async (_options, cmd) => {
1406
1415
  try {
1407
1416
  const globalOpts = cmd.optsWithGlobals();
@@ -2127,16 +2136,74 @@ function inferDelegationExpiry(data) {
2127
2136
  }
2128
2137
  return new Date(Date.now() + 60 * 60 * 1e3);
2129
2138
  }
2130
- 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 = {}) {
2131
2197
  const profile = await ProfileManager.getProfile(profileName).catch(() => null);
2198
+ const posture = profile ? resolveProfilePosture(profile) : null;
2132
2199
  let privateKey;
2133
2200
  let address;
2134
2201
  let did;
2135
2202
  let sessionDid = profile?.sessionDid;
2136
- if (profile?.authMethod === "local" && profile.privateKey && profile.address) {
2203
+ if ((profile?.authMethod === "local" || posture === "local-owner-key") && profile.privateKey) {
2137
2204
  privateKey = profile.privateKey;
2138
- address = profile.address;
2139
- 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);
2140
2207
  if (isInteractive()) {
2141
2208
  process.stderr.write(theme.muted("Using existing local key") + "\n");
2142
2209
  process.stderr.write(formatField("Address", address) + "\n");
@@ -2155,7 +2222,7 @@ async function handleLocalAuth(profileName, host) {
2155
2222
  }
2156
2223
  }
2157
2224
  const hasKey = await ProfileManager.getKey(profileName);
2158
- if (!hasKey) {
2225
+ if (options.forceSessionKey || !hasKey) {
2159
2226
  const { jwk, did: generatedSessionDid } = await withSpinner("Generating session key...", async () => {
2160
2227
  return generateKey();
2161
2228
  });
@@ -2180,7 +2247,7 @@ async function handleLocalAuth(profileName, host) {
2180
2247
  signature: sessionResult.signature
2181
2248
  });
2182
2249
  sessionDid = sessionResult.verificationMethod;
2183
- await ProfileManager.setProfile(profileName, {
2250
+ const updatedProfile = {
2184
2251
  ...profile,
2185
2252
  name: profileName,
2186
2253
  host,
@@ -2196,16 +2263,20 @@ async function handleLocalAuth(profileName, host) {
2196
2263
  authMethod: "local",
2197
2264
  privateKey,
2198
2265
  address
2199
- });
2200
- outputJson({
2201
- authenticated: true,
2202
- profile: profileName,
2203
- did,
2204
- sessionDid,
2205
- address,
2206
- spaceId: sessionResult.spaceId,
2207
- authMethod: "local"
2208
- });
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 };
2209
2280
  }
2210
2281
  async function handleOpenKeyAuth(profileName, host, paste) {
2211
2282
  const { profile, delegationData } = await refreshOpenKeySession(profileName, host, { paste });
@@ -2978,7 +3049,7 @@ _tc_completions() {
2978
3049
  commands="init auth kv space delegation share node profile completion"
2979
3050
 
2980
3051
  case "\${COMP_WORDS[1]}" in
2981
- auth) subcommands="login logout status whoami" ;;
3052
+ auth) subcommands="login logout rotate status whoami" ;;
2982
3053
  kv) subcommands="get put delete list head" ;;
2983
3054
  space) subcommands="list create info switch" ;;
2984
3055
  delegation) subcommands="create list info revoke" ;;
@@ -3028,7 +3099,7 @@ _tc() {
3028
3099
  ;;
3029
3100
  args)
3030
3101
  case $words[1] in
3031
- auth) _values 'subcommand' login logout status whoami ;;
3102
+ auth) _values 'subcommand' login logout rotate status whoami ;;
3032
3103
  kv) _values 'subcommand' get put delete list head ;;
3033
3104
  space) _values 'subcommand' list create info switch ;;
3034
3105
  delegation) _values 'subcommand' create list info revoke ;;
@@ -3063,7 +3134,7 @@ complete -c tc -n "not __fish_seen_subcommand_from $commands" -a profile -d "Pro
3063
3134
  complete -c tc -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
3064
3135
 
3065
3136
  # Subcommands
3066
- 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"
3067
3138
  complete -c tc -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
3068
3139
  complete -c tc -n "__fish_seen_subcommand_from space" -a "list create info switch"
3069
3140
  complete -c tc -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
@@ -3262,6 +3333,12 @@ import {
3262
3333
  resolveSecretPath
3263
3334
  } from "@tinycloud/node-sdk";
3264
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
+ };
3265
3342
  async function readStdin4() {
3266
3343
  const chunks = [];
3267
3344
  for await (const chunk of process.stdin) {
@@ -3347,20 +3424,23 @@ function parseDate(value) {
3347
3424
  const date = new Date(value);
3348
3425
  return Number.isNaN(date.getTime()) ? null : date;
3349
3426
  }
3427
+ function secretKvAbility(action) {
3428
+ return SECRET_KV_ABILITIES[action];
3429
+ }
3350
3430
  function secretPermissionEntries(params) {
3351
3431
  const path = params.action === "list" ? resolveSecretListPrefix(params.options) : resolveSecretPath(params.name ?? "", params.options).permissionPaths.vault;
3352
3432
  const permissions = [{
3353
3433
  service: "tinycloud.kv",
3354
3434
  space: SECRETS_SPACE,
3355
3435
  path,
3356
- actions: [params.action],
3436
+ actions: [secretKvAbility(params.action)],
3357
3437
  skipPrefix: true
3358
3438
  }];
3359
3439
  if (params.action === "get") {
3360
3440
  permissions.push({
3361
3441
  service: "tinycloud.encryption",
3362
3442
  path: params.node.getDefaultEncryptionNetworkId(),
3363
- actions: ["decrypt"],
3443
+ actions: ["tinycloud.encryption/decrypt"],
3364
3444
  skipPrefix: true
3365
3445
  });
3366
3446
  }