@tinycloud/cli 1.2.0-beta.1 → 1.2.0-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/README.md +10 -1
- package/dist/index.js +81 -9
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +763 -61
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +4 -4
- package/skills/tc-cli/AUTH.md +19 -0
- package/skills/tc-cli/REFERENCE.md +53 -5
package/dist/legacy-entry.js
CHANGED
|
@@ -60,8 +60,12 @@ var init_constants = __esm({
|
|
|
60
60
|
PERMISSION_DENIED: 5,
|
|
61
61
|
NETWORK_ERROR: 6,
|
|
62
62
|
NODE_ERROR: 7,
|
|
63
|
+
/** `tc secrets wait` only: the deadline passed while a secret was still missing. */
|
|
64
|
+
PENDING: 8,
|
|
63
65
|
/** A write was refused because the owner's TinyCloud storage is full. */
|
|
64
|
-
STORAGE_FULL: 10
|
|
66
|
+
STORAGE_FULL: 10,
|
|
67
|
+
/** Interrupted by SIGINT/SIGTERM (`CANCELLED`). */
|
|
68
|
+
CANCELLED: 130
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
});
|
|
@@ -216,7 +220,7 @@ function warningLine(warnings) {
|
|
|
216
220
|
return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with tc auth grants remove <cid>.`;
|
|
217
221
|
}
|
|
218
222
|
function outputError(code3, message, hint, details = {}) {
|
|
219
|
-
const { meta, warnings = [] } = details;
|
|
223
|
+
const { meta, warnings = [], setup, recovery, progress } = details;
|
|
220
224
|
if (!shouldOutputJson()) {
|
|
221
225
|
process.stderr.write(
|
|
222
226
|
`${theme.error("\u2717")} ${theme.label(code3)}: ${message}
|
|
@@ -235,6 +239,9 @@ function outputError(code3, message, hint, details = {}) {
|
|
|
235
239
|
error: { code: code3, message }
|
|
236
240
|
};
|
|
237
241
|
if (hint) payload.error.hint = hint;
|
|
242
|
+
if (recovery) payload.error.recovery = recovery;
|
|
243
|
+
if (setup) payload.error.setup = setup;
|
|
244
|
+
if (progress) payload.error.progress = progress;
|
|
238
245
|
if (meta) payload.error.meta = meta;
|
|
239
246
|
if (warnings.length > 0) payload.error.warnings = warnings;
|
|
240
247
|
process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
|
|
@@ -422,6 +429,13 @@ var init_storage2 = __esm({
|
|
|
422
429
|
import { readFileSync, readdirSync } from "fs";
|
|
423
430
|
import { join as join2 } from "path";
|
|
424
431
|
import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
432
|
+
import { isSafeRecovery } from "@tinycloud/operations/recovery";
|
|
433
|
+
import {
|
|
434
|
+
buildSecretRequestUrl,
|
|
435
|
+
isSafeRequestAccessRecovery,
|
|
436
|
+
isValidSecretName,
|
|
437
|
+
SECRET_REQUEST_MAX_NAMES
|
|
438
|
+
} from "@tinycloud/operations/secret-request";
|
|
425
439
|
import { authorizationVerdictOf, parseCapabilityResource, SERVICE_LONG_TO_SHORT, validatedCapabilityOf } from "@tinycloud/sdk-core";
|
|
426
440
|
function setActiveProfileName(name) {
|
|
427
441
|
activeProfileName = name;
|
|
@@ -519,12 +533,64 @@ function handleError(error) {
|
|
|
519
533
|
meta.resource = capability.resource;
|
|
520
534
|
meta.requiredAction = capability.requiredAction;
|
|
521
535
|
}
|
|
536
|
+
const deniedNames = cliError.metadata?.deniedNames;
|
|
537
|
+
const recovery = safeCliRecovery(cliError.metadata?.recovery, {
|
|
538
|
+
deniedNames: Array.isArray(deniedNames) && deniedNames.every((name) => typeof name === "string") ? deniedNames : []
|
|
539
|
+
});
|
|
540
|
+
const setup = cliError.code === "NOT_FOUND" || cliError.code === "PERMISSION_DENIED" ? safeCliSetup(cliError.metadata?.setup) : void 0;
|
|
541
|
+
const progress = cliError.code === "NETWORK_ERROR" || cliError.code === "CANCELLED" ? safeCliProgress(cliError.metadata?.progress) : void 0;
|
|
522
542
|
outputError(cliError.code, cliError.message, hint, {
|
|
543
|
+
...recovery ? { recovery } : {},
|
|
544
|
+
...setup ? { setup } : {},
|
|
545
|
+
...progress ? { progress } : {},
|
|
523
546
|
...Object.keys(meta).length ? { meta } : {},
|
|
524
547
|
warnings: mergeWarnings(drainWarnings(), operationWarnings(cliError.metadata?.warnings))
|
|
525
548
|
});
|
|
526
549
|
process.exit(cliError.exitCode);
|
|
527
550
|
}
|
|
551
|
+
function safeCliRecovery(value, context) {
|
|
552
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !("action" in value)) return void 0;
|
|
553
|
+
const action = value.action;
|
|
554
|
+
switch (action) {
|
|
555
|
+
case "sign_in":
|
|
556
|
+
case "complete_migration":
|
|
557
|
+
return isSafeRecovery(value, action) ? { ...value } : void 0;
|
|
558
|
+
case "request_access":
|
|
559
|
+
return isSafeRequestAccessRecovery(value, context.deniedNames) ? { ...value, names: [...value.names] } : void 0;
|
|
560
|
+
default:
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function safeCliSetup(value) {
|
|
565
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
566
|
+
const setup = { ...value };
|
|
567
|
+
if (!Object.keys(setup).every((key) => SETUP_KEYS[key] === true)) return void 0;
|
|
568
|
+
const { url, names, scope, owner, action } = setup;
|
|
569
|
+
if (action !== "add_secret" || typeof url !== "string" || typeof owner !== "string") return void 0;
|
|
570
|
+
if (!Array.isArray(names) || !names.every((name) => typeof name === "string")) return void 0;
|
|
571
|
+
if (scope !== void 0 && typeof scope !== "string") return void 0;
|
|
572
|
+
try {
|
|
573
|
+
const link3 = buildSecretRequestUrl({ owner, names, ...scope !== void 0 ? { scope } : {} });
|
|
574
|
+
if (link3.url !== url || link3.owner !== owner || link3.names.length !== names.length) return void 0;
|
|
575
|
+
} catch {
|
|
576
|
+
return void 0;
|
|
577
|
+
}
|
|
578
|
+
return { url, names: [...names], ...scope !== void 0 ? { scope } : {}, owner, action };
|
|
579
|
+
}
|
|
580
|
+
function safeNameList(value) {
|
|
581
|
+
if (!Array.isArray(value) || value.length > SECRET_REQUEST_MAX_NAMES) return void 0;
|
|
582
|
+
if (!value.every((name) => typeof name === "string" && isValidSecretName(name))) return void 0;
|
|
583
|
+
if (new Set(value).size !== value.length) return void 0;
|
|
584
|
+
return [...value];
|
|
585
|
+
}
|
|
586
|
+
function safeCliProgress(value) {
|
|
587
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
588
|
+
if (!("present" in value) || !("pending" in value) || Object.keys(value).length !== 2) return void 0;
|
|
589
|
+
const present = safeNameList(value.present);
|
|
590
|
+
const pending = safeNameList(value.pending);
|
|
591
|
+
if (!present || !pending || present.some((name) => pending.includes(name))) return void 0;
|
|
592
|
+
return { present, pending };
|
|
593
|
+
}
|
|
528
594
|
function buildAuthHint(error) {
|
|
529
595
|
const capability = validatedCapabilityOf({ meta: error.metadata });
|
|
530
596
|
if (!capability) return void 0;
|
|
@@ -586,7 +652,7 @@ function buildNetworkHint() {
|
|
|
586
652
|
lines.push(`Or override per-command with --host or TC_HOST.`);
|
|
587
653
|
return lines.join("\n");
|
|
588
654
|
}
|
|
589
|
-
var activeProfileName, CLIError;
|
|
655
|
+
var activeProfileName, CLIError, SETUP_KEYS;
|
|
590
656
|
var init_errors = __esm({
|
|
591
657
|
"src/output/errors.ts"() {
|
|
592
658
|
"use strict";
|
|
@@ -605,6 +671,7 @@ var init_errors = __esm({
|
|
|
605
671
|
}
|
|
606
672
|
status;
|
|
607
673
|
};
|
|
674
|
+
SETUP_KEYS = { url: true, names: true, scope: true, owner: true, action: true };
|
|
608
675
|
}
|
|
609
676
|
});
|
|
610
677
|
|
|
@@ -7470,6 +7537,12 @@ function defaultVaultMessage(input) {
|
|
|
7470
7537
|
return input.message ?? `Public key not found for ${input.did}`;
|
|
7471
7538
|
case "STORAGE_ERROR":
|
|
7472
7539
|
return input.message ?? input.cause.message;
|
|
7540
|
+
case "ALREADY_EXISTS":
|
|
7541
|
+
return input.message ?? `A value already exists at ${input.key}`;
|
|
7542
|
+
case "AUTH_UNAUTHORIZED":
|
|
7543
|
+
return input.message;
|
|
7544
|
+
case "PERMISSION_DENIED":
|
|
7545
|
+
return input.message;
|
|
7473
7546
|
case "STORAGE_QUOTA_EXCEEDED":
|
|
7474
7547
|
case "STORAGE_LIMIT_REACHED":
|
|
7475
7548
|
return input.message;
|
|
@@ -7483,13 +7556,33 @@ function vaultError(input) {
|
|
|
7483
7556
|
};
|
|
7484
7557
|
return { ok: false, error };
|
|
7485
7558
|
}
|
|
7559
|
+
function typedKvCause(error) {
|
|
7560
|
+
return Object.assign(new Error(error.message), {
|
|
7561
|
+
name: "ServiceError",
|
|
7562
|
+
code: error.code,
|
|
7563
|
+
service: error.service,
|
|
7564
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
7565
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
7566
|
+
});
|
|
7567
|
+
}
|
|
7486
7568
|
function kvWriteError(context, error) {
|
|
7487
7569
|
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
7488
7570
|
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
7489
7571
|
}
|
|
7572
|
+
const message = `${context}: ${error.message}`;
|
|
7573
|
+
if (error.code === ErrorCodes.AUTH_UNAUTHORIZED) {
|
|
7574
|
+
const code3 = authorizationVerdictOf2(error) === "unauthenticated" ? "AUTH_UNAUTHORIZED" : "PERMISSION_DENIED";
|
|
7575
|
+
return vaultError({
|
|
7576
|
+
code: code3,
|
|
7577
|
+
message,
|
|
7578
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
7579
|
+
cause: typedKvCause(error)
|
|
7580
|
+
});
|
|
7581
|
+
}
|
|
7490
7582
|
return vaultError({
|
|
7491
7583
|
code: "STORAGE_ERROR",
|
|
7492
|
-
cause: new Error(
|
|
7584
|
+
cause: new Error(message, { cause: typedKvCause(error) }),
|
|
7585
|
+
...error.meta !== void 0 ? { meta: error.meta } : {}
|
|
7493
7586
|
});
|
|
7494
7587
|
}
|
|
7495
7588
|
function canonicalizeSecretScope(scope) {
|
|
@@ -8229,7 +8322,7 @@ function encOk(data) {
|
|
|
8229
8322
|
function encErr(error) {
|
|
8230
8323
|
return { ok: false, error };
|
|
8231
8324
|
}
|
|
8232
|
-
var import_decrypt_transport_response_error, ErrorCodes, defaultRetryPolicy, TelemetryEvents, REDACTED, SAFE_NUMBER_FIELDS, SAFE_BOOLEAN_FIELDS, DEBUG_FLAG, MAX_EVENTS, TinyCloudDebugLogger, tinyCloudDebugLogger, ServiceErrorSchema, GenericResultSchema, KVResponseHeadersSchema, GenericKVResponseSchema, KVListResponseSchema, KVListResultSchema, ServiceRequestEventSchema, ServiceResponseEventSchema, ServiceErrorEventSchema, ServiceRetryEventSchema, TelemetrySpanEventSchema, RetryPolicySchema, ServiceSessionSchema, URN_PREFIX, NETWORK_NAME_RE, PKH_EIP155_DID_RE, NetworkIdError, RequestTimeoutError, grantableActions, capabilityServices, MAX_CAUSE_DEPTH2, STORAGE_FULL_MESSAGE, STORAGE_WRITE_TOO_LARGE_MESSAGE, MAX_TIMER_DELAY_MS, BaseService, PrefixedKVService, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, KVAction, MAX_KV_BATCH_READ_ITEMS, pathByteEncoder, REVOKED_BODY, KVService, SQLMigrations, DatabaseHandle, SQLAction, DDL_TOKENS, MIGRATIONS_TABLE, MIGRATIONS_SCHEMA, MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID, SQLService, DuckDbDatabaseHandle, DuckDbAction, DuckDbService, AsyncQueue, HOOK_STREAM_RETRY_BASE_DELAY_MS, HOOK_STREAM_RETRY_MAX_DELAY_MS, HOOK_STREAM_HEALTHY_MS, HooksService, SUPERSEDED, STREAM_ERROR_NAMES, STREAM_ERROR_CODES, VaultVersionConfig, CURRENT_VAULT_VERSION, VaultHeaders, DB_NAME, DB_VERSION, STORE_NAME, WRAP_KEY_ID, DataVaultService, SECRET_NAME_RE, SECRET_PREFIX, SCOPED_SECRET_PREFIX, RESERVED_SECRET_SCOPES, HEX, DEFAULT_ENCRYPTION_ALG, ENVELOPE_VERSION, DEFAULT_KEY_VERSION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, EncryptionService;
|
|
8325
|
+
var import_decrypt_transport_response_error, ErrorCodes, defaultRetryPolicy, TelemetryEvents, REDACTED, SAFE_NUMBER_FIELDS, SAFE_BOOLEAN_FIELDS, DEBUG_FLAG, MAX_EVENTS, TinyCloudDebugLogger, tinyCloudDebugLogger, ServiceErrorSchema, GenericResultSchema, KVResponseHeadersSchema, GenericKVResponseSchema, KVListResponseSchema, KVListResultSchema, ServiceRequestEventSchema, ServiceResponseEventSchema, ServiceErrorEventSchema, ServiceRetryEventSchema, TelemetrySpanEventSchema, RetryPolicySchema, ServiceSessionSchema, URN_PREFIX, NETWORK_NAME_RE, PKH_EIP155_DID_RE, NetworkIdError, RequestTimeoutError, grantableActions, capabilityServices, MAX_CAUSE_DEPTH2, STORAGE_FULL_MESSAGE, STORAGE_WRITE_TOO_LARGE_MESSAGE, MAX_TIMER_DELAY_MS, BaseService, PrefixedKVService, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, KVAction, MAX_KV_BATCH_READ_ITEMS, pathByteEncoder, REVOKED_BODY, KVService, SQLMigrations, DatabaseHandle, SQLAction, DDL_TOKENS, MIGRATIONS_TABLE, MIGRATIONS_SCHEMA, MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID, SQLService, DuckDbDatabaseHandle, DuckDbAction, DuckDbService, AsyncQueue, HOOK_STREAM_RETRY_BASE_DELAY_MS, HOOK_STREAM_RETRY_MAX_DELAY_MS, HOOK_STREAM_HEALTHY_MS, HooksService, SUPERSEDED, STREAM_ERROR_NAMES, STREAM_ERROR_CODES, VaultVersionConfig, CURRENT_VAULT_VERSION, VaultHeaders, DB_NAME, DB_VERSION, STORE_NAME, WRAP_KEY_ID, DataVaultService, SECRET_NAME_RE, SECRET_PREFIX, SCOPED_SECRET_PREFIX, RESERVED_SECRET_SCOPES, TC832_RESERVED_SCOPES, HEX, DEFAULT_ENCRYPTION_ALG, ENVELOPE_VERSION, DEFAULT_KEY_VERSION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, EncryptionService;
|
|
8233
8326
|
var init_dist2 = __esm({
|
|
8234
8327
|
"../sdk-services/dist/index.js"() {
|
|
8235
8328
|
"use strict";
|
|
@@ -9529,7 +9622,7 @@ var init_dist2 = __esm({
|
|
|
9529
9622
|
);
|
|
9530
9623
|
}
|
|
9531
9624
|
if (response.status === 404) {
|
|
9532
|
-
return this.classifyNotFound(response, key);
|
|
9625
|
+
return await this.classifyNotFound(response, key);
|
|
9533
9626
|
}
|
|
9534
9627
|
const errorText = await response.text();
|
|
9535
9628
|
if (response.status === 413) {
|
|
@@ -10098,7 +10191,7 @@ var init_dist2 = __esm({
|
|
|
10098
10191
|
);
|
|
10099
10192
|
}
|
|
10100
10193
|
if (response.status === 404) {
|
|
10101
|
-
return this.classifyNotFound(response, key);
|
|
10194
|
+
return await this.classifyNotFound(response, key);
|
|
10102
10195
|
}
|
|
10103
10196
|
const errorText = await response.text();
|
|
10104
10197
|
if (response.status === 412) {
|
|
@@ -10165,7 +10258,7 @@ var init_dist2 = __esm({
|
|
|
10165
10258
|
);
|
|
10166
10259
|
}
|
|
10167
10260
|
if (response.status === 404) {
|
|
10168
|
-
return this.classifyNotFound(response, key);
|
|
10261
|
+
return await this.classifyNotFound(response, key);
|
|
10169
10262
|
}
|
|
10170
10263
|
const errorText = await response.text();
|
|
10171
10264
|
return err(
|
|
@@ -10231,7 +10324,7 @@ var init_dist2 = __esm({
|
|
|
10231
10324
|
signal: request.signal
|
|
10232
10325
|
});
|
|
10233
10326
|
if (!response.ok) {
|
|
10234
|
-
return this.createSignedReadUrlError(response, key, path);
|
|
10327
|
+
return await this.createSignedReadUrlError(response, key, path);
|
|
10235
10328
|
}
|
|
10236
10329
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
10237
10330
|
await response.json()
|
|
@@ -12008,11 +12101,18 @@ var init_dist2 = __esm({
|
|
|
12008
12101
|
cause: new Error(envelopeResult.error.message)
|
|
12009
12102
|
});
|
|
12010
12103
|
}
|
|
12011
|
-
const
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
);
|
|
12104
|
+
const valuePath = `vault/${key}`;
|
|
12105
|
+
const valueBody = JSON.stringify(envelopeResult.data);
|
|
12106
|
+
const valuePutResult = options?.ifNoneMatch !== void 0 ? await this.tc.kv.put(valuePath, valueBody, { ifNoneMatch: options.ifNoneMatch }) : await this.tc.kv.put(valuePath, valueBody);
|
|
12015
12107
|
if (!valuePutResult.ok) {
|
|
12108
|
+
if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
|
|
12109
|
+
return vaultError({
|
|
12110
|
+
code: "ALREADY_EXISTS",
|
|
12111
|
+
key,
|
|
12112
|
+
...valuePutResult.error.meta !== void 0 ? { meta: valuePutResult.error.meta } : {},
|
|
12113
|
+
cause: typedKvCause(valuePutResult.error)
|
|
12114
|
+
});
|
|
12115
|
+
}
|
|
12016
12116
|
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
12017
12117
|
}
|
|
12018
12118
|
return { ok: true, data: void 0 };
|
|
@@ -12180,6 +12280,14 @@ var init_dist2 = __esm({
|
|
|
12180
12280
|
if (this.usesNetworkEncryption) {
|
|
12181
12281
|
return this.putNetworkEncrypted(key, value, options);
|
|
12182
12282
|
}
|
|
12283
|
+
if (options?.ifNoneMatch !== void 0) {
|
|
12284
|
+
return vaultError({
|
|
12285
|
+
code: "STORAGE_ERROR",
|
|
12286
|
+
cause: new Error(
|
|
12287
|
+
"ifNoneMatch is only supported by network-encrypted vaults"
|
|
12288
|
+
)
|
|
12289
|
+
});
|
|
12290
|
+
}
|
|
12183
12291
|
if (!this._isUnlocked || !this.masterKey) {
|
|
12184
12292
|
return vaultError({
|
|
12185
12293
|
code: "VAULT_LOCKED",
|
|
@@ -12973,6 +13081,11 @@ var init_dist2 = __esm({
|
|
|
12973
13081
|
SECRET_PREFIX = "secrets/";
|
|
12974
13082
|
SCOPED_SECRET_PREFIX = "secrets/scoped/";
|
|
12975
13083
|
RESERVED_SECRET_SCOPES = /* @__PURE__ */ new Set(["default", "global"]);
|
|
13084
|
+
TC832_RESERVED_SCOPES = Object.freeze([
|
|
13085
|
+
"default",
|
|
13086
|
+
"global",
|
|
13087
|
+
"scoped"
|
|
13088
|
+
]);
|
|
12976
13089
|
HEX = "0123456789abcdef";
|
|
12977
13090
|
DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
12978
13091
|
ENVELOPE_VERSION = 1;
|
|
@@ -13904,6 +14017,8 @@ import {
|
|
|
13904
14017
|
buildPermissionRequestArtifact,
|
|
13905
14018
|
isPermissionRequestArtifact
|
|
13906
14019
|
} from "@tinycloud/operations/artifacts";
|
|
14020
|
+
import { isLocalOwnerProfile } from "@tinycloud/operations/profile";
|
|
14021
|
+
import { resolveSecretsOwner } from "@tinycloud/operations/secret-request";
|
|
13907
14022
|
import {
|
|
13908
14023
|
additionalDelegationsPath as sharedAdditionalDelegationsPath,
|
|
13909
14024
|
authRequestsPath as sharedAuthRequestsPath,
|
|
@@ -14190,17 +14305,26 @@ async function secretPermissionsFromAppManifest(manifest, profile, requestedOwne
|
|
|
14190
14305
|
}
|
|
14191
14306
|
async function defaultSecretsNetworkId(profileName2, requestedOwner) {
|
|
14192
14307
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
14193
|
-
const
|
|
14194
|
-
const
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14308
|
+
const requested = requestedOwner === void 0 ? void 0 : canonicalOwnerDid(requestedOwner.split("#")[0]);
|
|
14309
|
+
const resolution = resolveSecretsOwner({
|
|
14310
|
+
local: isLocalOwnerProfile(profile),
|
|
14311
|
+
posture: resolveProfilePosture(profile),
|
|
14312
|
+
...profile.ownerDid === void 0 ? {} : { ownerDid: profile.ownerDid.split("#")[0] },
|
|
14313
|
+
...profile.did === void 0 ? {} : { principalDid: profile.did.split("#")[0] },
|
|
14314
|
+
...profile.spaceId === void 0 ? {} : { space: profile.spaceId }
|
|
14315
|
+
}, requested);
|
|
14316
|
+
if (!resolution.ok) {
|
|
14317
|
+
throw resolution.code === "OWNER_MISMATCH" ? new CLIError(
|
|
14318
|
+
"OWNER_MISMATCH",
|
|
14319
|
+
`--owner does not match the secrets owner of profile "${profileName2}". ${resolution.message}`,
|
|
14320
|
+
ExitCode.USAGE_ERROR
|
|
14321
|
+
) : new CLIError(
|
|
14198
14322
|
"OWNER_DID_UNKNOWN",
|
|
14199
14323
|
`Cannot determine the secrets owner for profile "${profileName2}". Pass --owner did:pkh:eip155:CHAIN:ADDRESS with --manifest.`,
|
|
14200
14324
|
ExitCode.AUTH_REQUIRED
|
|
14201
14325
|
);
|
|
14202
14326
|
}
|
|
14203
|
-
return `urn:tinycloud:encryption:${
|
|
14327
|
+
return `urn:tinycloud:encryption:${resolution.owner}:default`;
|
|
14204
14328
|
}
|
|
14205
14329
|
function permissionsFromDelegation(delegation) {
|
|
14206
14330
|
if (delegation.resources?.length) {
|
|
@@ -14335,14 +14459,17 @@ var init_permissions = __esm({
|
|
|
14335
14459
|
});
|
|
14336
14460
|
|
|
14337
14461
|
// src/auth/session-expired.ts
|
|
14462
|
+
import { signInRecovery } from "@tinycloud/operations/recovery";
|
|
14338
14463
|
function signInAgainHint(profileName2, posture) {
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14464
|
+
const delegate = posture === "delegate-session";
|
|
14465
|
+
const { command } = signInRecovery(
|
|
14466
|
+
profileName2,
|
|
14467
|
+
delegate ? "delegate-session" : posture === "local-owner-key" ? "local-owner-key" : "owner-openkey"
|
|
14468
|
+
);
|
|
14469
|
+
if (command === void 0) {
|
|
14470
|
+
return delegate ? `Have the owner approve a new scoped login for profile ${JSON.stringify(profileName2)} with tc auth login --method openkey --paste --manifest <manifest.json>.` : `Sign in again to profile ${JSON.stringify(profileName2)} with tc auth login.`;
|
|
14344
14471
|
}
|
|
14345
|
-
return `
|
|
14472
|
+
return delegate ? `Have the owner approve a new scoped login: ${command}. Pass the owner's code on stdin, newline-terminated.` : `Sign in again with: ${command}`;
|
|
14346
14473
|
}
|
|
14347
14474
|
function sessionExpiredError(profileName2, posture) {
|
|
14348
14475
|
return new CLIError(
|
|
@@ -19439,8 +19566,8 @@ async function callbackFlow(did, options = {}) {
|
|
|
19439
19566
|
}
|
|
19440
19567
|
if (openBrowser) {
|
|
19441
19568
|
try {
|
|
19442
|
-
const
|
|
19443
|
-
await
|
|
19569
|
+
const open8 = (await import("open")).default;
|
|
19570
|
+
await open8(authUrl);
|
|
19444
19571
|
} catch {
|
|
19445
19572
|
settle({ error: new Error("Failed to open browser") });
|
|
19446
19573
|
return;
|
|
@@ -19593,8 +19720,8 @@ async function localKeySignIn(options) {
|
|
|
19593
19720
|
// src/auth/scoped-login.ts
|
|
19594
19721
|
init_errors();
|
|
19595
19722
|
init_constants();
|
|
19596
|
-
init_types();
|
|
19597
19723
|
import { NodeWasmBindings, signedCompactUcanExpiry as signedCompactUcanExpiry2 } from "@tinycloud/node-sdk";
|
|
19724
|
+
import { isLocalOwnerProfile as isLocalOwnerProfile2 } from "@tinycloud/operations/profile";
|
|
19598
19725
|
|
|
19599
19726
|
// src/lib/duration.ts
|
|
19600
19727
|
function parseDuration(input) {
|
|
@@ -19652,11 +19779,8 @@ function openKeyExpiryParam(expiry) {
|
|
|
19652
19779
|
}
|
|
19653
19780
|
return `${seconds}s`;
|
|
19654
19781
|
}
|
|
19655
|
-
function isLocalOwnerProfile(profile) {
|
|
19656
|
-
return resolveProfilePosture(profile) === "local-owner-key" || profile.authMethod === "local" || typeof profile.privateKey === "string";
|
|
19657
|
-
}
|
|
19658
19782
|
function pinnedOwner(profile) {
|
|
19659
|
-
return profile && !
|
|
19783
|
+
return profile && !isLocalOwnerProfile2(profile) ? profile.ownerDid : void 0;
|
|
19660
19784
|
}
|
|
19661
19785
|
var TRUST_FIELDS = ["ownerDid", "permissions", "permissionsSource", "expiresAt", "expiry", "expirationTime"];
|
|
19662
19786
|
function withoutTrustFields(session) {
|
|
@@ -19907,9 +20031,10 @@ init_constants();
|
|
|
19907
20031
|
init_profiles();
|
|
19908
20032
|
init_errors();
|
|
19909
20033
|
init_space();
|
|
20034
|
+
import { isLocalOwnerProfile as isLocalOwnerProfile3 } from "@tinycloud/operations/profile";
|
|
19910
20035
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError2 } from "@tinycloud/operations/state";
|
|
19911
20036
|
function assertNotLocalOwner(profileName2, profile, flow) {
|
|
19912
|
-
if (profile === null || !
|
|
20037
|
+
if (profile === null || !isLocalOwnerProfile3(profile)) return;
|
|
19913
20038
|
throw new CLIError(
|
|
19914
20039
|
"LOCAL_OWNER_PROFILE",
|
|
19915
20040
|
`Profile "${profileName2}" holds a local owner key. ${flow} would turn it into an OpenKey profile while keeping that key. Use a separate profile: \`tc init --name publisher --key-only\`, then \`tc --profile publisher auth login --device --manifest ...\`.`,
|
|
@@ -24160,8 +24285,21 @@ init_warnings();
|
|
|
24160
24285
|
init_constants();
|
|
24161
24286
|
import { readFile as readFile9 } from "fs/promises";
|
|
24162
24287
|
import { join as join9 } from "path";
|
|
24163
|
-
import { homedir } from "os";
|
|
24288
|
+
import { homedir, hostname, userInfo } from "os";
|
|
24289
|
+
import { appSecretScope } from "@tinycloud/sdk-core";
|
|
24164
24290
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
24291
|
+
import { isLocalOwnerProfile as isLocalOwnerProfile4 } from "@tinycloud/operations/profile";
|
|
24292
|
+
import {
|
|
24293
|
+
buildSecretManagerUrl,
|
|
24294
|
+
buildSecretRequestUrl as buildSecretRequestUrl2,
|
|
24295
|
+
canonicalSecretRequestOwner,
|
|
24296
|
+
isValidSecretRequestScope,
|
|
24297
|
+
missingSecretRecovery,
|
|
24298
|
+
ownerOfSpace,
|
|
24299
|
+
resolveSecretsOwner as resolveSecretsOwner2,
|
|
24300
|
+
SecretRequestInputError,
|
|
24301
|
+
validSecretNames
|
|
24302
|
+
} from "@tinycloud/operations/secret-request";
|
|
24165
24303
|
import {
|
|
24166
24304
|
SECRET_DECRYPT_CAPABILITY,
|
|
24167
24305
|
secretCapabilityAction
|
|
@@ -24170,6 +24308,402 @@ import { invokeSecretsGetWithLocalAuthorityRetry } from "@tinycloud/operations/c
|
|
|
24170
24308
|
init_sdk();
|
|
24171
24309
|
init_session_expired();
|
|
24172
24310
|
init_space();
|
|
24311
|
+
|
|
24312
|
+
// src/lib/browser.ts
|
|
24313
|
+
init_formatter();
|
|
24314
|
+
init_theme();
|
|
24315
|
+
import open5 from "open";
|
|
24316
|
+
var LAUNCH_CHECK_MS = 1e3;
|
|
24317
|
+
async function tryOpenBrowser(url) {
|
|
24318
|
+
let launcher;
|
|
24319
|
+
try {
|
|
24320
|
+
launcher = await open5(url);
|
|
24321
|
+
} catch {
|
|
24322
|
+
return false;
|
|
24323
|
+
}
|
|
24324
|
+
return new Promise((resolve5) => {
|
|
24325
|
+
const timer = setTimeout(() => resolve5(true), LAUNCH_CHECK_MS);
|
|
24326
|
+
launcher.once("error", () => {
|
|
24327
|
+
clearTimeout(timer);
|
|
24328
|
+
resolve5(false);
|
|
24329
|
+
});
|
|
24330
|
+
launcher.once("exit", (code3) => {
|
|
24331
|
+
clearTimeout(timer);
|
|
24332
|
+
resolve5(code3 === 0);
|
|
24333
|
+
});
|
|
24334
|
+
});
|
|
24335
|
+
}
|
|
24336
|
+
function warnBrowserNotOpened() {
|
|
24337
|
+
process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings: [{ code: "BROWSER_OPEN_FAILED", reason: "open_failed" }] })}
|
|
24338
|
+
` : `${theme.warn("!")} Could not open a browser. Open the URL above yourself.
|
|
24339
|
+
`);
|
|
24340
|
+
}
|
|
24341
|
+
|
|
24342
|
+
// src/lib/probe-child.ts
|
|
24343
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
24344
|
+
var ATTEMPT_BUDGET_MS = 2e4;
|
|
24345
|
+
var KILL_GRACE_MS = 500;
|
|
24346
|
+
var MAX_PROBE_LINE_BYTES = 64 * 1024;
|
|
24347
|
+
var MAX_STDERR_BYTES = 64 * 1024;
|
|
24348
|
+
var STATUS_OPERATION_ID = "tinycloud.secrets.status";
|
|
24349
|
+
var ERROR_CODE_RE = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
24350
|
+
function probeCommandArgs(request) {
|
|
24351
|
+
return [
|
|
24352
|
+
"--json",
|
|
24353
|
+
"--profile",
|
|
24354
|
+
request.profile,
|
|
24355
|
+
"--host",
|
|
24356
|
+
request.host,
|
|
24357
|
+
"secrets",
|
|
24358
|
+
"probe",
|
|
24359
|
+
...request.names,
|
|
24360
|
+
...request.scope === void 0 ? [] : ["--scope", request.scope],
|
|
24361
|
+
...request.owner === void 0 ? [] : ["--owner", request.owner]
|
|
24362
|
+
];
|
|
24363
|
+
}
|
|
24364
|
+
function probeChildArgv(request) {
|
|
24365
|
+
const script = process.argv[1];
|
|
24366
|
+
if (script === void 0) throw new Error("Cannot locate the tc entry point for the probe child.");
|
|
24367
|
+
return [...process.execArgv, script, ...probeCommandArgs(request)];
|
|
24368
|
+
}
|
|
24369
|
+
function runProbeChild(request, options = {}) {
|
|
24370
|
+
const command = options.command ?? process.execPath;
|
|
24371
|
+
const args = options.argv ?? probeChildArgv(request);
|
|
24372
|
+
const budgetMs = options.budgetMs ?? ATTEMPT_BUDGET_MS;
|
|
24373
|
+
const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
|
|
24374
|
+
const spawn2 = options.spawn ?? nodeSpawn;
|
|
24375
|
+
const signal = options.signal;
|
|
24376
|
+
return new Promise((resolve5) => {
|
|
24377
|
+
if (signal?.aborted) {
|
|
24378
|
+
resolve5({ kind: "cancelled" });
|
|
24379
|
+
return;
|
|
24380
|
+
}
|
|
24381
|
+
const child = spawn2(command, args, { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
24382
|
+
let outcome;
|
|
24383
|
+
let stdout = Buffer.alloc(0);
|
|
24384
|
+
let stderr = Buffer.alloc(0);
|
|
24385
|
+
let exited = false;
|
|
24386
|
+
let stopping = false;
|
|
24387
|
+
let graceTimer;
|
|
24388
|
+
const stop = () => {
|
|
24389
|
+
if (exited || stopping) return;
|
|
24390
|
+
stopping = true;
|
|
24391
|
+
child.kill("SIGTERM");
|
|
24392
|
+
graceTimer = setTimeout(() => {
|
|
24393
|
+
if (!exited) child.kill("SIGKILL");
|
|
24394
|
+
}, killGraceMs);
|
|
24395
|
+
};
|
|
24396
|
+
const settle = (value) => {
|
|
24397
|
+
if (outcome !== void 0) return;
|
|
24398
|
+
outcome = value;
|
|
24399
|
+
stop();
|
|
24400
|
+
};
|
|
24401
|
+
const onAbort = () => settle({ kind: "cancelled" });
|
|
24402
|
+
const budgetTimer = setTimeout(() => settle({ kind: "timeout" }), budgetMs);
|
|
24403
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
24404
|
+
const finish = () => {
|
|
24405
|
+
if (exited) return;
|
|
24406
|
+
exited = true;
|
|
24407
|
+
clearTimeout(budgetTimer);
|
|
24408
|
+
clearTimeout(graceTimer);
|
|
24409
|
+
signal?.removeEventListener("abort", onAbort);
|
|
24410
|
+
if (outcome === void 0) {
|
|
24411
|
+
const envelope = stdout.length > 0 && stdout.length <= MAX_PROBE_LINE_BYTES ? parseStatusEnvelope(stdout.toString("utf8")) : void 0;
|
|
24412
|
+
outcome = envelope !== void 0 ? { kind: "ok", envelope } : probeFailed(stderr.toString("utf8"));
|
|
24413
|
+
}
|
|
24414
|
+
resolve5(outcome);
|
|
24415
|
+
};
|
|
24416
|
+
if (child.pid !== void 0) options.onSpawn?.(child.pid);
|
|
24417
|
+
child.stdout?.on("data", (chunk) => {
|
|
24418
|
+
if (outcome !== void 0) return;
|
|
24419
|
+
stdout = Buffer.concat([stdout, chunk]);
|
|
24420
|
+
const newline = stdout.indexOf(10);
|
|
24421
|
+
if (newline >= 0 && newline <= MAX_PROBE_LINE_BYTES) {
|
|
24422
|
+
const envelope = parseStatusEnvelope(stdout.subarray(0, newline).toString("utf8"));
|
|
24423
|
+
settle(envelope !== void 0 ? { kind: "ok", envelope } : { kind: "probe_failed" });
|
|
24424
|
+
} else if (newline > MAX_PROBE_LINE_BYTES || newline < 0 && stdout.length > MAX_PROBE_LINE_BYTES) {
|
|
24425
|
+
settle({ kind: "probe_failed" });
|
|
24426
|
+
}
|
|
24427
|
+
});
|
|
24428
|
+
child.stderr?.on("data", (chunk) => {
|
|
24429
|
+
stderr = Buffer.concat([stderr, chunk]);
|
|
24430
|
+
if (stderr.length > MAX_STDERR_BYTES) stderr = stderr.subarray(stderr.length - MAX_STDERR_BYTES);
|
|
24431
|
+
});
|
|
24432
|
+
child.on("error", () => {
|
|
24433
|
+
settle({ kind: "probe_failed" });
|
|
24434
|
+
if (child.pid === void 0) finish();
|
|
24435
|
+
});
|
|
24436
|
+
child.on("close", finish);
|
|
24437
|
+
});
|
|
24438
|
+
}
|
|
24439
|
+
function probeFailed(stderr) {
|
|
24440
|
+
const code3 = childErrorCode(stderr);
|
|
24441
|
+
return code3 === void 0 ? { kind: "probe_failed" } : { kind: "probe_failed", childErrorCode: code3 };
|
|
24442
|
+
}
|
|
24443
|
+
function childErrorCode(stderr) {
|
|
24444
|
+
const lines = stderr.split("\n");
|
|
24445
|
+
for (let start = lines.length - 1; start >= 0; start -= 1) {
|
|
24446
|
+
if (!lines[start].startsWith("{")) continue;
|
|
24447
|
+
try {
|
|
24448
|
+
const parsed = JSON.parse(lines.slice(start).join("\n"));
|
|
24449
|
+
if (typeof parsed !== "object" || parsed === null || !("error" in parsed)) continue;
|
|
24450
|
+
const error = parsed.error;
|
|
24451
|
+
if (typeof error !== "object" || error === null || !("code" in error)) continue;
|
|
24452
|
+
return typeof error.code === "string" && ERROR_CODE_RE.test(error.code) ? error.code : void 0;
|
|
24453
|
+
} catch {
|
|
24454
|
+
}
|
|
24455
|
+
}
|
|
24456
|
+
return void 0;
|
|
24457
|
+
}
|
|
24458
|
+
function plainObject(value) {
|
|
24459
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24460
|
+
}
|
|
24461
|
+
function parseStatusEnvelope(line) {
|
|
24462
|
+
let parsed;
|
|
24463
|
+
try {
|
|
24464
|
+
parsed = JSON.parse(line);
|
|
24465
|
+
} catch {
|
|
24466
|
+
return void 0;
|
|
24467
|
+
}
|
|
24468
|
+
if (!plainObject(parsed)) return void 0;
|
|
24469
|
+
const value = parsed;
|
|
24470
|
+
if (!plainObject(value.operation) || value.operation?.operationId !== STATUS_OPERATION_ID) return void 0;
|
|
24471
|
+
const warnings = Array.isArray(value.warnings) ? { warnings: value.warnings } : {};
|
|
24472
|
+
const operation = { operationId: STATUS_OPERATION_ID };
|
|
24473
|
+
if (value.status === "ok" && plainObject(value.output)) {
|
|
24474
|
+
return { status: "ok", operation, output: value.output, ...warnings };
|
|
24475
|
+
}
|
|
24476
|
+
const error = value.error;
|
|
24477
|
+
if (value.status === "error" && plainObject(error) && typeof error?.code === "string" && typeof error.message === "string") {
|
|
24478
|
+
return {
|
|
24479
|
+
status: "error",
|
|
24480
|
+
operation,
|
|
24481
|
+
error: {
|
|
24482
|
+
code: error.code,
|
|
24483
|
+
message: error.message,
|
|
24484
|
+
...typeof error.retryable === "boolean" ? { retryable: error.retryable } : {},
|
|
24485
|
+
...plainObject(error.details) ? { details: error.details } : {}
|
|
24486
|
+
},
|
|
24487
|
+
...warnings
|
|
24488
|
+
};
|
|
24489
|
+
}
|
|
24490
|
+
return void 0;
|
|
24491
|
+
}
|
|
24492
|
+
|
|
24493
|
+
// src/lib/secrets-wait.ts
|
|
24494
|
+
init_constants();
|
|
24495
|
+
init_errors();
|
|
24496
|
+
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
24497
|
+
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
24498
|
+
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
24499
|
+
var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
24500
|
+
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
24501
|
+
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
24502
|
+
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
24503
|
+
var EXPLICIT_KEY_HINT = "tc secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with tc --profile <profile> auth login --method local, without TC_PRIVATE_KEY.";
|
|
24504
|
+
function abortableSleep(ms2, signal) {
|
|
24505
|
+
return new Promise((resolve5) => {
|
|
24506
|
+
if (signal.aborted) {
|
|
24507
|
+
resolve5();
|
|
24508
|
+
return;
|
|
24509
|
+
}
|
|
24510
|
+
const done = () => {
|
|
24511
|
+
clearTimeout(timer);
|
|
24512
|
+
signal.removeEventListener("abort", done);
|
|
24513
|
+
resolve5();
|
|
24514
|
+
};
|
|
24515
|
+
const timer = setTimeout(done, ms2);
|
|
24516
|
+
signal.addEventListener("abort", done, { once: true });
|
|
24517
|
+
});
|
|
24518
|
+
}
|
|
24519
|
+
function parseWaitTimeout(value) {
|
|
24520
|
+
if (!TIMEOUT_RE.test(value)) {
|
|
24521
|
+
throw new CLIError("INVALID_TIMEOUT", `Invalid --timeout ${JSON.stringify(value)}: use 0, Ns or Nm (for example 90s or 5m).`, ExitCode.USAGE_ERROR);
|
|
24522
|
+
}
|
|
24523
|
+
const amount = value === "0" ? 0 : Number(value.slice(0, -1));
|
|
24524
|
+
const ms2 = value.endsWith("m") ? amount * 6e4 : amount * 1e3;
|
|
24525
|
+
if (ms2 > MAX_WAIT_TIMEOUT_MS) {
|
|
24526
|
+
throw new CLIError("INVALID_TIMEOUT", "--timeout may be at most 9m.", ExitCode.USAGE_ERROR);
|
|
24527
|
+
}
|
|
24528
|
+
return ms2;
|
|
24529
|
+
}
|
|
24530
|
+
async function runSecretsWait(request, deps) {
|
|
24531
|
+
const deadline = deps.now() + request.timeoutMs;
|
|
24532
|
+
let progress;
|
|
24533
|
+
let lastPending;
|
|
24534
|
+
let lastWarnings;
|
|
24535
|
+
let failures = 0;
|
|
24536
|
+
let step = 0;
|
|
24537
|
+
const cancelled = () => new CLIError(
|
|
24538
|
+
"CANCELLED",
|
|
24539
|
+
"Interrupted: tc secrets wait stopped before the secrets were ready.",
|
|
24540
|
+
ExitCode.CANCELLED,
|
|
24541
|
+
progress === void 0 ? {} : { progress }
|
|
24542
|
+
);
|
|
24543
|
+
const networkError = (message) => new CLIError(
|
|
24544
|
+
"NETWORK_ERROR",
|
|
24545
|
+
message,
|
|
24546
|
+
ExitCode.NETWORK_ERROR,
|
|
24547
|
+
{
|
|
24548
|
+
...progress === void 0 ? {} : { progress },
|
|
24549
|
+
...lastWarnings === void 0 ? {} : { warnings: lastWarnings }
|
|
24550
|
+
}
|
|
24551
|
+
);
|
|
24552
|
+
for (; ; ) {
|
|
24553
|
+
if (deps.signal.aborted) throw cancelled();
|
|
24554
|
+
const final = deps.now() >= deadline;
|
|
24555
|
+
const outcome = await deps.runProbe(deps.signal);
|
|
24556
|
+
if (deps.signal.aborted || outcome.kind === "cancelled") throw cancelled();
|
|
24557
|
+
let failed = false;
|
|
24558
|
+
switch (outcome.kind) {
|
|
24559
|
+
case "timeout":
|
|
24560
|
+
failed = true;
|
|
24561
|
+
break;
|
|
24562
|
+
case "probe_failed":
|
|
24563
|
+
throw new CLIError(
|
|
24564
|
+
"PROBE_FAILED",
|
|
24565
|
+
outcome.childErrorCode === void 0 ? "The status probe exited without a result." : `The status probe exited without a result (${outcome.childErrorCode}).`,
|
|
24566
|
+
ExitCode.ERROR
|
|
24567
|
+
);
|
|
24568
|
+
case "ok": {
|
|
24569
|
+
const envelope = outcome.envelope;
|
|
24570
|
+
lastWarnings = envelope.warnings;
|
|
24571
|
+
if (envelope.status === "error") {
|
|
24572
|
+
if (RETRYABLE_STATUS_ERRORS[envelope.error.code] === true && envelope.error.retryable !== false) {
|
|
24573
|
+
failed = true;
|
|
24574
|
+
break;
|
|
24575
|
+
}
|
|
24576
|
+
throw statusError(envelope.error, request.profile, envelope.warnings);
|
|
24577
|
+
}
|
|
24578
|
+
const output2 = statusOutput(envelope.output);
|
|
24579
|
+
if (output2 === void 0) throw new CLIError("PROBE_FAILED", "The status probe returned an unexpected result.", ExitCode.ERROR);
|
|
24580
|
+
if (output2.status === "ready") {
|
|
24581
|
+
return {
|
|
24582
|
+
exitCode: ExitCode.SUCCESS,
|
|
24583
|
+
stdout: { status: "ready", names: [...output2.names], ...output2.scope === void 0 ? {} : { scope: output2.scope } },
|
|
24584
|
+
...envelope.warnings === void 0 ? {} : { warnings: envelope.warnings }
|
|
24585
|
+
};
|
|
24586
|
+
}
|
|
24587
|
+
if (output2.status === "consent_required") throw consentError(output2, request.consentHint, envelope.warnings);
|
|
24588
|
+
progress = { present: [...output2.present], pending: [...output2.pending] };
|
|
24589
|
+
lastPending = output2;
|
|
24590
|
+
break;
|
|
24591
|
+
}
|
|
24592
|
+
}
|
|
24593
|
+
if (failed) {
|
|
24594
|
+
failures += 1;
|
|
24595
|
+
if (failures >= MAX_CONSECUTIVE_FAILURES) {
|
|
24596
|
+
throw networkError(`The TinyCloud node could not be reached (${failures} attempts in a row).`);
|
|
24597
|
+
}
|
|
24598
|
+
} else {
|
|
24599
|
+
failures = 0;
|
|
24600
|
+
}
|
|
24601
|
+
const end = deps.now();
|
|
24602
|
+
if (final || end >= deadline) {
|
|
24603
|
+
if (failed || lastPending === void 0) {
|
|
24604
|
+
throw networkError("The TinyCloud node could not be reached before the wait's deadline.");
|
|
24605
|
+
}
|
|
24606
|
+
return {
|
|
24607
|
+
exitCode: ExitCode.PENDING,
|
|
24608
|
+
stdout: {
|
|
24609
|
+
status: "pending",
|
|
24610
|
+
pending: [...lastPending.pending],
|
|
24611
|
+
present: [...lastPending.present],
|
|
24612
|
+
...lastPending.setup !== void 0 ? { url: lastPending.setup.url } : lastPending.hint !== void 0 ? { hint: lastPending.hint } : {}
|
|
24613
|
+
},
|
|
24614
|
+
...lastWarnings === void 0 ? {} : { warnings: lastWarnings }
|
|
24615
|
+
};
|
|
24616
|
+
}
|
|
24617
|
+
const interval = WAIT_BACKOFF_MS[step] ?? WAIT_STEADY_INTERVAL_MS;
|
|
24618
|
+
step += 1;
|
|
24619
|
+
await deps.sleep(Math.min(interval, deadline - end), deps.signal);
|
|
24620
|
+
}
|
|
24621
|
+
}
|
|
24622
|
+
function stringList(value) {
|
|
24623
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
24624
|
+
}
|
|
24625
|
+
function statusOutput(output2) {
|
|
24626
|
+
const { status, owner, scope, setup, hint } = output2;
|
|
24627
|
+
const names = stringList(output2.names);
|
|
24628
|
+
const present = stringList(output2.present);
|
|
24629
|
+
const pending = stringList(output2.pending);
|
|
24630
|
+
const denied = stringList(output2.denied);
|
|
24631
|
+
if (status !== "ready" && status !== "pending" && status !== "consent_required") return void 0;
|
|
24632
|
+
if (typeof owner !== "string" || !names || !present || !pending || !denied) return void 0;
|
|
24633
|
+
if (scope !== void 0 && typeof scope !== "string") return void 0;
|
|
24634
|
+
if (hint !== void 0 && typeof hint !== "string") return void 0;
|
|
24635
|
+
if (setup !== void 0 && (typeof setup !== "object" || setup === null || !("url" in setup) || typeof setup.url !== "string")) return void 0;
|
|
24636
|
+
return {
|
|
24637
|
+
status,
|
|
24638
|
+
owner,
|
|
24639
|
+
names,
|
|
24640
|
+
present,
|
|
24641
|
+
pending,
|
|
24642
|
+
denied,
|
|
24643
|
+
...scope === void 0 ? {} : { scope },
|
|
24644
|
+
...setup === void 0 ? {} : { setup },
|
|
24645
|
+
...hint === void 0 ? {} : { hint }
|
|
24646
|
+
};
|
|
24647
|
+
}
|
|
24648
|
+
function consentError(output2, hint, warnings) {
|
|
24649
|
+
const denied = [...output2.denied];
|
|
24650
|
+
return new CLIError(
|
|
24651
|
+
"PERMISSION_DENIED",
|
|
24652
|
+
`This profile may not read ${denied.join(", ")}. The owner must approve access first.`,
|
|
24653
|
+
ExitCode.PERMISSION_DENIED,
|
|
24654
|
+
{
|
|
24655
|
+
hint,
|
|
24656
|
+
recovery: {
|
|
24657
|
+
action: "request_access",
|
|
24658
|
+
names: denied,
|
|
24659
|
+
owner: output2.owner,
|
|
24660
|
+
...output2.scope === void 0 ? {} : { scope: output2.scope }
|
|
24661
|
+
},
|
|
24662
|
+
deniedNames: denied,
|
|
24663
|
+
...output2.setup === void 0 ? {} : { setup: output2.setup },
|
|
24664
|
+
...warnings === void 0 ? {} : { warnings }
|
|
24665
|
+
}
|
|
24666
|
+
);
|
|
24667
|
+
}
|
|
24668
|
+
function recoveryOf(error, action) {
|
|
24669
|
+
const recovery = error.details?.recovery;
|
|
24670
|
+
if (typeof recovery !== "object" || recovery === null) return void 0;
|
|
24671
|
+
const candidate = recovery;
|
|
24672
|
+
if (candidate.action !== action) return void 0;
|
|
24673
|
+
return candidate.command === void 0 || typeof candidate.command === "string" ? recovery : void 0;
|
|
24674
|
+
}
|
|
24675
|
+
function statusError(error, profile, warnings) {
|
|
24676
|
+
const extra = warnings === void 0 ? {} : { warnings };
|
|
24677
|
+
if (error.code === "AUTH_REQUIRED" && error.details?.reason === "explicit_key") {
|
|
24678
|
+
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint: EXPLICIT_KEY_HINT, ...extra });
|
|
24679
|
+
}
|
|
24680
|
+
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
24681
|
+
const recovery = recoveryOf(error, "sign_in");
|
|
24682
|
+
if (recovery !== void 0) {
|
|
24683
|
+
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with tc auth login, then retry.`;
|
|
24684
|
+
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
24685
|
+
}
|
|
24686
|
+
}
|
|
24687
|
+
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
24688
|
+
const recovery = recoveryOf(error, "complete_migration");
|
|
24689
|
+
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run tc auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
|
|
24690
|
+
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
24691
|
+
hint,
|
|
24692
|
+
...recovery === void 0 ? {} : { recovery },
|
|
24693
|
+
...extra
|
|
24694
|
+
});
|
|
24695
|
+
}
|
|
24696
|
+
if (error.code === "INPUT_INVALID") {
|
|
24697
|
+
const reason = error.details?.reason;
|
|
24698
|
+
if (reason === "OWNER_MISMATCH") return new CLIError("OWNER_MISMATCH", error.message, ExitCode.USAGE_ERROR, extra);
|
|
24699
|
+
if (reason === "OWNER_DID_UNKNOWN") return new CLIError("OWNER_DID_UNKNOWN", error.message, ExitCode.AUTH_REQUIRED, extra);
|
|
24700
|
+
return new CLIError("INPUT_INVALID", error.message, ExitCode.USAGE_ERROR, extra);
|
|
24701
|
+
}
|
|
24702
|
+
const mapped = cliErrorFromService(error);
|
|
24703
|
+
return new CLIError(mapped.code, mapped.message, mapped.exitCode, { ...mapped.metadata, ...extra });
|
|
24704
|
+
}
|
|
24705
|
+
|
|
24706
|
+
// src/commands/secrets.ts
|
|
24173
24707
|
init_types();
|
|
24174
24708
|
var SECRETS_SPACE3 = "secrets";
|
|
24175
24709
|
var SECRET_KV_ABILITIES = {
|
|
@@ -24189,6 +24723,32 @@ function authOptions(options) {
|
|
|
24189
24723
|
const privateKey = options.privateKey || process.env.TC_PRIVATE_KEY;
|
|
24190
24724
|
return privateKey ? { privateKey } : void 0;
|
|
24191
24725
|
}
|
|
24726
|
+
function effectiveLocal(profile, options) {
|
|
24727
|
+
return profile !== null && isLocalOwnerProfile4(profile) || authOptions(options) !== void 0;
|
|
24728
|
+
}
|
|
24729
|
+
function secretRequestUsageError(error) {
|
|
24730
|
+
return error instanceof SecretRequestInputError ? new CLIError(error.code, error.message, ExitCode.USAGE_ERROR) : error;
|
|
24731
|
+
}
|
|
24732
|
+
function requestedSecretScope(options) {
|
|
24733
|
+
if (options.scope !== void 0 && options.app !== void 0) {
|
|
24734
|
+
throw new CLIError("USAGE_ERROR", "Pass either --scope or --app, not both.", ExitCode.USAGE_ERROR);
|
|
24735
|
+
}
|
|
24736
|
+
if (options.app === void 0) return canonicalizeSecretScope2(options.scope);
|
|
24737
|
+
try {
|
|
24738
|
+
return appSecretScope(options.app);
|
|
24739
|
+
} catch (error) {
|
|
24740
|
+
throw new CLIError("INVALID_SECRET_SCOPE", error instanceof Error ? error.message : String(error), ExitCode.USAGE_ERROR);
|
|
24741
|
+
}
|
|
24742
|
+
}
|
|
24743
|
+
var CONTROL_OR_FORMAT = /[\p{Cc}\p{Cf}]/u;
|
|
24744
|
+
function defaultRequester() {
|
|
24745
|
+
try {
|
|
24746
|
+
const requester = `${userInfo().username}@${hostname().split(".")[0]}`;
|
|
24747
|
+
return requester.length <= 64 && !CONTROL_OR_FORMAT.test(requester) ? requester : void 0;
|
|
24748
|
+
} catch {
|
|
24749
|
+
return void 0;
|
|
24750
|
+
}
|
|
24751
|
+
}
|
|
24192
24752
|
function resolveSecretScope(options) {
|
|
24193
24753
|
return options.scope ? { scope: options.scope } : void 0;
|
|
24194
24754
|
}
|
|
@@ -24447,14 +25007,20 @@ function throwCanonicalSecretGetError(result, name) {
|
|
|
24447
25007
|
"Permission denied while reading secret",
|
|
24448
25008
|
ExitCode.PERMISSION_DENIED
|
|
24449
25009
|
);
|
|
24450
|
-
case "setup_required":
|
|
25010
|
+
case "setup_required": {
|
|
25011
|
+
const { url, names, scope, owner, action } = result.setup;
|
|
25012
|
+
const message = typeof result.setup.message === "string" ? result.setup.message : `Secret "${name}" is not set.`;
|
|
24451
25013
|
throw new CLIError(
|
|
24452
25014
|
"NOT_FOUND",
|
|
24453
|
-
|
|
25015
|
+
message,
|
|
24454
25016
|
ExitCode.NOT_FOUND,
|
|
24455
|
-
{
|
|
24456
|
-
|
|
25017
|
+
{
|
|
25018
|
+
hint: `${String(url)}
|
|
25019
|
+
${message}`,
|
|
25020
|
+
setup: { url, names, ...scope === void 0 ? {} : { scope }, owner, action }
|
|
25021
|
+
}
|
|
24457
25022
|
);
|
|
25023
|
+
}
|
|
24458
25024
|
case "error":
|
|
24459
25025
|
if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
|
|
24460
25026
|
throw new CLIError(
|
|
@@ -24467,6 +25033,9 @@ ${result.setup.message}`, setup: result.setup }
|
|
|
24467
25033
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
24468
25034
|
throw new CLIError("NETWORK_ERROR", result.error.message, ExitCode.NETWORK_ERROR);
|
|
24469
25035
|
}
|
|
25036
|
+
if (result.error.code === "KV_NOT_FOUND") {
|
|
25037
|
+
throw new CLIError("NOT_FOUND", `Secret "${name}" not found.`, ExitCode.NOT_FOUND, { hint: result.error.message });
|
|
25038
|
+
}
|
|
24470
25039
|
if (result.error.code === "PERMISSION_HINT_INVALID") {
|
|
24471
25040
|
throw new CLIError(result.error.code, result.error.message, ExitCode.PERMISSION_DENIED);
|
|
24472
25041
|
}
|
|
@@ -24738,17 +25307,26 @@ async function readDelegatedSecretValue(params) {
|
|
|
24738
25307
|
});
|
|
24739
25308
|
if (!envelopeResult.ok) {
|
|
24740
25309
|
if (envelopeResult.error.code === "NOT_FOUND" || envelopeResult.error.code === "KEY_NOT_FOUND" || envelopeResult.error.code === "KV_NOT_FOUND") {
|
|
25310
|
+
const recovery = missingSecretRecovery({
|
|
25311
|
+
owner: ownerOfSpace(params.space?.startsWith("tinycloud:") ? params.space : params.delegation.spaceId),
|
|
25312
|
+
local: params.local,
|
|
25313
|
+
names: [params.name],
|
|
25314
|
+
...params.scope === void 0 ? {} : { scope: params.scope }
|
|
25315
|
+
});
|
|
24741
25316
|
throw new CLIError(
|
|
24742
25317
|
"NOT_FOUND",
|
|
24743
25318
|
`Secret "${params.name}" not found`,
|
|
24744
|
-
ExitCode.NOT_FOUND
|
|
25319
|
+
ExitCode.NOT_FOUND,
|
|
25320
|
+
recovery.kind === "add_secret" ? { hint: `Ask the owner to add it: ${recovery.setup.url}`, setup: recovery.setup } : { hint: recovery.hint }
|
|
24745
25321
|
);
|
|
24746
25322
|
}
|
|
24747
|
-
|
|
25323
|
+
const classified = cliErrorFromService(envelopeResult.error);
|
|
25324
|
+
if (envelopeResult.error.code === "AUTH_UNAUTHORIZED" || classified.code === "PERMISSION_DENIED" || classified.code === "AUTH_REQUIRED") {
|
|
24748
25325
|
throw new CLIError(
|
|
24749
25326
|
"PERMISSION_DENIED",
|
|
24750
25327
|
`Delegation "${params.delegationCid}" does not cover secret "${params.secretPath}".`,
|
|
24751
|
-
ExitCode.PERMISSION_DENIED
|
|
25328
|
+
ExitCode.PERMISSION_DENIED,
|
|
25329
|
+
classified.metadata
|
|
24752
25330
|
);
|
|
24753
25331
|
}
|
|
24754
25332
|
throw new CLIError(
|
|
@@ -24885,7 +25463,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
24885
25463
|
...descriptor ? { descriptor } : {}
|
|
24886
25464
|
});
|
|
24887
25465
|
} catch (error) {
|
|
24888
|
-
handleError(error);
|
|
25466
|
+
return handleError(error);
|
|
24889
25467
|
}
|
|
24890
25468
|
});
|
|
24891
25469
|
network.command("init [name]").description("Create a secrets encryption network if needed").option("--private-key <hex>", "Ethereum private key override (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
@@ -24903,7 +25481,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
24903
25481
|
descriptor
|
|
24904
25482
|
});
|
|
24905
25483
|
} catch (error) {
|
|
24906
|
-
handleError(error);
|
|
25484
|
+
return handleError(error);
|
|
24907
25485
|
}
|
|
24908
25486
|
});
|
|
24909
25487
|
secrets.command("doctor [name]").description("Check secrets setup and optional secret access").option("--scope <scope>", "Logical secret scope").option("--space <name|uri>", "Target a non-default secrets space (short name or full URI)").option("--network <name>", "Encryption network name", "default").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
@@ -24960,6 +25538,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
24960
25538
|
});
|
|
24961
25539
|
} else {
|
|
24962
25540
|
const notFound = result.error.code === "NOT_FOUND" || result.error.code === "KEY_NOT_FOUND";
|
|
25541
|
+
const local = notFound && effectiveLocal(await ProfileManager.getProfile(ctx.profile).catch(() => null), options);
|
|
24963
25542
|
secret = {
|
|
24964
25543
|
name: resolved.name,
|
|
24965
25544
|
path: resolved.permissionPaths.vault,
|
|
@@ -24971,7 +25550,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
24971
25550
|
name: "Secret access",
|
|
24972
25551
|
ok: false,
|
|
24973
25552
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
24974
|
-
hint: notFound ? `tc secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)}
|
|
25553
|
+
hint: notFound ? local ? `tc secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `tc secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
|
|
24975
25554
|
});
|
|
24976
25555
|
}
|
|
24977
25556
|
} else {
|
|
@@ -24993,7 +25572,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
24993
25572
|
checks
|
|
24994
25573
|
});
|
|
24995
25574
|
} catch (error) {
|
|
24996
|
-
handleError(error);
|
|
25575
|
+
return handleError(error);
|
|
24997
25576
|
}
|
|
24998
25577
|
});
|
|
24999
25578
|
secrets.command("list").description("List secrets").option("--scope <scope>", "Logical secret scope").option("--space <name|uri>", "Target a non-default secrets space (short name or full URI)").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (options, cmd) => {
|
|
@@ -25024,7 +25603,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
25024
25603
|
...spaceUri ? { space: spaceUri } : {}
|
|
25025
25604
|
});
|
|
25026
25605
|
} catch (error) {
|
|
25027
|
-
handleError(error);
|
|
25606
|
+
return handleError(error);
|
|
25028
25607
|
}
|
|
25029
25608
|
});
|
|
25030
25609
|
secrets.command("get <name>").description("Get a secret value").option("--scope <scope>", "Logical secret scope").option("--space <name|uri>", "Target a non-default secrets space (short name or full URI)").option("--raw", "Output raw value (no JSON wrapping)").option("--value-only", "Output only the secret value (alias for --raw)").option("-o, --output <file>", "Write value to file").option("--delegation <source>", "Delegation file path or imported profile name").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
@@ -25043,6 +25622,8 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
25043
25622
|
);
|
|
25044
25623
|
const effectiveHost = globalOpts.host ?? delegated.delegation.host ?? ctx.host;
|
|
25045
25624
|
const delegatedCtx = { ...ctx, host: effectiveHost };
|
|
25625
|
+
const local = effectiveLocal(await ProfileManager.getProfile(ctx.profile).catch(() => null), options);
|
|
25626
|
+
const scope = resolveSecretPath2(name, scopeOptions).scope;
|
|
25046
25627
|
const node = await ensureSecretsNode(delegatedCtx, options);
|
|
25047
25628
|
const value2 = await withSpinner(
|
|
25048
25629
|
`Getting secret ${name}...`,
|
|
@@ -25053,7 +25634,9 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
25053
25634
|
permissions: delegated.permissions,
|
|
25054
25635
|
secretPath,
|
|
25055
25636
|
space: legacySpaceUri ?? SECRETS_SPACE3,
|
|
25056
|
-
name
|
|
25637
|
+
name,
|
|
25638
|
+
...scope === void 0 ? {} : { scope },
|
|
25639
|
+
local
|
|
25057
25640
|
})
|
|
25058
25641
|
);
|
|
25059
25642
|
if (options.output) {
|
|
@@ -25101,7 +25684,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
25101
25684
|
}
|
|
25102
25685
|
outputWarnings(operationWarnings(result.warnings));
|
|
25103
25686
|
} catch (error) {
|
|
25104
|
-
handleError(error);
|
|
25687
|
+
return handleError(error);
|
|
25105
25688
|
}
|
|
25106
25689
|
});
|
|
25107
25690
|
secrets.command("put <name> [value]").description("Store a secret").option("--scope <scope>", "Logical secret scope").option("--space <name|uri>", "Target a non-default secrets space (short name or full URI)").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, value, options, cmd) => {
|
|
@@ -25142,7 +25725,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
25142
25725
|
}
|
|
25143
25726
|
outputJson({ name, written: true });
|
|
25144
25727
|
} catch (error) {
|
|
25145
|
-
handleError(error);
|
|
25728
|
+
return handleError(error);
|
|
25146
25729
|
}
|
|
25147
25730
|
});
|
|
25148
25731
|
secrets.command("delete <name>").description("Delete a secret").option("--scope <scope>", "Logical secret scope").option("--space <name|uri>", "Target a non-default secrets space (short name or full URI)").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
@@ -25168,7 +25751,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
25168
25751
|
}
|
|
25169
25752
|
outputJson({ name, deleted: true });
|
|
25170
25753
|
} catch (error) {
|
|
25171
|
-
handleError(error);
|
|
25754
|
+
return handleError(error);
|
|
25172
25755
|
}
|
|
25173
25756
|
});
|
|
25174
25757
|
network.command("grant <recipientDid> [name]").description("Grant decrypt permission for a secrets encryption network").option("--private-key <hex>", "Ethereum private key override (or set TC_PRIVATE_KEY)").action(async (recipientDid, name, options, cmd) => {
|
|
@@ -25199,16 +25782,135 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
25199
25782
|
actions: result.delegation.actions
|
|
25200
25783
|
});
|
|
25201
25784
|
} catch (error) {
|
|
25202
|
-
handleError(error);
|
|
25785
|
+
return handleError(error);
|
|
25203
25786
|
}
|
|
25204
25787
|
});
|
|
25205
|
-
secrets.command("
|
|
25788
|
+
secrets.command("request <name...>").description("Print a Secret Manager link asking the owner to add secrets (offline)").option("--scope <scope>", "Logical secret scope").option("--app <id>", "Derive the scope from an app id (instead of --scope)").option("--owner <did>", "Owner did:pkh when the profile does not record one").option("--requester <label>", "Who is asking, shown to the owner (default: user@host)").option("--open", "Also open the link in a browser").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY); a local key cannot request").action(async (names, options, cmd) => {
|
|
25206
25789
|
try {
|
|
25207
|
-
const
|
|
25208
|
-
await
|
|
25209
|
-
|
|
25790
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
25791
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
25792
|
+
const scope = requestedSecretScope(options);
|
|
25793
|
+
const profile = await ProfileManager.getProfile(ctx.profile).catch(() => null);
|
|
25794
|
+
if (effectiveLocal(profile, options)) {
|
|
25795
|
+
throw new CLIError(
|
|
25796
|
+
"LOCAL_OWNER_PROFILE",
|
|
25797
|
+
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with tc secrets put NAME --stdin instead of requesting them.`,
|
|
25798
|
+
ExitCode.USAGE_ERROR
|
|
25799
|
+
);
|
|
25800
|
+
}
|
|
25801
|
+
let link3;
|
|
25802
|
+
try {
|
|
25803
|
+
const requestedOwner = options.owner === void 0 ? void 0 : canonicalSecretRequestOwner(options.owner);
|
|
25804
|
+
const resolution = resolveSecretsOwner2({
|
|
25805
|
+
local: false,
|
|
25806
|
+
posture: resolveProfilePosture(profile ?? {}),
|
|
25807
|
+
...profile?.ownerDid === void 0 ? {} : { ownerDid: profile.ownerDid.split("#")[0] },
|
|
25808
|
+
...profile?.did === void 0 ? {} : { principalDid: profile.did.split("#")[0] },
|
|
25809
|
+
...profile?.spaceId === void 0 ? {} : { space: profile.spaceId }
|
|
25810
|
+
}, requestedOwner);
|
|
25811
|
+
if (!resolution.ok) {
|
|
25812
|
+
throw new CLIError(
|
|
25813
|
+
resolution.code,
|
|
25814
|
+
resolution.message,
|
|
25815
|
+
resolution.code === "OWNER_MISMATCH" ? ExitCode.USAGE_ERROR : ExitCode.AUTH_REQUIRED
|
|
25816
|
+
);
|
|
25817
|
+
}
|
|
25818
|
+
const requester = options.requester ?? defaultRequester();
|
|
25819
|
+
link3 = buildSecretRequestUrl2({
|
|
25820
|
+
owner: resolution.owner,
|
|
25821
|
+
names,
|
|
25822
|
+
...scope === void 0 ? {} : { scope },
|
|
25823
|
+
...requester === void 0 ? {} : { requester }
|
|
25824
|
+
});
|
|
25825
|
+
} catch (error) {
|
|
25826
|
+
throw secretRequestUsageError(error);
|
|
25827
|
+
}
|
|
25828
|
+
outputJson({ url: link3.url, names: link3.names, ...link3.scope === void 0 ? {} : { scope: link3.scope }, owner: link3.owner });
|
|
25829
|
+
if (options.open && !await tryOpenBrowser(link3.url)) warnBrowserNotOpened();
|
|
25210
25830
|
} catch (error) {
|
|
25211
|
-
handleError(error);
|
|
25831
|
+
return handleError(error);
|
|
25832
|
+
}
|
|
25833
|
+
});
|
|
25834
|
+
secrets.command("manage [name...]").description("Open the TinyCloud Secrets Manager, optionally at the named secrets").option("--scope <scope>", "Logical secret scope").option("--app <id>", "Derive the scope from an app id (instead of --scope)").option("--no-open", "Print the URL without opening a browser").action(async (names, options) => {
|
|
25835
|
+
try {
|
|
25836
|
+
const scope = requestedSecretScope(options);
|
|
25837
|
+
let url;
|
|
25838
|
+
try {
|
|
25839
|
+
url = buildSecretManagerUrl({ names, ...scope === void 0 ? {} : { scope } });
|
|
25840
|
+
} catch (error) {
|
|
25841
|
+
throw secretRequestUsageError(error);
|
|
25842
|
+
}
|
|
25843
|
+
const opened = options.open === false ? false : await tryOpenBrowser(url);
|
|
25844
|
+
outputJson({ url, opened });
|
|
25845
|
+
if (options.open !== false && !opened) warnBrowserNotOpened();
|
|
25846
|
+
} catch (error) {
|
|
25847
|
+
return handleError(error);
|
|
25848
|
+
}
|
|
25849
|
+
});
|
|
25850
|
+
secrets.command("wait <name...>").description("Wait until named secrets exist. Never signs in: needs a stored session, not TC_PRIVATE_KEY").option("--scope <scope>", "Logical secret scope").option("--app <id>", "Derive the scope from an app id (instead of --scope)").option("--owner <did>", "Expected owner did:pkh, checked against the profile's live identity").option("--timeout <duration>", "0 (one attempt), Ns or Nm; at most 9m", DEFAULT_WAIT_TIMEOUT).action(async (names, options, cmd) => {
|
|
25851
|
+
const controller = new AbortController();
|
|
25852
|
+
const cancel = () => controller.abort();
|
|
25853
|
+
process.once("SIGINT", cancel);
|
|
25854
|
+
process.once("SIGTERM", cancel);
|
|
25855
|
+
try {
|
|
25856
|
+
const timeoutMs = parseWaitTimeout(options.timeout);
|
|
25857
|
+
const scope = requestedSecretScope(options);
|
|
25858
|
+
let validNames;
|
|
25859
|
+
let owner;
|
|
25860
|
+
try {
|
|
25861
|
+
validNames = validSecretNames(names);
|
|
25862
|
+
if (scope !== void 0 && !isValidSecretRequestScope(scope)) {
|
|
25863
|
+
throw new CLIError("INVALID_SECRET_SCOPE", `Invalid secret scope ${JSON.stringify(scope)}.`, ExitCode.USAGE_ERROR);
|
|
25864
|
+
}
|
|
25865
|
+
owner = options.owner === void 0 ? void 0 : canonicalSecretRequestOwner(options.owner);
|
|
25866
|
+
} catch (error) {
|
|
25867
|
+
throw secretRequestUsageError(error);
|
|
25868
|
+
}
|
|
25869
|
+
const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
|
|
25870
|
+
const probe = {
|
|
25871
|
+
profile: ctx.profile,
|
|
25872
|
+
host: ctx.host,
|
|
25873
|
+
names: validNames,
|
|
25874
|
+
...scope === void 0 ? {} : { scope },
|
|
25875
|
+
...owner === void 0 ? {} : { owner }
|
|
25876
|
+
};
|
|
25877
|
+
const result = await runSecretsWait(
|
|
25878
|
+
{ profile: ctx.profile, timeoutMs, consentHint: scopedSecretLoginHint(ctx.profile) },
|
|
25879
|
+
{
|
|
25880
|
+
now: Date.now,
|
|
25881
|
+
sleep: abortableSleep,
|
|
25882
|
+
runProbe: (signal) => runProbeChild(probe, { signal }),
|
|
25883
|
+
signal: controller.signal
|
|
25884
|
+
}
|
|
25885
|
+
);
|
|
25886
|
+
outputJson(result.stdout);
|
|
25887
|
+
outputWarnings(operationWarnings(result.warnings));
|
|
25888
|
+
process.exitCode = result.exitCode;
|
|
25889
|
+
} catch (error) {
|
|
25890
|
+
return handleError(error);
|
|
25891
|
+
} finally {
|
|
25892
|
+
process.off("SIGINT", cancel);
|
|
25893
|
+
process.off("SIGTERM", cancel);
|
|
25894
|
+
}
|
|
25895
|
+
});
|
|
25896
|
+
secrets.command("probe <name...>", { hidden: true }).option("--scope <scope>", "Logical secret scope").option("--owner <did>", "Requested owner did:pkh").action(async (names, options, cmd) => {
|
|
25897
|
+
try {
|
|
25898
|
+
const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
|
|
25899
|
+
const privateKey = process.env.TC_PRIVATE_KEY;
|
|
25900
|
+
const result = await invokeOperation2("tinycloud.secrets.status", 1, {
|
|
25901
|
+
profile: ctx.profile,
|
|
25902
|
+
host: ctx.host,
|
|
25903
|
+
allowOwnerProfile: true,
|
|
25904
|
+
...privateKey ? { privateKey } : {}
|
|
25905
|
+
}, {
|
|
25906
|
+
names,
|
|
25907
|
+
...options.scope === void 0 ? {} : { scope: options.scope },
|
|
25908
|
+
...options.owner === void 0 ? {} : { owner: options.owner }
|
|
25909
|
+
});
|
|
25910
|
+
process.stdout.write(`${JSON.stringify(result)}
|
|
25911
|
+
`, () => process.exit(0));
|
|
25912
|
+
} catch (error) {
|
|
25913
|
+
return handleError(error);
|
|
25212
25914
|
}
|
|
25213
25915
|
});
|
|
25214
25916
|
}
|
|
@@ -30582,7 +31284,7 @@ async function importAesKey(key32, usage) {
|
|
|
30582
31284
|
[usage]
|
|
30583
31285
|
);
|
|
30584
31286
|
}
|
|
30585
|
-
async function
|
|
31287
|
+
async function open6(blob, key32) {
|
|
30586
31288
|
if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
|
|
30587
31289
|
throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
|
|
30588
31290
|
}
|
|
@@ -30920,7 +31622,7 @@ async function resolvePolicyShare(link3, options) {
|
|
|
30920
31622
|
options.signal?.throwIfAborted();
|
|
30921
31623
|
let envelope;
|
|
30922
31624
|
try {
|
|
30923
|
-
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(await
|
|
31625
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(await open6(parsed.ciphertext, parsed.key32));
|
|
30924
31626
|
const value = JSON.parse(encoded);
|
|
30925
31627
|
if (canonicalize2(value) !== encoded) throw new Error("non-canonical envelope");
|
|
30926
31628
|
envelope = shareEnvelopeV3Schema.parse(value);
|
|
@@ -33509,7 +34211,7 @@ function receiveJson(result, path) {
|
|
|
33509
34211
|
|
|
33510
34212
|
// src/share/io.ts
|
|
33511
34213
|
import { constants } from "fs";
|
|
33512
|
-
import { lstat as lstat4, mkdir as mkdir4, mkdtemp, open as
|
|
34214
|
+
import { lstat as lstat4, mkdir as mkdir4, mkdtemp, open as open7, readFile as readFile10, realpath as realpath2, stat as stat4, link as link2, rename as rename4, rm as rm5, unlink as unlink3 } from "fs/promises";
|
|
33513
34215
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
33514
34216
|
import { basename as basename4, join as join10, resolve as resolve3, sep as sep2 } from "path";
|
|
33515
34217
|
init_errors();
|
|
@@ -33585,7 +34287,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
33585
34287
|
const outputDirectory = resolve3(directory);
|
|
33586
34288
|
await assertDirectory(outputDirectory);
|
|
33587
34289
|
const safeName = safeFilename(filename);
|
|
33588
|
-
const directoryHandle = await
|
|
34290
|
+
const directoryHandle = await open7(outputDirectory, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0));
|
|
33589
34291
|
const stableDirectory = await realpath2(outputDirectory);
|
|
33590
34292
|
const outputPath = join10(stableDirectory, safeName);
|
|
33591
34293
|
const directoryIdentity = await directoryHandle.stat();
|
|
@@ -33609,7 +34311,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
33609
34311
|
if (error.code !== "ENOENT") throw error;
|
|
33610
34312
|
}
|
|
33611
34313
|
temporaryPath = stagingPath;
|
|
33612
|
-
handle = await
|
|
34314
|
+
handle = await open7(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 384);
|
|
33613
34315
|
await handle.writeFile(bytes);
|
|
33614
34316
|
await handle.close();
|
|
33615
34317
|
handle = void 0;
|