@tinycloud/cli 1.1.0 → 1.2.0-beta.0
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 +118 -90
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +801 -476
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +4 -4
- package/skills/tc-cli/AUTH.md +17 -5
- package/skills/tc-cli/REFERENCE.md +18 -2
package/dist/legacy-entry.js
CHANGED
|
@@ -160,6 +160,51 @@ var init_theme = __esm({
|
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
// src/output/warnings.ts
|
|
164
|
+
function warningKey(warning) {
|
|
165
|
+
return JSON.stringify([warning.code, warning.reason, warning.grantCid ?? null]);
|
|
166
|
+
}
|
|
167
|
+
function mergeWarnings(first, second) {
|
|
168
|
+
const merged = [...first];
|
|
169
|
+
const counts = /* @__PURE__ */ new Map();
|
|
170
|
+
for (const warning of first) {
|
|
171
|
+
const key = warningKey(warning);
|
|
172
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
173
|
+
}
|
|
174
|
+
const seen = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const warning of second) {
|
|
176
|
+
const key = warningKey(warning);
|
|
177
|
+
const occurrence = (seen.get(key) ?? 0) + 1;
|
|
178
|
+
seen.set(key, occurrence);
|
|
179
|
+
if (occurrence > (counts.get(key) ?? 0)) merged.push(warning);
|
|
180
|
+
}
|
|
181
|
+
return merged;
|
|
182
|
+
}
|
|
183
|
+
function recordWarnings(warnings) {
|
|
184
|
+
if (warnings.length === 0) return;
|
|
185
|
+
const holder = globalThis;
|
|
186
|
+
holder[PENDING_WARNINGS] = mergeWarnings(holder[PENDING_WARNINGS] ?? [], warnings);
|
|
187
|
+
}
|
|
188
|
+
function drainWarnings() {
|
|
189
|
+
const holder = globalThis;
|
|
190
|
+
const warnings = holder[PENDING_WARNINGS] ?? [];
|
|
191
|
+
delete holder[PENDING_WARNINGS];
|
|
192
|
+
return warnings;
|
|
193
|
+
}
|
|
194
|
+
function discardWarnings(cid) {
|
|
195
|
+
const holder = globalThis;
|
|
196
|
+
if (holder[PENDING_WARNINGS]) {
|
|
197
|
+
holder[PENDING_WARNINGS] = holder[PENDING_WARNINGS].filter((warning) => warning.grantCid !== cid);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
var PENDING_WARNINGS;
|
|
201
|
+
var init_warnings = __esm({
|
|
202
|
+
"src/output/warnings.ts"() {
|
|
203
|
+
"use strict";
|
|
204
|
+
PENDING_WARNINGS = /* @__PURE__ */ Symbol.for("tinycloud.cli.pendingWarnings.v1");
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
163
208
|
// src/output/formatter.ts
|
|
164
209
|
import ora from "ora";
|
|
165
210
|
function outputJson(data) {
|
|
@@ -168,7 +213,7 @@ function outputJson(data) {
|
|
|
168
213
|
function warningLine(warnings) {
|
|
169
214
|
const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
|
|
170
215
|
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
|
|
216
|
+
return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with tc auth grants remove <cid>.`;
|
|
172
217
|
}
|
|
173
218
|
function outputError(code3, message, hint, details = {}) {
|
|
174
219
|
const { meta, warnings = [] } = details;
|
|
@@ -183,7 +228,7 @@ function outputError(code3, message, hint, details = {}) {
|
|
|
183
228
|
`);
|
|
184
229
|
}
|
|
185
230
|
}
|
|
186
|
-
if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings)
|
|
231
|
+
if (warnings.length > 0) process.stderr.write(` ${theme.warn("!")} ${warningLine(warnings)}
|
|
187
232
|
`);
|
|
188
233
|
} else {
|
|
189
234
|
const payload = {
|
|
@@ -196,9 +241,10 @@ function outputError(code3, message, hint, details = {}) {
|
|
|
196
241
|
}
|
|
197
242
|
}
|
|
198
243
|
function outputWarnings(warnings) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
244
|
+
const combined = mergeWarnings(drainWarnings(), operationWarnings(warnings));
|
|
245
|
+
if (combined.length === 0) return;
|
|
246
|
+
process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings: combined })}
|
|
247
|
+
` : `${theme.warn("!")} ${warningLine(combined)}
|
|
202
248
|
`);
|
|
203
249
|
}
|
|
204
250
|
function operationWarnings(value) {
|
|
@@ -284,6 +330,7 @@ var init_formatter = __esm({
|
|
|
284
330
|
"src/output/formatter.ts"() {
|
|
285
331
|
"use strict";
|
|
286
332
|
init_theme();
|
|
333
|
+
init_warnings();
|
|
287
334
|
WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
|
|
288
335
|
WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
|
|
289
336
|
DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
|
|
@@ -295,20 +342,20 @@ var init_formatter = __esm({
|
|
|
295
342
|
function storageRejection(error) {
|
|
296
343
|
let node = error;
|
|
297
344
|
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null; depth += 1) {
|
|
298
|
-
const
|
|
345
|
+
const record2 = node;
|
|
299
346
|
let code3;
|
|
300
|
-
if (
|
|
301
|
-
code3 =
|
|
302
|
-
} else if (
|
|
347
|
+
if (record2.code === "STORAGE_QUOTA_EXCEEDED" || record2.code === "STORAGE_LIMIT_REACHED") {
|
|
348
|
+
code3 = record2.code;
|
|
349
|
+
} else if (record2.code !== void 0) {
|
|
303
350
|
return void 0;
|
|
304
|
-
} else if (isStorageStatus(
|
|
305
|
-
code3 = storageCodeFromText(
|
|
351
|
+
} else if (isStorageStatus(record2)) {
|
|
352
|
+
code3 = storageCodeFromText(record2.message);
|
|
306
353
|
}
|
|
307
354
|
if (code3) {
|
|
308
|
-
const account = accountTotals(
|
|
355
|
+
const account = accountTotals(record2) ?? accountTotals(record2.meta) ?? accountTotals(record2.metadata);
|
|
309
356
|
return account ? { code: code3, account } : { code: code3 };
|
|
310
357
|
}
|
|
311
|
-
node =
|
|
358
|
+
node = record2.cause;
|
|
312
359
|
}
|
|
313
360
|
return void 0;
|
|
314
361
|
}
|
|
@@ -342,11 +389,11 @@ function storageCodeFromText(message) {
|
|
|
342
389
|
}
|
|
343
390
|
return void 0;
|
|
344
391
|
}
|
|
345
|
-
function isStorageStatus(
|
|
346
|
-
const meta = "meta" in
|
|
392
|
+
function isStorageStatus(record2) {
|
|
393
|
+
const meta = "meta" in record2 && typeof record2.meta === "object" && record2.meta !== null ? record2.meta : {};
|
|
347
394
|
const statuses = [
|
|
348
|
-
"status" in
|
|
349
|
-
"statusCode" in
|
|
395
|
+
"status" in record2 ? record2.status : void 0,
|
|
396
|
+
"statusCode" in record2 ? record2.statusCode : void 0,
|
|
350
397
|
"status" in meta ? meta.status : void 0
|
|
351
398
|
];
|
|
352
399
|
return statuses.some((value) => value === 402 || value === 413);
|
|
@@ -463,6 +510,9 @@ function handleError(error) {
|
|
|
463
510
|
const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
|
|
464
511
|
const authMeta = cliError.code === "AUTH_REQUIRED" || cliError.code === "PERMISSION_DENIED" ? cliError.metadata : void 0;
|
|
465
512
|
const meta = {};
|
|
513
|
+
if (cliError.code === "GRANT_REVOKED_LOCAL_REMOVE_FAILED" && typeof cliError.metadata?.causeCode === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(cliError.metadata.causeCode)) {
|
|
514
|
+
meta.causeCode = cliError.metadata.causeCode;
|
|
515
|
+
}
|
|
466
516
|
if (cliError.status !== void 0 && authMeta !== void 0) meta.status = cliError.status;
|
|
467
517
|
const capability = validatedCapabilityOf({ meta: authMeta });
|
|
468
518
|
if (capability) {
|
|
@@ -471,7 +521,7 @@ function handleError(error) {
|
|
|
471
521
|
}
|
|
472
522
|
outputError(cliError.code, cliError.message, hint, {
|
|
473
523
|
...Object.keys(meta).length ? { meta } : {},
|
|
474
|
-
warnings: operationWarnings(cliError.metadata?.warnings)
|
|
524
|
+
warnings: mergeWarnings(drainWarnings(), operationWarnings(cliError.metadata?.warnings))
|
|
475
525
|
});
|
|
476
526
|
process.exit(cliError.exitCode);
|
|
477
527
|
}
|
|
@@ -543,6 +593,7 @@ var init_errors = __esm({
|
|
|
543
593
|
init_constants();
|
|
544
594
|
init_formatter();
|
|
545
595
|
init_storage2();
|
|
596
|
+
init_warnings();
|
|
546
597
|
CLIError = class extends Error {
|
|
547
598
|
constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
|
|
548
599
|
super(message);
|
|
@@ -7215,21 +7266,21 @@ function validatedCapabilityOf2(error) {
|
|
|
7215
7266
|
let node = error;
|
|
7216
7267
|
for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
7217
7268
|
seen.add(node);
|
|
7218
|
-
const
|
|
7219
|
-
const status = errorStatusOf(
|
|
7220
|
-
const meta =
|
|
7221
|
-
if ((status === 401 || status === 403 || status === void 0 &&
|
|
7269
|
+
const record2 = node;
|
|
7270
|
+
const status = errorStatusOf(record2);
|
|
7271
|
+
const meta = record2.meta;
|
|
7272
|
+
if ((status === 401 || status === 403 || status === void 0 && record2.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
|
|
7222
7273
|
const metadata = meta;
|
|
7223
7274
|
const { resource, requiredAction } = metadata;
|
|
7224
7275
|
if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
|
|
7225
7276
|
const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
|
|
7226
|
-
const explicitService =
|
|
7277
|
+
const explicitService = record2.service === void 0 ? metadata.service : record2.service;
|
|
7227
7278
|
if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
|
|
7228
7279
|
return { resource, requiredAction };
|
|
7229
7280
|
}
|
|
7230
7281
|
}
|
|
7231
7282
|
}
|
|
7232
|
-
node =
|
|
7283
|
+
node = record2.cause;
|
|
7233
7284
|
}
|
|
7234
7285
|
return void 0;
|
|
7235
7286
|
}
|
|
@@ -7254,13 +7305,13 @@ function authorizationVerdictOf2(error) {
|
|
|
7254
7305
|
let node = error;
|
|
7255
7306
|
for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
7256
7307
|
seen.add(node);
|
|
7257
|
-
const
|
|
7258
|
-
const status = errorStatusOf(
|
|
7308
|
+
const record2 = node;
|
|
7309
|
+
const status = errorStatusOf(record2);
|
|
7259
7310
|
if (status === 401) return "unauthenticated";
|
|
7260
7311
|
if (status === 403) return "forbidden";
|
|
7261
7312
|
if (status !== void 0) return "other";
|
|
7262
|
-
if (
|
|
7263
|
-
node =
|
|
7313
|
+
if (record2.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;
|
|
7314
|
+
node = record2.cause;
|
|
7264
7315
|
}
|
|
7265
7316
|
return sawUnauthorizedCode ? "forbidden" : void 0;
|
|
7266
7317
|
}
|
|
@@ -10947,8 +10998,8 @@ function normalizeWebhookRecord(data) {
|
|
|
10947
10998
|
if (!data || typeof data !== "object") {
|
|
10948
10999
|
return null;
|
|
10949
11000
|
}
|
|
10950
|
-
const
|
|
10951
|
-
const candidate = pickWebhookRecord(
|
|
11001
|
+
const record2 = isRecordContainer(data);
|
|
11002
|
+
const candidate = pickWebhookRecord(record2) ?? normalizeWebhookRecord(record2.webhook) ?? normalizeWebhookRecord(record2.hook) ?? normalizeWebhookRecord(record2.subscription) ?? normalizeWebhookRecord(record2.data);
|
|
10952
11003
|
return candidate ?? null;
|
|
10953
11004
|
}
|
|
10954
11005
|
function normalizeWebhookRecordList(data) {
|
|
@@ -10959,12 +11010,12 @@ function normalizeWebhookRecordList(data) {
|
|
|
10959
11010
|
if (!data || typeof data !== "object") {
|
|
10960
11011
|
return null;
|
|
10961
11012
|
}
|
|
10962
|
-
const
|
|
10963
|
-
const nested = maybeRecordArray(
|
|
11013
|
+
const record2 = isRecordContainer(data);
|
|
11014
|
+
const nested = maybeRecordArray(record2.webhooks) ?? maybeRecordArray(record2.subscriptions) ?? maybeRecordArray(record2.hooks) ?? maybeRecordArray(record2.data);
|
|
10964
11015
|
if (nested) {
|
|
10965
11016
|
return nested;
|
|
10966
11017
|
}
|
|
10967
|
-
const single = pickWebhookRecord(
|
|
11018
|
+
const single = pickWebhookRecord(record2);
|
|
10968
11019
|
return single ? [single] : null;
|
|
10969
11020
|
}
|
|
10970
11021
|
function maybeRecordArray(value) {
|
|
@@ -14222,7 +14273,7 @@ async function appendLocalGrantArtifact(profile, delegation) {
|
|
|
14222
14273
|
await withProfileLock2(profile, async () => {
|
|
14223
14274
|
await refuseWriteToDeletedProfile2(profile);
|
|
14224
14275
|
const records = await loadLocalGrantArtifacts(profile);
|
|
14225
|
-
if (records.some((
|
|
14276
|
+
if (records.some((record2) => record2.delegationCid === delegation.cid)) return;
|
|
14226
14277
|
await writeJsonAtomic(localGrantArtifactsPath(profile), [
|
|
14227
14278
|
...records,
|
|
14228
14279
|
{ delegationCid: delegation.cid, delegation }
|
|
@@ -14239,6 +14290,20 @@ async function appendAdditionalDelegations(profile, entries) {
|
|
|
14239
14290
|
async function appendAdditionalDelegation(profile, entry) {
|
|
14240
14291
|
await appendAdditionalDelegations(profile, [entry]);
|
|
14241
14292
|
}
|
|
14293
|
+
async function removeStoredDelegations(profile, cid) {
|
|
14294
|
+
return updateProfileStore(profile, "additional-delegations", (records) => {
|
|
14295
|
+
const remaining = records.filter((entry) => {
|
|
14296
|
+
if (entry === null || typeof entry !== "object" || !("delegation" in entry)) return true;
|
|
14297
|
+
const delegation = entry.delegation;
|
|
14298
|
+
return delegation === null || typeof delegation !== "object" || !("cid" in delegation) || delegation.cid !== cid;
|
|
14299
|
+
});
|
|
14300
|
+
const removed = records.length - remaining.length;
|
|
14301
|
+
if (removed === 0) {
|
|
14302
|
+
throw new CLIError("GRANT_NOT_FOUND", `No stored grant with CID ${cid} exists for this profile.`, ExitCode.NOT_FOUND);
|
|
14303
|
+
}
|
|
14304
|
+
return { records: remaining, result: removed };
|
|
14305
|
+
});
|
|
14306
|
+
}
|
|
14242
14307
|
async function loadPermissionRequestArtifacts(profile) {
|
|
14243
14308
|
const raw = await readAuthRequests(profile);
|
|
14244
14309
|
return raw.filter(isCompatiblePermissionRequestArtifact);
|
|
@@ -14266,55 +14331,13 @@ async function getLastPermissionRequestArtifact(profile) {
|
|
|
14266
14331
|
return existing.at(-1) ?? null;
|
|
14267
14332
|
}
|
|
14268
14333
|
async function replayAdditionalDelegations(node, profile, options) {
|
|
14269
|
-
const {
|
|
14270
|
-
|
|
14271
|
-
prepareStoredDelegationReplay,
|
|
14272
|
-
replayStoredDelegation,
|
|
14273
|
-
storedDelegationKind
|
|
14274
|
-
} = await import("@tinycloud/operations/delegation-binding");
|
|
14275
|
-
const activator = node;
|
|
14276
|
-
const migrated = await prepareStoredDelegationReplay(profile, activator, {
|
|
14334
|
+
const { operationSpaceResolver: operationSpaceResolver2, replayProfileDelegations } = await import("@tinycloud/operations/delegation-binding");
|
|
14335
|
+
const { warnings } = await replayProfileDelegations(profile, node, {
|
|
14277
14336
|
host: options.host,
|
|
14278
|
-
migrate: options.migrate
|
|
14337
|
+
migrate: options.migrate,
|
|
14338
|
+
resolveSpace: operationSpaceResolver2(node, options.ownerSpace)
|
|
14279
14339
|
});
|
|
14280
|
-
|
|
14281
|
-
const entries = await loadAdditionalDelegations(profile);
|
|
14282
|
-
for (const stored of entries) {
|
|
14283
|
-
const kind = storedDelegationKind(stored);
|
|
14284
|
-
if (kind === "compact" || kind === "signed-login") {
|
|
14285
|
-
const replay = await replayStoredDelegation(activator, stored, {
|
|
14286
|
-
host: options.host,
|
|
14287
|
-
migrated,
|
|
14288
|
-
resolveSpace: resolveSpace2
|
|
14289
|
-
});
|
|
14290
|
-
if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
|
|
14291
|
-
process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
|
|
14292
|
-
`);
|
|
14293
|
-
}
|
|
14294
|
-
continue;
|
|
14295
|
-
}
|
|
14296
|
-
if (kind === "refused") {
|
|
14297
|
-
if (process.env.TC_DEBUG_REPLAY === "1") {
|
|
14298
|
-
process.stderr.write("[replay] skipping a malformed stored record or one bound to a request it cannot be held to\n");
|
|
14299
|
-
}
|
|
14300
|
-
continue;
|
|
14301
|
-
}
|
|
14302
|
-
const entry = stored;
|
|
14303
|
-
const expiry = entry.delegation.expiry instanceof Date ? entry.delegation.expiry : new Date(entry.delegation.expiry);
|
|
14304
|
-
if (expiry.getTime() <= Date.now()) continue;
|
|
14305
|
-
try {
|
|
14306
|
-
await node.useRuntimeDelegation({
|
|
14307
|
-
...entry.delegation,
|
|
14308
|
-
delegationHeader: { Authorization: entry.delegation.delegationHeader.Authorization },
|
|
14309
|
-
expiry
|
|
14310
|
-
});
|
|
14311
|
-
} catch (err2) {
|
|
14312
|
-
if (process.env.TC_DEBUG_REPLAY === "1") {
|
|
14313
|
-
process.stderr.write(`[replay] skipping ${entry.delegation.cid}: ${err2.message}
|
|
14314
|
-
`);
|
|
14315
|
-
}
|
|
14316
|
-
}
|
|
14317
|
-
}
|
|
14340
|
+
return warnings;
|
|
14318
14341
|
}
|
|
14319
14342
|
function storedAdditionalDelegation(delegation, permissions) {
|
|
14320
14343
|
return { delegation, permissions };
|
|
@@ -14608,6 +14631,7 @@ function withSignInHint(error, profileName2, posture) {
|
|
|
14608
14631
|
}
|
|
14609
14632
|
|
|
14610
14633
|
// src/lib/sdk.ts
|
|
14634
|
+
init_warnings();
|
|
14611
14635
|
function jwkHasPrivateParameter(jwk) {
|
|
14612
14636
|
if (!jwk || typeof jwk !== "object") return false;
|
|
14613
14637
|
const d = jwk.d;
|
|
@@ -14643,6 +14667,44 @@ async function restoreProfileSession(node, profileName2, profile, sessionData) {
|
|
|
14643
14667
|
throw error;
|
|
14644
14668
|
}
|
|
14645
14669
|
}
|
|
14670
|
+
function persistedSessionData(profileName2, profile, session, key) {
|
|
14671
|
+
return {
|
|
14672
|
+
delegationHeader: session.delegationHeader,
|
|
14673
|
+
delegationCid: session.delegationCid,
|
|
14674
|
+
spaceId: session.spaceId,
|
|
14675
|
+
jwk: signerJwkForProfile(profileName2, session.jwk, key),
|
|
14676
|
+
verificationMethod: session.verificationMethod ?? profile.sessionDid ?? profile.did,
|
|
14677
|
+
address: session.address,
|
|
14678
|
+
chainId: session.chainId,
|
|
14679
|
+
siwe: session.siwe,
|
|
14680
|
+
signature: session.signature
|
|
14681
|
+
};
|
|
14682
|
+
}
|
|
14683
|
+
async function restoreProfileSessionNode(ctx) {
|
|
14684
|
+
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
14685
|
+
const session = await ProfileManager.getSession(ctx.profile);
|
|
14686
|
+
if (!session) return { unavailable: "session_missing" };
|
|
14687
|
+
if (typeof session.delegationCid !== "string" || typeof session.spaceId !== "string" || session.delegationHeader === null || typeof session.delegationHeader !== "object" || !("Authorization" in session.delegationHeader) || typeof session.delegationHeader.Authorization !== "string") {
|
|
14688
|
+
return { unavailable: "session_invalid" };
|
|
14689
|
+
}
|
|
14690
|
+
if (typeof session.expiresAt === "string" && Date.parse(session.expiresAt) <= Date.now()) {
|
|
14691
|
+
return { unavailable: "session_expired" };
|
|
14692
|
+
}
|
|
14693
|
+
const key = await ProfileManager.getKey(ctx.profile);
|
|
14694
|
+
try {
|
|
14695
|
+
const node = new TinyCloudNode({
|
|
14696
|
+
host: ctx.host,
|
|
14697
|
+
...profile.authMethod === "local" && profile.privateKey ? { privateKey: profile.privateKey } : {}
|
|
14698
|
+
});
|
|
14699
|
+
await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
|
|
14700
|
+
return { node };
|
|
14701
|
+
} catch (error) {
|
|
14702
|
+
if (error instanceof CLIError && error.metadata?.reason === "session_expired") {
|
|
14703
|
+
return { unavailable: "session_expired" };
|
|
14704
|
+
}
|
|
14705
|
+
return { unavailable: "session_invalid" };
|
|
14706
|
+
}
|
|
14707
|
+
}
|
|
14646
14708
|
async function createSDKInstance(ctx, options) {
|
|
14647
14709
|
const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
|
|
14648
14710
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
@@ -14660,56 +14722,35 @@ async function createSDKInstance(ctx, options) {
|
|
|
14660
14722
|
host: ctx.host,
|
|
14661
14723
|
privateKey: effectivePrivateKey
|
|
14662
14724
|
});
|
|
14663
|
-
|
|
14664
|
-
if (
|
|
14665
|
-
await restoreProfileSession(node2, ctx.profile, profile,
|
|
14666
|
-
delegationHeader: session.delegationHeader,
|
|
14667
|
-
delegationCid: session.delegationCid,
|
|
14668
|
-
spaceId: session.spaceId,
|
|
14669
|
-
jwk: signerJwkForProfile(ctx.profile, session.jwk, key),
|
|
14670
|
-
verificationMethod: session.verificationMethod ?? profile?.sessionDid ?? profile?.did,
|
|
14671
|
-
address: session.address,
|
|
14672
|
-
chainId: session.chainId,
|
|
14673
|
-
siwe: session.siwe,
|
|
14674
|
-
signature: session.signature
|
|
14675
|
-
});
|
|
14676
|
-
restoredOwnSession2 = true;
|
|
14725
|
+
const restoredOwnSession = Boolean(session && session.delegationHeader && session.delegationCid && session.spaceId);
|
|
14726
|
+
if (restoredOwnSession) {
|
|
14727
|
+
await restoreProfileSession(node2, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
|
|
14677
14728
|
} else {
|
|
14678
14729
|
await node2.signIn();
|
|
14679
14730
|
}
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14731
|
+
if (restoredOwnSession) {
|
|
14732
|
+
recordWarnings(await replayAdditionalDelegations(node2, ctx.profile, {
|
|
14733
|
+
host: ctx.host,
|
|
14734
|
+
ownerSpace: profile.spaceId,
|
|
14735
|
+
migrate: options?.privateKey === void 0
|
|
14736
|
+
}));
|
|
14737
|
+
}
|
|
14685
14738
|
return node2;
|
|
14686
14739
|
}
|
|
14687
14740
|
const node = new TinyCloudNode({
|
|
14688
14741
|
host: ctx.host,
|
|
14689
14742
|
privateKey: options?.privateKey
|
|
14690
14743
|
});
|
|
14691
|
-
let restoredOwnSession = false;
|
|
14692
14744
|
if (options?.privateKey) {
|
|
14693
14745
|
await node.signIn();
|
|
14694
14746
|
} else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
|
|
14695
|
-
await restoreProfileSession(node, ctx.profile, profile,
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
address: session.address,
|
|
14702
|
-
chainId: session.chainId,
|
|
14703
|
-
siwe: session.siwe,
|
|
14704
|
-
signature: session.signature
|
|
14705
|
-
});
|
|
14706
|
-
restoredOwnSession = true;
|
|
14747
|
+
await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
|
|
14748
|
+
recordWarnings(await replayAdditionalDelegations(node, ctx.profile, {
|
|
14749
|
+
host: ctx.host,
|
|
14750
|
+
ownerSpace: profile?.spaceId,
|
|
14751
|
+
migrate: true
|
|
14752
|
+
}));
|
|
14707
14753
|
}
|
|
14708
|
-
await replayAdditionalDelegations(node, ctx.profile, {
|
|
14709
|
-
host: ctx.host,
|
|
14710
|
-
ownerSpace: profile?.spaceId,
|
|
14711
|
-
migrate: restoredOwnSession
|
|
14712
|
-
});
|
|
14713
14754
|
return node;
|
|
14714
14755
|
}
|
|
14715
14756
|
var BOOTSTRAP_PROFILE_FIELDS = ["sessionDid", "spaceId"];
|
|
@@ -19312,8 +19353,8 @@ async function generateLocalIdentity(chainId = 1) {
|
|
|
19312
19353
|
return { privateKey, address, did };
|
|
19313
19354
|
}
|
|
19314
19355
|
async function localKeySignIn(options) {
|
|
19315
|
-
const { TinyCloudNode:
|
|
19316
|
-
const node = new
|
|
19356
|
+
const { TinyCloudNode: TinyCloudNode3 } = await import("@tinycloud/node-sdk");
|
|
19357
|
+
const node = new TinyCloudNode3({
|
|
19317
19358
|
privateKey: options.privateKey,
|
|
19318
19359
|
host: options.host,
|
|
19319
19360
|
autoCreateSpace: true
|
|
@@ -19875,8 +19916,8 @@ function deviceAuthRateLimit(response) {
|
|
|
19875
19916
|
}
|
|
19876
19917
|
function publicSessionJwk(value) {
|
|
19877
19918
|
const publicJwk = publicJwkForDelegation(value);
|
|
19878
|
-
const
|
|
19879
|
-
if (
|
|
19919
|
+
const record2 = publicJwk;
|
|
19920
|
+
if (record2.kty !== "OKP" || record2.crv !== "Ed25519" || typeof record2.x !== "string") {
|
|
19880
19921
|
throw new Error("CLI session key is not a public Ed25519 JWK");
|
|
19881
19922
|
}
|
|
19882
19923
|
return publicJwk;
|
|
@@ -20196,6 +20237,427 @@ async function loginWithDeviceAuthorization(input) {
|
|
|
20196
20237
|
|
|
20197
20238
|
// src/commands/auth.ts
|
|
20198
20239
|
init_theme();
|
|
20240
|
+
|
|
20241
|
+
// src/commands/auth-grants.ts
|
|
20242
|
+
init_profiles();
|
|
20243
|
+
init_types();
|
|
20244
|
+
init_constants();
|
|
20245
|
+
import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
|
|
20246
|
+
import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
|
|
20247
|
+
import {
|
|
20248
|
+
inspectStoredDelegation as inspectStoredDelegation2,
|
|
20249
|
+
operationSpaceResolver,
|
|
20250
|
+
storedDelegationKind,
|
|
20251
|
+
storedDelegationForAnotherKey,
|
|
20252
|
+
storedDelegationSource,
|
|
20253
|
+
withoutBearer as withoutBearer2
|
|
20254
|
+
} from "@tinycloud/operations/delegation-binding";
|
|
20255
|
+
|
|
20256
|
+
// src/lib/revoke-delegation.ts
|
|
20257
|
+
init_constants();
|
|
20258
|
+
init_errors();
|
|
20259
|
+
import { parseSignedCompactUcanAttenuation } from "@tinycloud/sdk-core";
|
|
20260
|
+
import { inspectStoredDelegation, withoutBearer } from "@tinycloud/operations/delegation-binding";
|
|
20261
|
+
function normalizeDid(input) {
|
|
20262
|
+
const trimmed = input.trim();
|
|
20263
|
+
const fragment = trimmed.indexOf("#");
|
|
20264
|
+
return (fragment < 0 ? trimmed : trimmed.slice(0, fragment)).toLowerCase();
|
|
20265
|
+
}
|
|
20266
|
+
function ownerDidFromSpace(spaceId) {
|
|
20267
|
+
const owner = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40})(?::|\/|$)/.exec(spaceId ?? "")?.[1];
|
|
20268
|
+
if (!owner) return void 0;
|
|
20269
|
+
return owner.startsWith("did:") ? owner : `did:${owner}`;
|
|
20270
|
+
}
|
|
20271
|
+
async function cidBoundOwnerSpace(record2, ownerDid, node, host) {
|
|
20272
|
+
const { delegation } = record2;
|
|
20273
|
+
const authorization = delegation.delegationHeader?.Authorization;
|
|
20274
|
+
if (typeof authorization !== "string" || typeof delegation.cid !== "string") return void 0;
|
|
20275
|
+
if (!delegation.siweProof && withoutBearer(authorization).split(".").length === 3) {
|
|
20276
|
+
try {
|
|
20277
|
+
const signed = parseSignedCompactUcanAttenuation(withoutBearer(authorization), delegation.cid);
|
|
20278
|
+
for (const resource of Object.keys(signed.payload.att)) {
|
|
20279
|
+
const space = /^(tinycloud:[^/]+)\/[^/]+\/.*$/.exec(resource)?.[1];
|
|
20280
|
+
if (space && ownerDidFromSpace(space) && normalizeDid(ownerDidFromSpace(space)) === normalizeDid(ownerDid)) {
|
|
20281
|
+
return space;
|
|
20282
|
+
}
|
|
20283
|
+
}
|
|
20284
|
+
} catch {
|
|
20285
|
+
return void 0;
|
|
20286
|
+
}
|
|
20287
|
+
return void 0;
|
|
20288
|
+
}
|
|
20289
|
+
if (!node || !delegation.siweProof) return void 0;
|
|
20290
|
+
const inspected = await inspectStoredDelegation(record2, { node, host: host ?? delegation.host });
|
|
20291
|
+
if (inspected.local !== "verified" || inspected.cid !== delegation.cid) return void 0;
|
|
20292
|
+
for (const permission of inspected.effectivePermissions) {
|
|
20293
|
+
const space = permission.space;
|
|
20294
|
+
if (space && ownerDidFromSpace(space) && normalizeDid(ownerDidFromSpace(space)) === normalizeDid(ownerDid)) {
|
|
20295
|
+
return space;
|
|
20296
|
+
}
|
|
20297
|
+
}
|
|
20298
|
+
return void 0;
|
|
20299
|
+
}
|
|
20300
|
+
async function resolveCidBoundTargetSpace(params) {
|
|
20301
|
+
if (!params.ownerDid) return void 0;
|
|
20302
|
+
if (params.localTarget?.delegation?.cid === params.cid) {
|
|
20303
|
+
const space = await cidBoundOwnerSpace(params.localTarget, params.ownerDid, params.node, params.host);
|
|
20304
|
+
if (space) return { space, source: "local-stored-grant" };
|
|
20305
|
+
}
|
|
20306
|
+
for (const artifact of await loadLocalGrantArtifacts(params.profileName)) {
|
|
20307
|
+
if (artifact.delegationCid !== params.cid || artifact.delegation.cid !== params.cid) continue;
|
|
20308
|
+
const space = await cidBoundOwnerSpace(artifact, params.ownerDid, params.node, params.host);
|
|
20309
|
+
if (space) return { space, source: "local-signed-grant-artifact" };
|
|
20310
|
+
}
|
|
20311
|
+
return void 0;
|
|
20312
|
+
}
|
|
20313
|
+
async function revokeDelegationOnNode(params) {
|
|
20314
|
+
const { ctx, node, profile, cid } = params;
|
|
20315
|
+
const revokePermission = {
|
|
20316
|
+
service: "tinycloud.delegation",
|
|
20317
|
+
space: `urn:cid:${cid}`,
|
|
20318
|
+
path: "",
|
|
20319
|
+
actions: ["tinycloud.delegation/revoke"]
|
|
20320
|
+
};
|
|
20321
|
+
const rawAuthorityCovered = node.hasRuntimePermissions([revokePermission]);
|
|
20322
|
+
if (!rawAuthorityCovered) await requirePermissionConsent([revokePermission], params.yes);
|
|
20323
|
+
let targetFound = params.localTarget?.delegation?.cid === cid;
|
|
20324
|
+
let targetSpaceSource = targetFound ? "local-stored-grant" : "node";
|
|
20325
|
+
let boundSpace;
|
|
20326
|
+
if (!targetFound) {
|
|
20327
|
+
const accountSpaceId = node.accountSpaceId;
|
|
20328
|
+
if (!accountSpaceId) {
|
|
20329
|
+
throw new CLIError("DELEGATION_QUERY_AUTHORITY_UNAVAILABLE", "Cannot determine the account space needed to query delegation records.", ExitCode.AUTH_REQUIRED);
|
|
20330
|
+
}
|
|
20331
|
+
let queryDenied = false;
|
|
20332
|
+
let cursor;
|
|
20333
|
+
do {
|
|
20334
|
+
const query = await node.delegationManager.query({
|
|
20335
|
+
direction: "all",
|
|
20336
|
+
limit: 100,
|
|
20337
|
+
...cursor === void 0 ? {} : { cursor }
|
|
20338
|
+
});
|
|
20339
|
+
if (!query.ok) {
|
|
20340
|
+
if (query.error.meta?.status === 403) {
|
|
20341
|
+
queryDenied = true;
|
|
20342
|
+
break;
|
|
20343
|
+
}
|
|
20344
|
+
throw cliErrorFromService(query.error);
|
|
20345
|
+
}
|
|
20346
|
+
targetFound = query.data.items.some((delegation) => delegation.cid === cid);
|
|
20347
|
+
cursor = targetFound ? void 0 : query.data.nextCursor;
|
|
20348
|
+
} while (cursor !== void 0);
|
|
20349
|
+
if (!targetFound) {
|
|
20350
|
+
const recordedGrant = [...await readGrantHistory(ctx.profile)].reverse().find((entry) => entry.delegationCid === cid);
|
|
20351
|
+
if (recordedGrant) {
|
|
20352
|
+
targetSpaceSource = "local-grant-history";
|
|
20353
|
+
} else if (queryDenied) {
|
|
20354
|
+
boundSpace = await resolveCidBoundTargetSpace({
|
|
20355
|
+
profileName: ctx.profile,
|
|
20356
|
+
ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
|
|
20357
|
+
cid,
|
|
20358
|
+
node,
|
|
20359
|
+
host: ctx.host
|
|
20360
|
+
});
|
|
20361
|
+
if (!boundSpace) {
|
|
20362
|
+
throw new CLIError("TARGET_NOT_FOUND", `Delegation "${cid}" was not found by the node or local grant sources.`, ExitCode.NOT_FOUND);
|
|
20363
|
+
}
|
|
20364
|
+
targetSpaceSource = boundSpace.source;
|
|
20365
|
+
} else {
|
|
20366
|
+
throw new CLIError("TARGET_NOT_FOUND", `Delegation "${cid}" was not found by the node or local grant history.`, ExitCode.NOT_FOUND);
|
|
20367
|
+
}
|
|
20368
|
+
}
|
|
20369
|
+
}
|
|
20370
|
+
let authorityScopeSource = "cid-resource";
|
|
20371
|
+
let authorityScopeReason = "The revoke authority is scoped to the exact delegation CID.";
|
|
20372
|
+
let fallbackSpaceId;
|
|
20373
|
+
let commandAuthorityCid;
|
|
20374
|
+
if (!rawAuthorityCovered) {
|
|
20375
|
+
try {
|
|
20376
|
+
const acquired = await ensureDelegationAuthority({
|
|
20377
|
+
ctx,
|
|
20378
|
+
profile,
|
|
20379
|
+
node,
|
|
20380
|
+
requested: [revokePermission],
|
|
20381
|
+
expiryOption: void 0,
|
|
20382
|
+
reason: `Revoke delegation ${cid}`,
|
|
20383
|
+
yes: params.yes,
|
|
20384
|
+
consentAlreadyGiven: true,
|
|
20385
|
+
persist: false,
|
|
20386
|
+
openKeyAcquisition: params.openKeyAcquisition
|
|
20387
|
+
});
|
|
20388
|
+
commandAuthorityCid = acquired?.cid;
|
|
20389
|
+
} catch (error) {
|
|
20390
|
+
if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "RAW_RECAP_RESOURCE_UNSUPPORTED") {
|
|
20391
|
+
throw error;
|
|
20392
|
+
}
|
|
20393
|
+
const fallback = boundSpace ?? await resolveCidBoundTargetSpace({
|
|
20394
|
+
profileName: ctx.profile,
|
|
20395
|
+
ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
|
|
20396
|
+
cid,
|
|
20397
|
+
node,
|
|
20398
|
+
host: ctx.host,
|
|
20399
|
+
localTarget: params.localTarget
|
|
20400
|
+
});
|
|
20401
|
+
if (!fallback) {
|
|
20402
|
+
throw new CLIError(
|
|
20403
|
+
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
20404
|
+
`Cannot safely revoke "${cid}": raw CID ReCap resources are unavailable and no matching owner-signed local grant binds its space to the CID.`,
|
|
20405
|
+
ExitCode.PERMISSION_DENIED
|
|
20406
|
+
);
|
|
20407
|
+
}
|
|
20408
|
+
fallbackSpaceId = fallback.space;
|
|
20409
|
+
authorityScopeSource = fallback.source;
|
|
20410
|
+
authorityScopeReason = fallback.source === "local-signed-grant-artifact" ? "Raw-CID revoke authority could not be issued; the fallback space came from an owner-signed local grant whose Authorization recomputes to this CID." : "Raw-CID revoke authority could not be issued; the fallback space came from a locally verified owner-signed stored grant bound to this CID.";
|
|
20411
|
+
targetSpaceSource = fallback.source;
|
|
20412
|
+
const acquired = await ensureDelegationAuthority({
|
|
20413
|
+
ctx,
|
|
20414
|
+
profile,
|
|
20415
|
+
node,
|
|
20416
|
+
requested: [{
|
|
20417
|
+
service: "tinycloud.delegation",
|
|
20418
|
+
space: profile.authMethod !== "openkey" && fallback.space === node.accountSpaceId ? "default" : fallback.space,
|
|
20419
|
+
path: "",
|
|
20420
|
+
actions: ["tinycloud.delegation/revoke"]
|
|
20421
|
+
}],
|
|
20422
|
+
expiryOption: void 0,
|
|
20423
|
+
reason: `Revoke delegation ${cid} using its CID-bound owner-signed grant ${fallback.source === "local-stored-grant" ? "record" : "artifact"}`,
|
|
20424
|
+
yes: params.yes,
|
|
20425
|
+
consentAlreadyGiven: true,
|
|
20426
|
+
persist: false,
|
|
20427
|
+
openKeyAcquisition: params.openKeyAcquisition
|
|
20428
|
+
});
|
|
20429
|
+
commandAuthorityCid = acquired?.cid;
|
|
20430
|
+
}
|
|
20431
|
+
}
|
|
20432
|
+
const result = fallbackSpaceId ? await node.delegationManager.revoke(cid, {
|
|
20433
|
+
targetSpaceId: fallbackSpaceId,
|
|
20434
|
+
...commandAuthorityCid === void 0 ? {} : { authorityCid: commandAuthorityCid }
|
|
20435
|
+
}) : commandAuthorityCid === void 0 ? await node.revokeDelegation(cid) : await node.delegationManager.revoke(cid, { authorityCid: commandAuthorityCid });
|
|
20436
|
+
if (!result.ok) {
|
|
20437
|
+
if (result.error.meta?.status === 403 && result.error.message === "Failed to revoke delegation: 403 - Unauthorized Revoker") {
|
|
20438
|
+
const profileDid = profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId) ?? "unknown";
|
|
20439
|
+
throw new CLIError(
|
|
20440
|
+
"REVOKE_UNAUTHORIZED",
|
|
20441
|
+
"The node rejected this revocation: Unauthorized Revoker.",
|
|
20442
|
+
ExitCode.PERMISSION_DENIED,
|
|
20443
|
+
{ status: 403, hint: `Use a different profile belonging to the delegation's grantor, its recipient, or the owner of a space it covers; the rejected profile was "${ctx.profile}" (DID ${profileDid}). Retry with \`tc --profile <profile-name> delegation revoke ${cid}\`.` }
|
|
20444
|
+
);
|
|
20445
|
+
}
|
|
20446
|
+
throw cliErrorFromService(result.error);
|
|
20447
|
+
}
|
|
20448
|
+
return { cid, revoked: true, targetSpaceSource, authorityScopeSource, authorityScopeReason };
|
|
20449
|
+
}
|
|
20450
|
+
|
|
20451
|
+
// src/commands/auth-grants.ts
|
|
20452
|
+
init_warnings();
|
|
20453
|
+
init_errors();
|
|
20454
|
+
init_formatter();
|
|
20455
|
+
var CID = /^bafkr4i[a-z2-7]{52}$/;
|
|
20456
|
+
function record(value) {
|
|
20457
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
20458
|
+
}
|
|
20459
|
+
function isoDate(value) {
|
|
20460
|
+
if (typeof value !== "string" && !(value instanceof Date)) return null;
|
|
20461
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
20462
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : null;
|
|
20463
|
+
}
|
|
20464
|
+
async function localContext(options) {
|
|
20465
|
+
const profileName2 = options.profile ?? process.env.TC_PROFILE ?? (await ProfileManager.getConfig()).defaultProfile;
|
|
20466
|
+
const profile = await ProfileManager.getProfile(profileName2);
|
|
20467
|
+
const ctx = await ProfileManager.resolveContext({
|
|
20468
|
+
...options,
|
|
20469
|
+
profile: profileName2,
|
|
20470
|
+
host: options.host ?? process.env.TC_HOST ?? profile.host
|
|
20471
|
+
});
|
|
20472
|
+
return { ctx, profile };
|
|
20473
|
+
}
|
|
20474
|
+
function storedScope(value) {
|
|
20475
|
+
const parsed = PermissionEntrySchema.array().safeParse(record(value)?.permissions);
|
|
20476
|
+
return parsed.success ? parsed.data : [];
|
|
20477
|
+
}
|
|
20478
|
+
function storedProof(value) {
|
|
20479
|
+
const kind = storedDelegationKind(value);
|
|
20480
|
+
if (kind === "signed-login") return "siwe";
|
|
20481
|
+
if (kind === "compact") return "compact";
|
|
20482
|
+
if (kind === "other") return "missing";
|
|
20483
|
+
const header = record(record(record(value)?.delegation)?.delegationHeader)?.Authorization;
|
|
20484
|
+
if (typeof header !== "string") return null;
|
|
20485
|
+
return withoutBearer2(header).includes(".") ? "compact" : "missing";
|
|
20486
|
+
}
|
|
20487
|
+
async function listedGrant(value, index, options) {
|
|
20488
|
+
const delegation = record(record(value)?.delegation);
|
|
20489
|
+
const rawCid = delegation?.cid;
|
|
20490
|
+
const expiry = isoDate(delegation?.expiry);
|
|
20491
|
+
const forAnotherKey = options.node !== void 0 && storedDelegationForAnotherKey(value, options.node);
|
|
20492
|
+
const inspection = forAnotherKey ? void 0 : await inspectStoredDelegation2(value, options);
|
|
20493
|
+
const source = storedDelegationSource(value);
|
|
20494
|
+
return {
|
|
20495
|
+
index,
|
|
20496
|
+
cid: typeof rawCid === "string" && CID.test(rawCid) ? rawCid : null,
|
|
20497
|
+
expiry,
|
|
20498
|
+
expired: expiry !== null && Date.parse(expiry) <= Date.now(),
|
|
20499
|
+
local: inspection?.local ?? "for_another_key",
|
|
20500
|
+
reason: inspection?.local === "refused" ? inspection.reason : null,
|
|
20501
|
+
node: "not_checked",
|
|
20502
|
+
bound: DelegationRequestBindingSchema.safeParse(record(value)?.authorityRequest).success,
|
|
20503
|
+
proof: storedProof(value),
|
|
20504
|
+
source,
|
|
20505
|
+
scope: inspection?.local === "verified" ? inspection.effectivePermissions : storedScope(value),
|
|
20506
|
+
scopeSource: inspection?.local === "verified" ? "signed" : "stored"
|
|
20507
|
+
};
|
|
20508
|
+
}
|
|
20509
|
+
function localSessionCid(session, host) {
|
|
20510
|
+
const header = record(session.delegationHeader)?.Authorization;
|
|
20511
|
+
const cid = session.delegationCid;
|
|
20512
|
+
if (typeof header !== "string" || typeof cid !== "string" || !CID.test(cid)) return null;
|
|
20513
|
+
try {
|
|
20514
|
+
return new TinyCloudNode2({ host }).computeDelegationCid(header) === cid ? cid : null;
|
|
20515
|
+
} catch {
|
|
20516
|
+
return null;
|
|
20517
|
+
}
|
|
20518
|
+
}
|
|
20519
|
+
function renderList(result) {
|
|
20520
|
+
if (shouldOutputJson()) {
|
|
20521
|
+
outputJson(result);
|
|
20522
|
+
return;
|
|
20523
|
+
}
|
|
20524
|
+
process.stdout.write(`Profile: ${result.profile}
|
|
20525
|
+
`);
|
|
20526
|
+
process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (revoke with tc delegation revoke ${result.session.cid})
|
|
20527
|
+
` : "\n"));
|
|
20528
|
+
process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
|
|
20529
|
+
`);
|
|
20530
|
+
process.stdout.write(formatTable(
|
|
20531
|
+
["CID", "LOCAL", "EXPIRES", "SOURCE", "BOUND", "SCOPE"],
|
|
20532
|
+
result.grants.map((grant) => [
|
|
20533
|
+
grant.cid ?? "\u2014",
|
|
20534
|
+
grant.local === "refused" ? `refused (${grant.reason})` : grant.local === "for_another_key" ? "for another key (not installable here)" : grant.local,
|
|
20535
|
+
grant.expiry ?? "\u2014",
|
|
20536
|
+
grant.source,
|
|
20537
|
+
grant.bound ? "yes" : "no",
|
|
20538
|
+
grant.scope.map((permission) => `${permission.service}:${permission.space ?? "default"}:${permission.path}:${permission.actions.join(",")}`).join("; ") || "\u2014"
|
|
20539
|
+
])
|
|
20540
|
+
) + "\n");
|
|
20541
|
+
}
|
|
20542
|
+
function registerAuthGrantsCommand(auth, options = {}) {
|
|
20543
|
+
const grants = auth.command("grants").description("Inspect and remove locally stored grants");
|
|
20544
|
+
grants.command("list").description("Inspect locally stored grants without asking the node (node chain and revocation remain unchecked)").action(async (_options, cmd) => {
|
|
20545
|
+
try {
|
|
20546
|
+
const { ctx, profile } = await localContext(cmd.optsWithGlobals());
|
|
20547
|
+
const session = record(await ProfileManager.getSession(ctx.profile));
|
|
20548
|
+
const restored = await restoreProfileSessionNode(ctx);
|
|
20549
|
+
const node = "node" in restored ? restored.node : void 0;
|
|
20550
|
+
const entries = await loadAdditionalDelegations(ctx.profile);
|
|
20551
|
+
const grants2 = await Promise.all(entries.map((entry, index) => listedGrant(entry, index, {
|
|
20552
|
+
node,
|
|
20553
|
+
host: ctx.host,
|
|
20554
|
+
...node ? { resolveSpace: operationSpaceResolver(node, profile.spaceId) } : {}
|
|
20555
|
+
})));
|
|
20556
|
+
renderList({
|
|
20557
|
+
profile: ctx.profile,
|
|
20558
|
+
session: session === void 0 ? null : {
|
|
20559
|
+
cid: localSessionCid(session, ctx.host),
|
|
20560
|
+
expiresAt: isoDate(session.expiresAt ?? session.expiry)
|
|
20561
|
+
},
|
|
20562
|
+
localVerification: "node" in restored ? "available" : restored.unavailable,
|
|
20563
|
+
grants: grants2
|
|
20564
|
+
});
|
|
20565
|
+
} catch (error) {
|
|
20566
|
+
handleError(error);
|
|
20567
|
+
}
|
|
20568
|
+
});
|
|
20569
|
+
grants.command("remove <cid>").description("Remove locally stored grants for exactly this CID; optionally revoke as the owner").option("--revoke", "Revoke on the node before removing local grants").option("--yes", "Skip local-key TTY confirmation", false).action(async (cid, flags, cmd) => {
|
|
20570
|
+
try {
|
|
20571
|
+
const { ctx, profile } = await localContext(cmd.optsWithGlobals());
|
|
20572
|
+
if (flags.revoke && resolveProfilePosture(profile) === "delegate-session") {
|
|
20573
|
+
throw new CLIError(
|
|
20574
|
+
"REVOKE_UNAUTHORIZED",
|
|
20575
|
+
"A delegate-session profile cannot revoke a stored grant. Remove it locally or ask its owner to revoke it.",
|
|
20576
|
+
ExitCode.PERMISSION_DENIED
|
|
20577
|
+
);
|
|
20578
|
+
}
|
|
20579
|
+
if (!CID.test(cid)) {
|
|
20580
|
+
throw new CLIError("INVALID_ARGUMENT", "Expected a delegation CID.", ExitCode.USAGE_ERROR);
|
|
20581
|
+
}
|
|
20582
|
+
const entries = await loadAdditionalDelegations(ctx.profile);
|
|
20583
|
+
const matched = entries.filter((entry) => record(record(entry)?.delegation)?.cid === cid);
|
|
20584
|
+
if (matched.length === 0) {
|
|
20585
|
+
throw new CLIError("GRANT_NOT_FOUND", `No locally stored grant has CID ${cid}.`, ExitCode.NOT_FOUND);
|
|
20586
|
+
}
|
|
20587
|
+
if (flags.revoke) {
|
|
20588
|
+
const revokeCtx = await ProfileManager.resolveContext({ ...cmd.optsWithGlobals(), profile: ctx.profile });
|
|
20589
|
+
const node = await ensureAuthenticated(revokeCtx);
|
|
20590
|
+
const resolveSpace2 = operationSpaceResolver(node, profile.spaceId);
|
|
20591
|
+
let localTarget = matched[0];
|
|
20592
|
+
for (const candidate of matched) {
|
|
20593
|
+
const inspection = await inspectStoredDelegation2(candidate, { node, host: revokeCtx.host, resolveSpace: resolveSpace2 });
|
|
20594
|
+
if (inspection.local === "verified") {
|
|
20595
|
+
localTarget = candidate;
|
|
20596
|
+
break;
|
|
20597
|
+
}
|
|
20598
|
+
}
|
|
20599
|
+
const revoked = await revokeDelegationOnNode({
|
|
20600
|
+
ctx: revokeCtx,
|
|
20601
|
+
profile,
|
|
20602
|
+
node,
|
|
20603
|
+
cid,
|
|
20604
|
+
yes: flags.yes ?? false,
|
|
20605
|
+
localTarget,
|
|
20606
|
+
openKeyAcquisition: options.openKeyAcquisition
|
|
20607
|
+
});
|
|
20608
|
+
let removed;
|
|
20609
|
+
try {
|
|
20610
|
+
removed = await removeStoredDelegations(ctx.profile, cid);
|
|
20611
|
+
} catch (error) {
|
|
20612
|
+
const cause = wrapError(error);
|
|
20613
|
+
if (cause.code === "GRANT_NOT_FOUND") {
|
|
20614
|
+
removed = 0;
|
|
20615
|
+
} else {
|
|
20616
|
+
throw new CLIError(
|
|
20617
|
+
"GRANT_REVOKED_LOCAL_REMOVE_FAILED",
|
|
20618
|
+
`Delegation ${cid} was revoked on the node, but its local grant could not be removed (${cause.code}).`,
|
|
20619
|
+
ExitCode.ERROR,
|
|
20620
|
+
{
|
|
20621
|
+
causeCode: cause.code,
|
|
20622
|
+
hint: `Do not use --revoke again. After fixing local profile storage, run: tc --profile ${ctx.profile} auth grants remove ${cid}`
|
|
20623
|
+
}
|
|
20624
|
+
);
|
|
20625
|
+
}
|
|
20626
|
+
}
|
|
20627
|
+
discardWarnings(cid);
|
|
20628
|
+
const result = {
|
|
20629
|
+
profile: ctx.profile,
|
|
20630
|
+
cid,
|
|
20631
|
+
removed,
|
|
20632
|
+
revoked: true,
|
|
20633
|
+
targetSpaceSource: revoked.targetSpaceSource,
|
|
20634
|
+
authorityScopeSource: revoked.authorityScopeSource,
|
|
20635
|
+
authorityScopeReason: revoked.authorityScopeReason
|
|
20636
|
+
};
|
|
20637
|
+
if (shouldOutputJson()) outputJson(result);
|
|
20638
|
+
else process.stdout.write(`Revoked ${cid} and removed ${removed} local grant${removed === 1 ? "" : "s"}.
|
|
20639
|
+
`);
|
|
20640
|
+
} else {
|
|
20641
|
+
const removed = await removeStoredDelegations(ctx.profile, cid);
|
|
20642
|
+
discardWarnings(cid);
|
|
20643
|
+
const result = {
|
|
20644
|
+
profile: ctx.profile,
|
|
20645
|
+
cid,
|
|
20646
|
+
removed,
|
|
20647
|
+
revoked: false,
|
|
20648
|
+
warning: "Only local copies were removed; the node delegation was not revoked."
|
|
20649
|
+
};
|
|
20650
|
+
if (shouldOutputJson()) outputJson(result);
|
|
20651
|
+
else process.stdout.write(`Removed ${removed} local grant${removed === 1 ? "" : "s"} for ${cid}. The node delegation was not revoked.
|
|
20652
|
+
`);
|
|
20653
|
+
}
|
|
20654
|
+
} catch (error) {
|
|
20655
|
+
handleError(error);
|
|
20656
|
+
}
|
|
20657
|
+
});
|
|
20658
|
+
}
|
|
20659
|
+
|
|
20660
|
+
// src/commands/auth.ts
|
|
20199
20661
|
init_space();
|
|
20200
20662
|
function resolveOpenKeyHost(profile) {
|
|
20201
20663
|
return process.env.TC_OPENKEY_HOST ?? profile.openkeyHost ?? DEFAULT_OPENKEY_HOST;
|
|
@@ -20226,18 +20688,19 @@ async function promptAuthMethod() {
|
|
|
20226
20688
|
});
|
|
20227
20689
|
});
|
|
20228
20690
|
}
|
|
20229
|
-
function registerAuthCommand(program) {
|
|
20691
|
+
function registerAuthCommand(program, options) {
|
|
20230
20692
|
const auth = program.command("auth").description("Authentication management");
|
|
20231
|
-
auth
|
|
20693
|
+
registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
|
|
20694
|
+
auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`tc share publish\``).option("--expiry <duration>", "OpenKey session lifetime, e.g. 1h or 7d (device login: at most 30d, default 30d)").option("--owner <did>", "Sign in as this owner (did:pkh:eip155:CHAIN:ADDRESS): OpenKey preselects that key, e.g. one that is not the account's primary key, and an approval by any other identity is refused. With --manifest, also names the secrets owner for a manifest's `secrets` on a profile with no recorded owner").option("--replace-session", "Scoped or device login: replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").action(async (options2, cmd) => {
|
|
20232
20695
|
try {
|
|
20233
|
-
if (
|
|
20696
|
+
if (options2.device && options2.paste) {
|
|
20234
20697
|
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
20235
20698
|
}
|
|
20236
|
-
const scoped =
|
|
20237
|
-
if (scoped &&
|
|
20699
|
+
const scoped = options2.device || options2.manifest || options2.expiry || options2.owner;
|
|
20700
|
+
if (scoped && options2.method === "local") {
|
|
20238
20701
|
throw new CLIError("INVALID_ARGUMENT", "--device, --manifest, --expiry and --owner require OpenKey login.", ExitCode.USAGE_ERROR);
|
|
20239
20702
|
}
|
|
20240
|
-
if (
|
|
20703
|
+
if (options2.device && !options2.manifest) {
|
|
20241
20704
|
throw new CLIError(
|
|
20242
20705
|
"MANIFEST_REQUIRED",
|
|
20243
20706
|
`Device login requests an explicit scope. Pass --manifest FILE, or --manifest ${SHARE_PUBLISHING_MANIFEST_REF} for Share publishing.`,
|
|
@@ -20246,19 +20709,19 @@ function registerAuthCommand(program) {
|
|
|
20246
20709
|
}
|
|
20247
20710
|
const globalOpts = cmd.optsWithGlobals();
|
|
20248
20711
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20249
|
-
const owner =
|
|
20250
|
-
const permissions =
|
|
20712
|
+
const owner = options2.owner === void 0 ? void 0 : canonicalOwnerDid(options2.owner);
|
|
20713
|
+
const permissions = options2.manifest ? await loadManifestPermissions(options2.manifest, ctx.profile, { allowLogicalSpaces: true, ownerDid: owner, device: options2.device === true }) : void 0;
|
|
20251
20714
|
const persistHost = globalOpts.host !== void 0;
|
|
20252
|
-
if (
|
|
20715
|
+
if (options2.device) {
|
|
20253
20716
|
const { profile, result } = await loginWithDeviceAuthorization({
|
|
20254
20717
|
profileName: ctx.profile,
|
|
20255
20718
|
nodeOrigin: ctx.host,
|
|
20256
20719
|
shareOrigin: DEFAULT_SHARE_ORIGIN,
|
|
20257
20720
|
permissions,
|
|
20258
|
-
...
|
|
20721
|
+
...options2.expiry === void 0 ? {} : { expiry: parseRequestedExpiry(parseExpiryOption(options2.expiry)) },
|
|
20259
20722
|
reason: "Allow this TinyCloud CLI profile to use the permissions in the requested manifest.",
|
|
20260
20723
|
expectedOwner: owner,
|
|
20261
|
-
replaceSession:
|
|
20724
|
+
replaceSession: options2.replaceSession === true,
|
|
20262
20725
|
persistHost
|
|
20263
20726
|
});
|
|
20264
20727
|
reportDeclined(result.declined);
|
|
@@ -20279,19 +20742,19 @@ function registerAuthCommand(program) {
|
|
|
20279
20742
|
return;
|
|
20280
20743
|
}
|
|
20281
20744
|
let method;
|
|
20282
|
-
if (
|
|
20283
|
-
if (
|
|
20745
|
+
if (options2.method) {
|
|
20746
|
+
if (options2.method !== "local" && options2.method !== "openkey") {
|
|
20284
20747
|
throw new CLIError(
|
|
20285
20748
|
"INVALID_METHOD",
|
|
20286
|
-
`Invalid auth method "${
|
|
20749
|
+
`Invalid auth method "${options2.method}". Use "local" or "openkey".`,
|
|
20287
20750
|
ExitCode.USAGE_ERROR
|
|
20288
20751
|
);
|
|
20289
20752
|
}
|
|
20290
|
-
method =
|
|
20753
|
+
method = options2.method;
|
|
20291
20754
|
} else {
|
|
20292
20755
|
method = scoped ? "openkey" : await promptAuthMethod();
|
|
20293
20756
|
}
|
|
20294
|
-
if (method === "openkey" && !
|
|
20757
|
+
if (method === "openkey" && !options2.paste && options2.popup !== false && !process.stdin.isTTY && !process.stderr.isTTY) {
|
|
20295
20758
|
throw new CLIError(
|
|
20296
20759
|
"INTERACTIVE_LOGIN_REQUIRED",
|
|
20297
20760
|
"Browser login needs a visible approval URL and would wait silently here. Use `--paste` to print the URL and provide the owner's code on stdin.",
|
|
@@ -20302,12 +20765,12 @@ function registerAuthCommand(program) {
|
|
|
20302
20765
|
await handleLocalAuth(ctx.profile, ctx.host);
|
|
20303
20766
|
} else {
|
|
20304
20767
|
await handleOpenKeyAuth(ctx.profile, ctx.host, {
|
|
20305
|
-
paste:
|
|
20306
|
-
noPopup:
|
|
20768
|
+
paste: options2.paste,
|
|
20769
|
+
noPopup: options2.popup === false,
|
|
20307
20770
|
permissions,
|
|
20308
|
-
expiry: parseExpiryOption(
|
|
20771
|
+
expiry: parseExpiryOption(options2.expiry),
|
|
20309
20772
|
expectedOwner: owner,
|
|
20310
|
-
replaceSession:
|
|
20773
|
+
replaceSession: options2.replaceSession === true,
|
|
20311
20774
|
persistHost
|
|
20312
20775
|
});
|
|
20313
20776
|
}
|
|
@@ -20315,31 +20778,31 @@ function registerAuthCommand(program) {
|
|
|
20315
20778
|
handleError(error);
|
|
20316
20779
|
}
|
|
20317
20780
|
});
|
|
20318
|
-
auth.command("logout").description("Clear the local session and remove local replicas. Logout is local; delegations and grants on the node stay valid until they expire. Use `tc delegation revoke <cid>` to end access now.").option("--keep-replicas", "Keep this profile's local replicas").action(async (
|
|
20781
|
+
auth.command("logout").description("Clear the local session and remove local replicas. Logout is local; delegations and grants on the node stay valid until they expire. Use `tc delegation revoke <cid>` to end access now.").option("--keep-replicas", "Keep this profile's local replicas").action(async (options2, cmd) => {
|
|
20319
20782
|
try {
|
|
20320
20783
|
const globalOpts = cmd.optsWithGlobals();
|
|
20321
20784
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20322
20785
|
await ProfileManager.getProfile(ctx.profile);
|
|
20323
20786
|
await ProfileManager.clearSession(ctx.profile);
|
|
20324
|
-
const replicasRemoved =
|
|
20787
|
+
const replicasRemoved = options2.keepReplicas ? [] : await removeProfileReplicas(ctx.profile);
|
|
20325
20788
|
outputJson({
|
|
20326
20789
|
profile: ctx.profile,
|
|
20327
20790
|
authenticated: false,
|
|
20328
20791
|
replicasRemoved,
|
|
20329
|
-
replicasKept:
|
|
20792
|
+
replicasKept: options2.keepReplicas === true,
|
|
20330
20793
|
warning: "Logout is local; delegations and grants on the node stay valid until they expire. Use `tc delegation revoke <cid>` to end access now."
|
|
20331
20794
|
});
|
|
20332
20795
|
} catch (error) {
|
|
20333
20796
|
handleError(error);
|
|
20334
20797
|
}
|
|
20335
20798
|
});
|
|
20336
|
-
auth.command("rotate").description("Rotate the active profile session key").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").action(async (
|
|
20799
|
+
auth.command("rotate").description("Rotate the active profile session key").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").action(async (options2, cmd) => {
|
|
20337
20800
|
try {
|
|
20338
20801
|
const globalOpts = cmd.optsWithGlobals();
|
|
20339
20802
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20340
20803
|
await rotateAuthKey(ctx.profile, ctx.host, {
|
|
20341
|
-
paste:
|
|
20342
|
-
noPopup:
|
|
20804
|
+
paste: options2.paste,
|
|
20805
|
+
noPopup: options2.popup === false
|
|
20343
20806
|
});
|
|
20344
20807
|
} catch (error) {
|
|
20345
20808
|
handleError(error);
|
|
@@ -20404,16 +20867,16 @@ function registerAuthCommand(program) {
|
|
|
20404
20867
|
).option("--permission <file>", 'JSON permission request: { "permissions": PermissionEntry[] }').option("--manifest <fileOrBase64>", "Manifest file, base64:<json>, or raw base64 JSON").option(
|
|
20405
20868
|
"--expiry <duration>",
|
|
20406
20869
|
`Lifetime of the granted delegation. ms-format string (e.g. "7d", "30m") or raw milliseconds. Defaults to 7d, capped by the active session's expiry.`
|
|
20407
|
-
).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").action(async (
|
|
20870
|
+
).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").action(async (options2, cmd) => {
|
|
20408
20871
|
try {
|
|
20409
20872
|
const globalOpts = cmd.optsWithGlobals();
|
|
20410
20873
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20411
20874
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
20412
|
-
if (
|
|
20875
|
+
if (options2.device && (!options2.grant || resolveProfilePosture(profile) !== "owner-openkey")) {
|
|
20413
20876
|
throw new CLIError("INVALID_ARGUMENT", "--device requires --grant on an OpenKey owner profile.", ExitCode.USAGE_ERROR);
|
|
20414
20877
|
}
|
|
20415
|
-
const requested = await collectRequestedPermissions(
|
|
20416
|
-
const expiryOption = parseExpiryOption(
|
|
20878
|
+
const requested = await collectRequestedPermissions(options2, ctx.profile);
|
|
20879
|
+
const expiryOption = parseExpiryOption(options2.expiry);
|
|
20417
20880
|
if (requested.length === 0) {
|
|
20418
20881
|
throw new CLIError(
|
|
20419
20882
|
"NO_CAPS_REQUESTED",
|
|
@@ -20421,7 +20884,7 @@ function registerAuthCommand(program) {
|
|
|
20421
20884
|
ExitCode.USAGE_ERROR
|
|
20422
20885
|
);
|
|
20423
20886
|
}
|
|
20424
|
-
if (!
|
|
20887
|
+
if (!options2.grant) {
|
|
20425
20888
|
const artifact = createPermissionRequestArtifact({
|
|
20426
20889
|
profileName: ctx.profile,
|
|
20427
20890
|
profile,
|
|
@@ -20430,7 +20893,7 @@ function registerAuthCommand(program) {
|
|
|
20430
20893
|
requestedExpiry: expiryOption
|
|
20431
20894
|
});
|
|
20432
20895
|
await appendPermissionRequestArtifact(ctx.profile, artifact);
|
|
20433
|
-
await emitPermissionRequestArtifact(artifact,
|
|
20896
|
+
await emitPermissionRequestArtifact(artifact, options2.emit);
|
|
20434
20897
|
return;
|
|
20435
20898
|
}
|
|
20436
20899
|
const node = await ensureAuthenticated(ctx);
|
|
@@ -20438,7 +20901,7 @@ function registerAuthCommand(program) {
|
|
|
20438
20901
|
outputJson({ changed: false, missing: [], added: [] });
|
|
20439
20902
|
return;
|
|
20440
20903
|
}
|
|
20441
|
-
if (profile.authMethod === "openkey" ||
|
|
20904
|
+
if (profile.authMethod === "openkey" || options2.device) {
|
|
20442
20905
|
const key = await ProfileManager.getKey(ctx.profile);
|
|
20443
20906
|
if (!key) {
|
|
20444
20907
|
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`tc --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
|
|
@@ -20460,7 +20923,7 @@ function registerAuthCommand(program) {
|
|
|
20460
20923
|
);
|
|
20461
20924
|
const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
|
|
20462
20925
|
let delegationData;
|
|
20463
|
-
if (
|
|
20926
|
+
if (options2.device) {
|
|
20464
20927
|
const approval = await acquireDeviceDelegation({
|
|
20465
20928
|
sessionDid: keyToDID(key),
|
|
20466
20929
|
jwk: key,
|
|
@@ -20482,13 +20945,13 @@ function registerAuthCommand(program) {
|
|
|
20482
20945
|
reason,
|
|
20483
20946
|
openkeyHost,
|
|
20484
20947
|
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
|
|
20485
|
-
noPopup:
|
|
20948
|
+
noPopup: options2.popup === false
|
|
20486
20949
|
});
|
|
20487
20950
|
}
|
|
20488
20951
|
const delegation = portableFromOpenKeyDelegation(delegationData, request, ctx.host, proof);
|
|
20489
20952
|
grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
|
|
20490
20953
|
}
|
|
20491
|
-
await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants,
|
|
20954
|
+
await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants, options2.manifest ? "manifest" : "cli");
|
|
20492
20955
|
const delegationCids2 = grants.map(({ delegation }) => delegation.cid);
|
|
20493
20956
|
const expiry2 = grants.at(-1)?.delegation.expiry.toISOString();
|
|
20494
20957
|
reportDeclined(declined);
|
|
@@ -20498,15 +20961,15 @@ function registerAuthCommand(program) {
|
|
|
20498
20961
|
delegationCid: delegationCids2[0],
|
|
20499
20962
|
delegationCids: delegationCids2,
|
|
20500
20963
|
expiry: expiry2,
|
|
20501
|
-
...
|
|
20964
|
+
...options2.device ? { declined } : {}
|
|
20502
20965
|
});
|
|
20503
20966
|
return;
|
|
20504
20967
|
}
|
|
20505
20968
|
if (isInteractive()) {
|
|
20506
|
-
if (!
|
|
20969
|
+
if (!options2.yes) {
|
|
20507
20970
|
await confirmPermissionRequest(requested);
|
|
20508
20971
|
}
|
|
20509
|
-
} else if (!
|
|
20972
|
+
} else if (!options2.yes) {
|
|
20510
20973
|
throw new CLIError(
|
|
20511
20974
|
"CONFIRMATION_REQUIRED",
|
|
20512
20975
|
"Local-key permission requests in non-interactive mode require --yes.",
|
|
@@ -20529,7 +20992,7 @@ function registerAuthCommand(program) {
|
|
|
20529
20992
|
expiry = delegation.expiry.toISOString();
|
|
20530
20993
|
await appendGrantHistory(ctx.profile, {
|
|
20531
20994
|
addedCaps: covering,
|
|
20532
|
-
source:
|
|
20995
|
+
source: options2.manifest ? "manifest" : "cli",
|
|
20533
20996
|
delegationCid: delegation.cid,
|
|
20534
20997
|
expiry
|
|
20535
20998
|
});
|
|
@@ -20549,12 +21012,12 @@ function registerAuthCommand(program) {
|
|
|
20549
21012
|
handleError(error);
|
|
20550
21013
|
}
|
|
20551
21014
|
});
|
|
20552
|
-
auth.command("import [source]").description("Import a TinyCloud delegation or permission request artifact").option("--stdin", "Read the JSON artifact from stdin").option("--paste", "Read the JSON artifact from stdin").action(async (source,
|
|
21015
|
+
auth.command("import [source]").description("Import a TinyCloud delegation or permission request artifact").option("--stdin", "Read the JSON artifact from stdin").option("--paste", "Read the JSON artifact from stdin").action(async (source, options2, cmd) => {
|
|
20553
21016
|
try {
|
|
20554
21017
|
const globalOpts = cmd.optsWithGlobals();
|
|
20555
21018
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20556
21019
|
const raw = await readAuthArtifactSource(source, {
|
|
20557
|
-
stdin:
|
|
21020
|
+
stdin: options2.stdin === true || options2.paste === true
|
|
20558
21021
|
});
|
|
20559
21022
|
const parsed = JSON.parse(raw);
|
|
20560
21023
|
if (isCompatiblePermissionRequestArtifact(parsed)) {
|
|
@@ -20573,8 +21036,8 @@ function registerAuthCommand(program) {
|
|
|
20573
21036
|
return;
|
|
20574
21037
|
}
|
|
20575
21038
|
const imported = normalizeDelegationImport(parsed);
|
|
20576
|
-
const {
|
|
20577
|
-
const kind =
|
|
21039
|
+
const { activateUnboundImport, storedDelegationKind: storedDelegationKind2, verifiedCompactAudience, STORED_GRANT_SKIP_REASONS } = await import("@tinycloud/operations/delegation-binding");
|
|
21040
|
+
const kind = storedDelegationKind2({ delegation: imported.delegation });
|
|
20578
21041
|
if (kind === "refused") {
|
|
20579
21042
|
throw new CLIError(
|
|
20580
21043
|
"INVALID_AUTH_IMPORT",
|
|
@@ -20582,39 +21045,72 @@ function registerAuthCommand(program) {
|
|
|
20582
21045
|
ExitCode.USAGE_ERROR
|
|
20583
21046
|
);
|
|
20584
21047
|
}
|
|
21048
|
+
if (kind === "other") {
|
|
21049
|
+
throw new CLIError(
|
|
21050
|
+
"INVALID_AUTH_IMPORT",
|
|
21051
|
+
"Imported delegation must carry verifiable signed proof.",
|
|
21052
|
+
ExitCode.USAGE_ERROR
|
|
21053
|
+
);
|
|
21054
|
+
}
|
|
21055
|
+
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
21056
|
+
let bootstrap;
|
|
20585
21057
|
let node;
|
|
20586
21058
|
try {
|
|
20587
21059
|
node = await ensureAuthenticated(ctx);
|
|
20588
21060
|
} catch (error) {
|
|
20589
|
-
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
20590
21061
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
20591
21062
|
if (session || resolveProfilePosture(profile) !== "delegate-session") throw error;
|
|
20592
|
-
|
|
21063
|
+
bootstrap = await bootstrapDelegatedSession(ctx, imported.delegation);
|
|
21064
|
+
node = bootstrap.node;
|
|
20593
21065
|
}
|
|
20594
|
-
const
|
|
21066
|
+
const signedAudience = kind === "compact" ? verifiedCompactAudience(node, imported.delegation) : void 0;
|
|
21067
|
+
let transportMatchesAudience = false;
|
|
21068
|
+
if (signedAudience !== void 0) {
|
|
21069
|
+
try {
|
|
21070
|
+
transportMatchesAudience = principalDidEquals(imported.delegation.delegateDID, signedAudience);
|
|
21071
|
+
} catch {
|
|
21072
|
+
}
|
|
21073
|
+
}
|
|
21074
|
+
if (kind === "compact" && (signedAudience === void 0 || !transportMatchesAudience)) {
|
|
21075
|
+
const reason = signedAudience === void 0 ? "proof_invalid" : "audience_mismatch";
|
|
21076
|
+
const refusal = new CLIError(
|
|
21077
|
+
"DELEGATION_REJECTED",
|
|
21078
|
+
`Imported delegation was rejected (${reason}). No grant was stored.`,
|
|
21079
|
+
ExitCode.ERROR
|
|
21080
|
+
);
|
|
21081
|
+
if (bootstrap) await bootstrap.abandon(refusal);
|
|
21082
|
+
throw refusal;
|
|
21083
|
+
}
|
|
21084
|
+
const targetsSessionKey = kind === "signed-login" || signedAudience !== void 0 && principalDidEquals(signedAudience, node.sessionDid);
|
|
20595
21085
|
let activated = false;
|
|
20596
21086
|
let permissions = imported.permissions;
|
|
20597
|
-
if (targetsSessionKey
|
|
20598
|
-
|
|
20599
|
-
|
|
20600
|
-
|
|
20601
|
-
|
|
20602
|
-
|
|
20603
|
-
|
|
20604
|
-
|
|
21087
|
+
if (targetsSessionKey) {
|
|
21088
|
+
let record2;
|
|
21089
|
+
try {
|
|
21090
|
+
record2 = await activateUnboundImport(
|
|
21091
|
+
node,
|
|
21092
|
+
imported.delegation,
|
|
21093
|
+
ctx.host
|
|
21094
|
+
);
|
|
21095
|
+
} catch (error) {
|
|
21096
|
+
const candidate = error !== null && typeof error === "object" && "reason" in error ? error.reason : void 0;
|
|
21097
|
+
const reason = typeof candidate === "string" && STORED_GRANT_SKIP_REASONS.includes(candidate) ? candidate : "invalid";
|
|
21098
|
+
const refusal = new CLIError(
|
|
21099
|
+
"DELEGATION_REJECTED",
|
|
21100
|
+
`Imported delegation was rejected (${reason}). No grant was stored.`,
|
|
21101
|
+
ExitCode.ERROR
|
|
21102
|
+
);
|
|
21103
|
+
if (bootstrap) await bootstrap.abandon(refusal);
|
|
21104
|
+
throw refusal;
|
|
21105
|
+
}
|
|
21106
|
+
await appendAdditionalDelegation(ctx.profile, record2);
|
|
21107
|
+
permissions = record2.permissions;
|
|
20605
21108
|
activated = true;
|
|
20606
21109
|
} else {
|
|
20607
21110
|
await appendAdditionalDelegation(ctx.profile, storedAdditionalDelegation(
|
|
20608
21111
|
imported.delegation,
|
|
20609
21112
|
imported.permissions
|
|
20610
21113
|
));
|
|
20611
|
-
if (targetsSessionKey) {
|
|
20612
|
-
await node.useRuntimeDelegation({
|
|
20613
|
-
...imported.delegation,
|
|
20614
|
-
delegationHeader: { Authorization: imported.delegation.delegationHeader.Authorization }
|
|
20615
|
-
});
|
|
20616
|
-
activated = true;
|
|
20617
|
-
}
|
|
20618
21114
|
}
|
|
20619
21115
|
await appendGrantHistory(ctx.profile, {
|
|
20620
21116
|
addedCaps: permissions,
|
|
@@ -20635,13 +21131,13 @@ function registerAuthCommand(program) {
|
|
|
20635
21131
|
handleError(error);
|
|
20636
21132
|
}
|
|
20637
21133
|
});
|
|
20638
|
-
auth.command("grant [request]").description("Grant a TinyCloud permission request artifact to its requester").option("--stdin", "Read the JSON request artifact from stdin").option("--paste", "Read the JSON request artifact from stdin").option("--yes", "Skip local-key TTY confirmation", false).action(async (source,
|
|
21134
|
+
auth.command("grant [request]").description("Grant a TinyCloud permission request artifact to its requester").option("--stdin", "Read the JSON request artifact from stdin").option("--paste", "Read the JSON request artifact from stdin").option("--yes", "Skip local-key TTY confirmation", false).action(async (source, options2, cmd) => {
|
|
20639
21135
|
try {
|
|
20640
21136
|
const globalOpts = cmd.optsWithGlobals();
|
|
20641
21137
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20642
21138
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
20643
21139
|
const raw = await readAuthArtifactSource(source, {
|
|
20644
|
-
stdin:
|
|
21140
|
+
stdin: options2.stdin === true || options2.paste === true
|
|
20645
21141
|
});
|
|
20646
21142
|
const parsed = JSON.parse(raw);
|
|
20647
21143
|
if (!isCompatiblePermissionRequestArtifact(parsed)) {
|
|
@@ -20661,7 +21157,7 @@ function registerAuthCommand(program) {
|
|
|
20661
21157
|
requested,
|
|
20662
21158
|
expiryOption: parsed.requestedExpiry,
|
|
20663
21159
|
reason: "Grant permissions requested by a TinyCloud auth request artifact.",
|
|
20664
|
-
yes:
|
|
21160
|
+
yes: options2.yes === true
|
|
20665
21161
|
});
|
|
20666
21162
|
const grant = await grantAuthRequest(node, resolvedRequest);
|
|
20667
21163
|
await appendLocalGrantArtifact(ctx.profile, grant.delegation);
|
|
@@ -20676,21 +21172,21 @@ function registerAuthCommand(program) {
|
|
|
20676
21172
|
handleError(error);
|
|
20677
21173
|
}
|
|
20678
21174
|
});
|
|
20679
|
-
auth.command("retry [requestId]").description("Check whether a stored permission request is now satisfied").option("--last", "Use the latest stored permission request for this profile").option("--exec", "Run the captured command when the request is covered").action(async (requestId,
|
|
21175
|
+
auth.command("retry [requestId]").description("Check whether a stored permission request is now satisfied").option("--last", "Use the latest stored permission request for this profile").option("--exec", "Run the captured command when the request is covered").action(async (requestId, options2, cmd) => {
|
|
20680
21176
|
try {
|
|
20681
21177
|
const globalOpts = cmd.optsWithGlobals();
|
|
20682
21178
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20683
|
-
const artifact =
|
|
21179
|
+
const artifact = options2.last ? await getLastPermissionRequestArtifact(ctx.profile) : requestId ? await getPermissionRequestArtifact(ctx.profile, requestId) : null;
|
|
20684
21180
|
if (!artifact) {
|
|
20685
21181
|
throw new CLIError(
|
|
20686
21182
|
"REQUEST_NOT_FOUND",
|
|
20687
|
-
|
|
21183
|
+
options2.last ? `No stored permission requests exist for profile "${ctx.profile}".` : "Provide a requestId or use --last.",
|
|
20688
21184
|
ExitCode.NOT_FOUND
|
|
20689
21185
|
);
|
|
20690
21186
|
}
|
|
20691
21187
|
const node = await ensureAuthenticated(ctx);
|
|
20692
21188
|
const covered = node.hasRuntimePermissions(artifact.requested);
|
|
20693
|
-
if (
|
|
21189
|
+
if (options2.exec) {
|
|
20694
21190
|
if (!covered) {
|
|
20695
21191
|
throw new CLIError(
|
|
20696
21192
|
"PERMISSIONS_MISSING",
|
|
@@ -20719,11 +21215,11 @@ function registerAuthCommand(program) {
|
|
|
20719
21215
|
handleError(error);
|
|
20720
21216
|
}
|
|
20721
21217
|
});
|
|
20722
|
-
auth.command("caps").description("Show granted capabilities for the active session").option("--diff <spec>", "Show missing capabilities for a spec").option("--history", "Show recent permission grants").action(async (
|
|
21218
|
+
auth.command("caps").description("Show granted capabilities for the active session").option("--diff <spec>", "Show missing capabilities for a spec").option("--history", "Show recent permission grants").action(async (options2, cmd) => {
|
|
20723
21219
|
try {
|
|
20724
21220
|
const globalOpts = cmd.optsWithGlobals();
|
|
20725
21221
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
20726
|
-
if (
|
|
21222
|
+
if (options2.history) {
|
|
20727
21223
|
const history = (await readGrantHistory(ctx.profile)).slice(-20);
|
|
20728
21224
|
if (shouldOutputJson()) {
|
|
20729
21225
|
outputJson({ grants: history });
|
|
@@ -20745,8 +21241,8 @@ function registerAuthCommand(program) {
|
|
|
20745
21241
|
const node = await ensureAuthenticated(ctx);
|
|
20746
21242
|
const runtimeDelegations = node.getRuntimePermissionDelegations();
|
|
20747
21243
|
const granted = runtimeDelegations.flatMap(permissionsFromDelegation);
|
|
20748
|
-
if (
|
|
20749
|
-
const requested = [await parseCapSpec(
|
|
21244
|
+
if (options2.diff) {
|
|
21245
|
+
const requested = [await parseCapSpec(options2.diff, ctx.profile)];
|
|
20750
21246
|
const covered = node.hasRuntimePermissions(requested);
|
|
20751
21247
|
outputJson({
|
|
20752
21248
|
requested,
|
|
@@ -21032,6 +21528,13 @@ async function ensureDelegationAuthority(params) {
|
|
|
21032
21528
|
await requirePermissionConsent(params.requested, params.yes);
|
|
21033
21529
|
}
|
|
21034
21530
|
if (params.profile.authMethod === "openkey") {
|
|
21531
|
+
if (params.requested.some(isRawCidRevocationPermission)) {
|
|
21532
|
+
throw new CLIError(
|
|
21533
|
+
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
21534
|
+
"OpenKey cannot sign raw urn:cid revocation authority.",
|
|
21535
|
+
ExitCode.PERMISSION_DENIED
|
|
21536
|
+
);
|
|
21537
|
+
}
|
|
21035
21538
|
const key = await ProfileManager.getKey(params.ctx.profile);
|
|
21036
21539
|
if (!key) {
|
|
21037
21540
|
throw new CLIError(
|
|
@@ -21052,26 +21555,14 @@ async function ensureDelegationAuthority(params) {
|
|
|
21052
21555
|
const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
|
|
21053
21556
|
if (params.persist === false) {
|
|
21054
21557
|
const request = grantRequestPermissions(params.requested, anchorSpace);
|
|
21055
|
-
|
|
21056
|
-
|
|
21057
|
-
|
|
21058
|
-
|
|
21059
|
-
|
|
21060
|
-
|
|
21061
|
-
|
|
21062
|
-
|
|
21063
|
-
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
|
|
21064
|
-
});
|
|
21065
|
-
} catch (error) {
|
|
21066
|
-
if (params.requested.some(isRawCidRevocationPermission) && /invalid ReCap resource URI/i.test(error instanceof Error ? error.message : String(error))) {
|
|
21067
|
-
throw new CLIError(
|
|
21068
|
-
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
21069
|
-
"The OpenKey/WASM ReCap encoder does not support raw urn:cid revocation resources.",
|
|
21070
|
-
ExitCode.PERMISSION_DENIED
|
|
21071
|
-
);
|
|
21072
|
-
}
|
|
21073
|
-
throw error;
|
|
21074
|
-
}
|
|
21558
|
+
const delegationData = await acquireOpenKey(params.profile.did, {
|
|
21559
|
+
jwk: key,
|
|
21560
|
+
host: params.ctx.host,
|
|
21561
|
+
permissions: request,
|
|
21562
|
+
reason: permissionGrantReason(params.reason, request),
|
|
21563
|
+
openkeyHost,
|
|
21564
|
+
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
|
|
21565
|
+
});
|
|
21075
21566
|
const delegation = portableFromOpenKeyDelegation(
|
|
21076
21567
|
delegationData,
|
|
21077
21568
|
request,
|
|
@@ -21732,45 +22223,16 @@ init_profiles();
|
|
|
21732
22223
|
init_formatter();
|
|
21733
22224
|
init_errors();
|
|
21734
22225
|
init_constants();
|
|
21735
|
-
|
|
21736
|
-
function normalizeDid(input) {
|
|
22226
|
+
function normalizeDid2(input) {
|
|
21737
22227
|
const normalized = input.trim();
|
|
21738
22228
|
const fragmentIndex = normalized.indexOf("#");
|
|
21739
22229
|
return (fragmentIndex === -1 ? normalized : normalized.slice(0, fragmentIndex)).toLowerCase();
|
|
21740
22230
|
}
|
|
21741
22231
|
function didMatches(actual, expected) {
|
|
21742
22232
|
if (!actual) return false;
|
|
21743
|
-
return
|
|
22233
|
+
return normalizeDid2(actual) === normalizeDid2(expected);
|
|
21744
22234
|
}
|
|
21745
|
-
function
|
|
21746
|
-
const owner = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40})(?::|\/|$)/.exec(spaceId ?? "")?.[1];
|
|
21747
|
-
if (!owner) return void 0;
|
|
21748
|
-
return owner.startsWith("did:") ? owner : `did:${owner}`;
|
|
21749
|
-
}
|
|
21750
|
-
async function resolveCidBoundTargetSpace(params) {
|
|
21751
|
-
if (!params.ownerDid) return void 0;
|
|
21752
|
-
const grants = await loadLocalGrantArtifacts(params.profileName);
|
|
21753
|
-
for (const grant of grants) {
|
|
21754
|
-
const delegation = grant.delegation;
|
|
21755
|
-
if (grant.delegationCid !== params.cid || delegation.cid !== params.cid) continue;
|
|
21756
|
-
try {
|
|
21757
|
-
const authorization = delegation.delegationHeader.Authorization.replace(/^Bearer /i, "");
|
|
21758
|
-
const signed = parseSignedCompactUcanAttenuation(authorization, params.cid);
|
|
21759
|
-
for (const resource of Object.keys(signed.payload.att)) {
|
|
21760
|
-
const space = /^(tinycloud:[^/]+)\/[^/]+\/.*$/.exec(resource)?.[1];
|
|
21761
|
-
if (!space) continue;
|
|
21762
|
-
const ownerMatch = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):/.exec(space);
|
|
21763
|
-
const owner = ownerMatch?.[1];
|
|
21764
|
-
const ownerDid = owner?.startsWith("did:") ? owner : owner ? `did:${owner}` : void 0;
|
|
21765
|
-
if (ownerDid && normalizeDid(ownerDid) === normalizeDid(params.ownerDid)) return space;
|
|
21766
|
-
}
|
|
21767
|
-
} catch {
|
|
21768
|
-
continue;
|
|
21769
|
-
}
|
|
21770
|
-
}
|
|
21771
|
-
return void 0;
|
|
21772
|
-
}
|
|
21773
|
-
function registerDelegationCommand(program) {
|
|
22235
|
+
function registerDelegationCommand(program, configuration = {}) {
|
|
21774
22236
|
const delegation = program.command("delegation").description("Manage delegations");
|
|
21775
22237
|
delegation.command("create").description("Create a delegation").requiredOption("--to <did>", "Recipient DID").requiredOption("--path <path>", "KV path scope").requiredOption("--actions <actions>", "Comma-separated actions (e.g., kv/get,kv/list)").option("--expiry <duration>", "Expiry duration (e.g., 1h, 7d, ISO date)", "1h").action(async (options, cmd) => {
|
|
21776
22238
|
try {
|
|
@@ -21854,152 +22316,14 @@ function registerDelegationCommand(program) {
|
|
|
21854
22316
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
21855
22317
|
const node = await ensureAuthenticated(ctx);
|
|
21856
22318
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
21857
|
-
|
|
21858
|
-
|
|
21859
|
-
|
|
21860
|
-
|
|
21861
|
-
|
|
21862
|
-
|
|
21863
|
-
|
|
21864
|
-
|
|
21865
|
-
let targetFound = false;
|
|
21866
|
-
let targetSpaceSource;
|
|
21867
|
-
let queryDenied = false;
|
|
21868
|
-
let boundSpace;
|
|
21869
|
-
const accountSpaceId = node.accountSpaceId;
|
|
21870
|
-
if (!accountSpaceId) {
|
|
21871
|
-
throw new CLIError(
|
|
21872
|
-
"DELEGATION_QUERY_AUTHORITY_UNAVAILABLE",
|
|
21873
|
-
"Cannot determine the account space needed to query delegation records.",
|
|
21874
|
-
ExitCode.AUTH_REQUIRED
|
|
21875
|
-
);
|
|
21876
|
-
}
|
|
21877
|
-
let cursor;
|
|
21878
|
-
do {
|
|
21879
|
-
const query = await node.delegationManager.query({
|
|
21880
|
-
direction: "all",
|
|
21881
|
-
limit: 100,
|
|
21882
|
-
...cursor === void 0 ? {} : { cursor }
|
|
21883
|
-
});
|
|
21884
|
-
if (!query.ok) {
|
|
21885
|
-
if (query.error.meta?.status === 403) {
|
|
21886
|
-
queryDenied = true;
|
|
21887
|
-
break;
|
|
21888
|
-
}
|
|
21889
|
-
throw cliErrorFromService(query.error);
|
|
21890
|
-
}
|
|
21891
|
-
targetFound = query.data.items.some((delegation2) => delegation2.cid === cid);
|
|
21892
|
-
cursor = targetFound ? void 0 : query.data.nextCursor;
|
|
21893
|
-
} while (cursor !== void 0);
|
|
21894
|
-
targetSpaceSource = "node";
|
|
21895
|
-
if (!targetFound) {
|
|
21896
|
-
const grantHistory = await readGrantHistory(ctx.profile);
|
|
21897
|
-
const recordedGrant = [...grantHistory].reverse().find((entry) => entry.delegationCid === cid);
|
|
21898
|
-
if (recordedGrant) {
|
|
21899
|
-
targetSpaceSource = "local-grant-history";
|
|
21900
|
-
} else if (queryDenied) {
|
|
21901
|
-
boundSpace = await resolveCidBoundTargetSpace({
|
|
21902
|
-
profileName: ctx.profile,
|
|
21903
|
-
ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
|
|
21904
|
-
cid
|
|
21905
|
-
});
|
|
21906
|
-
if (boundSpace) {
|
|
21907
|
-
targetSpaceSource = "local-signed-grant-artifact";
|
|
21908
|
-
} else {
|
|
21909
|
-
throw new CLIError(
|
|
21910
|
-
"TARGET_NOT_FOUND",
|
|
21911
|
-
`Delegation "${cid}" was not found by the node or local grant sources.`,
|
|
21912
|
-
ExitCode.NOT_FOUND
|
|
21913
|
-
);
|
|
21914
|
-
}
|
|
21915
|
-
} else {
|
|
21916
|
-
throw new CLIError(
|
|
21917
|
-
"TARGET_NOT_FOUND",
|
|
21918
|
-
`Delegation "${cid}" was not found by the node or local grant history.`,
|
|
21919
|
-
ExitCode.NOT_FOUND
|
|
21920
|
-
);
|
|
21921
|
-
}
|
|
21922
|
-
}
|
|
21923
|
-
let authorityScopeSource = "cid-resource";
|
|
21924
|
-
let authorityScopeReason = "The revoke authority is scoped to the exact delegation CID.";
|
|
21925
|
-
let fallbackSpaceId;
|
|
21926
|
-
let commandAuthorityCid;
|
|
21927
|
-
const authorizeFallbackSpace = async (spaceId) => {
|
|
21928
|
-
fallbackSpaceId = spaceId;
|
|
21929
|
-
authorityScopeSource = "local-signed-grant-artifact";
|
|
21930
|
-
authorityScopeReason = "Raw-CID revoke authority could not be issued; the fallback space came from an owner-signed local grant whose Authorization recomputes to this CID.";
|
|
21931
|
-
targetSpaceSource = "local-signed-grant-artifact";
|
|
21932
|
-
const acquired = await ensureDelegationAuthority({
|
|
21933
|
-
ctx,
|
|
21934
|
-
profile,
|
|
21935
|
-
node,
|
|
21936
|
-
requested: [{
|
|
21937
|
-
service: "tinycloud.delegation",
|
|
21938
|
-
space: spaceId === node.accountSpaceId ? "default" : spaceId,
|
|
21939
|
-
path: "",
|
|
21940
|
-
actions: ["tinycloud.delegation/revoke"]
|
|
21941
|
-
}],
|
|
21942
|
-
expiryOption: void 0,
|
|
21943
|
-
reason: `Revoke delegation ${cid} using its CID-bound owner-signed grant artifact`,
|
|
21944
|
-
yes: options.yes,
|
|
21945
|
-
consentAlreadyGiven: true,
|
|
21946
|
-
persist: false
|
|
21947
|
-
});
|
|
21948
|
-
commandAuthorityCid = acquired?.cid;
|
|
21949
|
-
};
|
|
21950
|
-
if (!rawAuthorityCovered) {
|
|
21951
|
-
try {
|
|
21952
|
-
const acquired = await ensureDelegationAuthority({
|
|
21953
|
-
ctx,
|
|
21954
|
-
profile,
|
|
21955
|
-
node,
|
|
21956
|
-
requested: [revokePermission],
|
|
21957
|
-
expiryOption: void 0,
|
|
21958
|
-
reason: `Revoke delegation ${cid}`,
|
|
21959
|
-
yes: options.yes,
|
|
21960
|
-
consentAlreadyGiven: true,
|
|
21961
|
-
persist: false
|
|
21962
|
-
});
|
|
21963
|
-
commandAuthorityCid = acquired?.cid;
|
|
21964
|
-
} catch (error) {
|
|
21965
|
-
if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "RAW_RECAP_RESOURCE_UNSUPPORTED") {
|
|
21966
|
-
throw error;
|
|
21967
|
-
}
|
|
21968
|
-
const fallbackSpace = boundSpace ?? await resolveCidBoundTargetSpace({
|
|
21969
|
-
profileName: ctx.profile,
|
|
21970
|
-
ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
|
|
21971
|
-
cid
|
|
21972
|
-
});
|
|
21973
|
-
if (!fallbackSpace) {
|
|
21974
|
-
throw new CLIError(
|
|
21975
|
-
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
21976
|
-
`Cannot safely revoke "${cid}": raw CID ReCap resources are unavailable and no matching owner-signed local grant artifact binds its space to the CID.`,
|
|
21977
|
-
ExitCode.PERMISSION_DENIED
|
|
21978
|
-
);
|
|
21979
|
-
}
|
|
21980
|
-
await authorizeFallbackSpace(fallbackSpace);
|
|
21981
|
-
}
|
|
21982
|
-
}
|
|
21983
|
-
const result = fallbackSpaceId ? await node.delegationManager.revoke(cid, {
|
|
21984
|
-
targetSpaceId: fallbackSpaceId,
|
|
21985
|
-
...commandAuthorityCid === void 0 ? {} : { authorityCid: commandAuthorityCid }
|
|
21986
|
-
}) : commandAuthorityCid === void 0 ? await node.revokeDelegation(cid) : await node.delegationManager.revoke(cid, { authorityCid: commandAuthorityCid });
|
|
21987
|
-
if (!result.ok) {
|
|
21988
|
-
if (result.error.meta?.status === 403 && result.error.message === "Failed to revoke delegation: 403 - Unauthorized Revoker") {
|
|
21989
|
-
const profileDid = profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId) ?? "unknown";
|
|
21990
|
-
throw new CLIError(
|
|
21991
|
-
"REVOKE_UNAUTHORIZED",
|
|
21992
|
-
"The node rejected this revocation: Unauthorized Revoker.",
|
|
21993
|
-
ExitCode.PERMISSION_DENIED,
|
|
21994
|
-
{
|
|
21995
|
-
status: 403,
|
|
21996
|
-
hint: `Use a different profile belonging to the delegation's grantor, its recipient, or the owner of a space it covers; the rejected profile was "${ctx.profile}" (DID ${profileDid}). Retry with \`tc --profile <profile-name> delegation revoke ${cid}\`.`
|
|
21997
|
-
}
|
|
21998
|
-
);
|
|
21999
|
-
}
|
|
22000
|
-
throw cliErrorFromService(result.error);
|
|
22001
|
-
}
|
|
22002
|
-
outputJson({ cid, revoked: true, targetSpaceSource, authorityScopeSource, authorityScopeReason });
|
|
22319
|
+
outputJson(await revokeDelegationOnNode({
|
|
22320
|
+
ctx,
|
|
22321
|
+
node,
|
|
22322
|
+
profile,
|
|
22323
|
+
cid,
|
|
22324
|
+
yes: options.yes,
|
|
22325
|
+
openKeyAcquisition: configuration.openKeyAcquisition
|
|
22326
|
+
}));
|
|
22003
22327
|
} catch (error) {
|
|
22004
22328
|
handleError(error);
|
|
22005
22329
|
}
|
|
@@ -23206,8 +23530,8 @@ async function storedGrants(profile) {
|
|
|
23206
23530
|
const records = await readAdditionalDelegations2(profile).catch(
|
|
23207
23531
|
() => []
|
|
23208
23532
|
);
|
|
23209
|
-
for (const
|
|
23210
|
-
const authorization =
|
|
23533
|
+
for (const record2 of records) {
|
|
23534
|
+
const authorization = record2.delegation?.delegationHeader?.Authorization;
|
|
23211
23535
|
if (typeof authorization === "string") authorizations.push(authorization);
|
|
23212
23536
|
}
|
|
23213
23537
|
const grants = [];
|
|
@@ -23572,6 +23896,7 @@ init_profiles();
|
|
|
23572
23896
|
init_formatter();
|
|
23573
23897
|
init_theme();
|
|
23574
23898
|
init_errors();
|
|
23899
|
+
init_warnings();
|
|
23575
23900
|
init_constants();
|
|
23576
23901
|
import { readFile as readFile9 } from "fs/promises";
|
|
23577
23902
|
import { join as join9 } from "path";
|
|
@@ -23762,16 +24087,16 @@ function withOperationWarnings(error, warnings) {
|
|
|
23762
24087
|
return cliError;
|
|
23763
24088
|
}
|
|
23764
24089
|
async function invokeCanonicalSecretGet(params) {
|
|
23765
|
-
|
|
24090
|
+
let warnings = [];
|
|
23766
24091
|
const collect = (result) => {
|
|
23767
|
-
|
|
24092
|
+
warnings = mergeWarnings(warnings, result.warnings ?? []);
|
|
23768
24093
|
return result;
|
|
23769
24094
|
};
|
|
23770
24095
|
try {
|
|
23771
24096
|
const result = await canonicalSecretGet(params, collect);
|
|
23772
|
-
return warnings.
|
|
24097
|
+
return warnings.length === 0 ? result : { ...result, warnings };
|
|
23773
24098
|
} catch (error) {
|
|
23774
|
-
throw withOperationWarnings(error,
|
|
24099
|
+
throw withOperationWarnings(error, warnings);
|
|
23775
24100
|
}
|
|
23776
24101
|
}
|
|
23777
24102
|
async function canonicalSecretGet(params, collect) {
|
|
@@ -23889,9 +24214,9 @@ function shouldRequestSecretPermissions(error) {
|
|
|
23889
24214
|
return /permission|session expired|autosign|capabilit/i.test(error.message);
|
|
23890
24215
|
}
|
|
23891
24216
|
function thrownPermissionError(error) {
|
|
23892
|
-
const
|
|
23893
|
-
const message = typeof
|
|
23894
|
-
const code3 = typeof
|
|
24217
|
+
const record2 = error;
|
|
24218
|
+
const message = typeof record2?.message === "string" ? record2.message : String(error);
|
|
24219
|
+
const code3 = typeof record2?.code === "string" ? record2.code : "PERMISSION_DENIED";
|
|
23895
24220
|
if (code3 !== "PERMISSION_DENIED" && !/permission|session expired|autosign|capabilit/i.test(message)) {
|
|
23896
24221
|
return null;
|
|
23897
24222
|
}
|
|
@@ -24205,11 +24530,11 @@ async function readDelegatedSecretValue(params) {
|
|
|
24205
24530
|
return parseDecryptedSecretPayload(decrypted.data, params.secretPath);
|
|
24206
24531
|
}
|
|
24207
24532
|
function isStoredSessionExpired(session) {
|
|
24208
|
-
const
|
|
24209
|
-
const direct = parseDate(
|
|
24533
|
+
const record2 = session;
|
|
24534
|
+
const direct = parseDate(record2.expiresAt ?? record2.expiry ?? record2.expirationTime);
|
|
24210
24535
|
if (direct) return direct.getTime() <= Date.now();
|
|
24211
|
-
if (typeof
|
|
24212
|
-
const match =
|
|
24536
|
+
if (typeof record2.siwe !== "string") return false;
|
|
24537
|
+
const match = record2.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
24213
24538
|
const expiry = match ? parseDate(match[1].trim()) : null;
|
|
24214
24539
|
return expiry !== null && expiry.getTime() <= Date.now();
|
|
24215
24540
|
}
|
|
@@ -29202,7 +29527,7 @@ function baseCache(cid2) {
|
|
|
29202
29527
|
}
|
|
29203
29528
|
return baseCache22;
|
|
29204
29529
|
}
|
|
29205
|
-
var
|
|
29530
|
+
var CID2 = class _CID {
|
|
29206
29531
|
code;
|
|
29207
29532
|
version;
|
|
29208
29533
|
multihash;
|
|
@@ -29521,12 +29846,12 @@ var code = 85;
|
|
|
29521
29846
|
var SHA256_CODE = 18;
|
|
29522
29847
|
async function computeCid(bytes3) {
|
|
29523
29848
|
const digest3 = create(SHA256_CODE, sha256(new Uint8Array(bytes3)));
|
|
29524
|
-
return
|
|
29849
|
+
return CID2.create(1, code, digest3).toString();
|
|
29525
29850
|
}
|
|
29526
29851
|
function isCanonicalRawCid(cidString) {
|
|
29527
29852
|
let cid2;
|
|
29528
29853
|
try {
|
|
29529
|
-
cid2 =
|
|
29854
|
+
cid2 = CID2.parse(cidString);
|
|
29530
29855
|
} catch {
|
|
29531
29856
|
return false;
|
|
29532
29857
|
}
|
|
@@ -30111,7 +30436,7 @@ function verifyCompactUcanAuthorization(authorization, expectedCid) {
|
|
|
30111
30436
|
const publicKey = ed25519PublicKeyFromDidKey(principal);
|
|
30112
30437
|
if (!equal(publicKey, fromBase64Url(jwk.x))) throw new TypeError("compact Authorization JWK does not bind issuer");
|
|
30113
30438
|
if (!ed25519.verify(signature, new TextEncoder().encode(`${headerSegment}.${payloadSegment}`), publicKey, { zip215: false })) throw new TypeError("compact Authorization signature is invalid");
|
|
30114
|
-
const cid2 =
|
|
30439
|
+
const cid2 = CID2.createV1(85, create(30, blake3(new TextEncoder().encode(authorization)))).toString();
|
|
30115
30440
|
if (expectedCid !== void 0 && cid2 !== expectedCid) throw new TypeError("compact Authorization CID mismatch");
|
|
30116
30441
|
return { authorization, cid: cid2, header, payload };
|
|
30117
30442
|
}
|
|
@@ -30230,7 +30555,7 @@ function base32Lower(bytes3) {
|
|
|
30230
30555
|
var KEY_LENGTH2 = 32;
|
|
30231
30556
|
var MAX_INLINE_BYTES = 256 * 1024;
|
|
30232
30557
|
function assertCanonicalCid(cidString) {
|
|
30233
|
-
const cid2 =
|
|
30558
|
+
const cid2 = CID2.parse(cidString);
|
|
30234
30559
|
if (cid2.version !== 1 || cid2.code !== code || cid2.multihash.code !== 18 || // sha2-256 (0x12) only, at the link layer too
|
|
30235
30560
|
cid2.toString() !== cidString) {
|
|
30236
30561
|
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
@@ -30261,21 +30586,21 @@ async function parseSealedInlineShareUrl(url, options = {}) {
|
|
|
30261
30586
|
throw new TypeError("sealed inline payload is not valid JSON");
|
|
30262
30587
|
}
|
|
30263
30588
|
if (typeof value !== "object" || value === null || Array.isArray(value) || canonicalize2(value) !== payloadText) throw new TypeError("sealed inline payload is not canonical JSON");
|
|
30264
|
-
const
|
|
30265
|
-
if (Object.keys(
|
|
30589
|
+
const record2 = value;
|
|
30590
|
+
if (Object.keys(record2).length !== 4 || record2.v !== 2 || typeof record2.c !== "string" || typeof record2.cid !== "string" || typeof record2.k !== "string") throw new TypeError("sealed inline payload has invalid fields");
|
|
30266
30591
|
let ciphertext;
|
|
30267
30592
|
let key32;
|
|
30268
30593
|
try {
|
|
30269
|
-
ciphertext = fromBase64Url(
|
|
30270
|
-
key32 = fromBase64Url(
|
|
30594
|
+
ciphertext = fromBase64Url(record2.c);
|
|
30595
|
+
key32 = fromBase64Url(record2.k);
|
|
30271
30596
|
} catch {
|
|
30272
30597
|
throw new TypeError("sealed inline payload has invalid base64url material");
|
|
30273
30598
|
}
|
|
30274
30599
|
if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("sealed inline ciphertext is outside the allowed size");
|
|
30275
30600
|
if (key32.byteLength !== KEY_LENGTH2) throw new TypeError("sealed inline key must be 32 bytes");
|
|
30276
|
-
assertCanonicalCid(
|
|
30277
|
-
if (await computeCid(ciphertext) !==
|
|
30278
|
-
return { kind: "inline", ciphertextCid:
|
|
30601
|
+
assertCanonicalCid(record2.cid);
|
|
30602
|
+
if (await computeCid(ciphertext) !== record2.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
|
|
30603
|
+
return { kind: "inline", ciphertextCid: record2.cid, ciphertext, key32 };
|
|
30279
30604
|
}
|
|
30280
30605
|
var SHARE_RESULT_VERSION = 1;
|
|
30281
30606
|
var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
|
|
@@ -31040,7 +31365,7 @@ function baseCache2(cid2) {
|
|
|
31040
31365
|
}
|
|
31041
31366
|
return baseCache32;
|
|
31042
31367
|
}
|
|
31043
|
-
var
|
|
31368
|
+
var CID22 = class _CID2 {
|
|
31044
31369
|
code;
|
|
31045
31370
|
version;
|
|
31046
31371
|
multihash;
|
|
@@ -31398,16 +31723,16 @@ var ShareNotifyError = class extends Error {
|
|
|
31398
31723
|
}
|
|
31399
31724
|
code = "delivery-failed";
|
|
31400
31725
|
};
|
|
31401
|
-
function recipientMatchesShareRecord(
|
|
31402
|
-
const matcher =
|
|
31726
|
+
function recipientMatchesShareRecord(record2, recipient) {
|
|
31727
|
+
const matcher = record2.recipientMatcher;
|
|
31403
31728
|
const mailbox = canonicalMailbox(recipient);
|
|
31404
31729
|
if (mailbox === void 0) return false;
|
|
31405
31730
|
if (matcher.kind === "exactEmail") return canonicalMailbox(matcher.value)?.email === mailbox.email;
|
|
31406
31731
|
if (matcher.kind === "emailDomain") return mailbox.domain === matcher.value;
|
|
31407
31732
|
return false;
|
|
31408
31733
|
}
|
|
31409
|
-
function shareDeliveryWindowExpiresAt(
|
|
31410
|
-
const expiresAt = Math.min(Date.parse(
|
|
31734
|
+
function shareDeliveryWindowExpiresAt(record2) {
|
|
31735
|
+
const expiresAt = Math.min(Date.parse(record2.expiresAt), Date.parse(record2.registeredAt) + 5 * 60 * 1e3);
|
|
31411
31736
|
if (!Number.isFinite(expiresAt)) throw new ShareNotifyError("share delivery history has invalid timestamps");
|
|
31412
31737
|
return expiresAt;
|
|
31413
31738
|
}
|
|
@@ -31484,17 +31809,17 @@ async function defaultIdempotencyKey(shareId, recipient) {
|
|
|
31484
31809
|
));
|
|
31485
31810
|
return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
|
|
31486
31811
|
}
|
|
31487
|
-
function targetKind2(
|
|
31488
|
-
if (
|
|
31489
|
-
return
|
|
31812
|
+
function targetKind2(record2) {
|
|
31813
|
+
if (record2.targetKind !== void 0) return record2.targetKind;
|
|
31814
|
+
return record2.recipientMatcher.kind === "exactEmail" ? "email" : record2.recipientMatcher.kind === "emailDomain" ? "emailDomain" : record2.recipientMatcher.kind === "recipientDid" ? "recipientDid" : "bearer";
|
|
31490
31815
|
}
|
|
31491
|
-
function policyNodeAudience(
|
|
31492
|
-
const envelope =
|
|
31493
|
-
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return
|
|
31816
|
+
function policyNodeAudience(record2) {
|
|
31817
|
+
const envelope = record2.deliveryMaterial?.envelope;
|
|
31818
|
+
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return record2.target.nodeAudience;
|
|
31494
31819
|
const binding = envelope.attestedEnforcerBinding;
|
|
31495
|
-
if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return
|
|
31820
|
+
if (typeof binding !== "object" || binding === null || Array.isArray(binding)) return record2.target.nodeAudience;
|
|
31496
31821
|
const nodeAudience = binding.nodeAudience;
|
|
31497
|
-
return typeof nodeAudience === "string" ? nodeAudience :
|
|
31822
|
+
return typeof nodeAudience === "string" ? nodeAudience : record2.target.nodeAudience;
|
|
31498
31823
|
}
|
|
31499
31824
|
async function revokeShare(input) {
|
|
31500
31825
|
const target = targetKind2(input.record);
|
|
@@ -31518,32 +31843,32 @@ async function revokeShare(input) {
|
|
|
31518
31843
|
}
|
|
31519
31844
|
const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
31520
31845
|
if (input.records?.update !== void 0) {
|
|
31521
|
-
await input.records.update(input.record.shareId, (
|
|
31846
|
+
await input.records.update(input.record.shareId, (record2) => ({ ...record2, revokedAt }));
|
|
31522
31847
|
} else if (input.records !== void 0) {
|
|
31523
31848
|
await input.records.put({ ...input.record, revokedAt });
|
|
31524
31849
|
}
|
|
31525
31850
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
31526
31851
|
}
|
|
31527
|
-
function redactRecord(
|
|
31528
|
-
const matcher =
|
|
31529
|
-
const revealedLink = revealLink ? link3 ??
|
|
31852
|
+
function redactRecord(record2, revealLink, link3) {
|
|
31853
|
+
const matcher = record2.recipientMatcher;
|
|
31854
|
+
const revealedLink = revealLink ? link3 ?? record2.link : void 0;
|
|
31530
31855
|
return {
|
|
31531
|
-
shareId:
|
|
31856
|
+
shareId: record2.shareId,
|
|
31532
31857
|
target: matcher.kind === "exactEmail" ? "email" : matcher.kind === "emailDomain" ? "email-domain" : matcher.kind === "recipientDid" ? "recipient-did" : "bearer",
|
|
31533
31858
|
...matcher.kind === "exactEmail" ? { recipient: matcher.value } : matcher.kind === "emailDomain" ? { recipient: `*@${matcher.value}` } : matcher.kind === "recipientDid" ? { recipient: matcher.value } : {},
|
|
31534
|
-
expiresAt:
|
|
31535
|
-
revoked:
|
|
31859
|
+
expiresAt: record2.expiresAt,
|
|
31860
|
+
revoked: record2.revokedAt !== void 0,
|
|
31536
31861
|
...revealedLink === void 0 ? {} : { link: revealedLink }
|
|
31537
31862
|
};
|
|
31538
31863
|
}
|
|
31539
31864
|
async function listShares(storage) {
|
|
31540
31865
|
const records = await storage.list();
|
|
31541
|
-
return records.map((
|
|
31866
|
+
return records.map((record2) => redactRecord(record2, false));
|
|
31542
31867
|
}
|
|
31543
31868
|
async function showShare(input) {
|
|
31544
|
-
const
|
|
31545
|
-
if (
|
|
31546
|
-
return redactRecord(
|
|
31869
|
+
const record2 = await input.storage.get(input.shareId);
|
|
31870
|
+
if (record2 === void 0) throw new Error("share not found");
|
|
31871
|
+
return redactRecord(record2, input.revealLink === true, input.link);
|
|
31547
31872
|
}
|
|
31548
31873
|
var SHARE_V2_PROTOCOL = Object.freeze({
|
|
31549
31874
|
challengeDomain: "xyz.tinycloud.share/policy-challenge/v2\0",
|
|
@@ -33202,24 +33527,24 @@ function expires(value) {
|
|
|
33202
33527
|
}
|
|
33203
33528
|
}
|
|
33204
33529
|
async function rememberPublishedShare(result) {
|
|
33205
|
-
const
|
|
33206
|
-
if (shareServices.records === void 0) return { record, recorded: false };
|
|
33530
|
+
const record2 = historyRecordForPublishedShare(result);
|
|
33531
|
+
if (shareServices.records === void 0) return { record: record2, recorded: false };
|
|
33207
33532
|
try {
|
|
33208
|
-
await shareServices.records.put(
|
|
33209
|
-
return { record, recorded: true };
|
|
33533
|
+
await shareServices.records.put(record2);
|
|
33534
|
+
return { record: record2, recorded: true };
|
|
33210
33535
|
} catch {
|
|
33211
33536
|
process.stderr.write("Warning: share was published but not recorded in sender history; future share list, share notify, and share revoke by ID will not find it.\n");
|
|
33212
|
-
return { record, recorded: false };
|
|
33537
|
+
return { record: record2, recorded: false };
|
|
33213
33538
|
}
|
|
33214
33539
|
}
|
|
33215
|
-
async function notifyRecordedShare(
|
|
33216
|
-
const result = await notifyShare({ shareId:
|
|
33540
|
+
async function notifyRecordedShare(record2, recipient, adapter, storage) {
|
|
33541
|
+
const result = await notifyShare({ shareId: record2.shareId, recipient, record: record2, adapter, checkDeliveryWindow: true });
|
|
33217
33542
|
if (result.state !== "partial-failure" && storage !== void 0) {
|
|
33218
33543
|
const mailbox = canonicalMailbox2(recipient).email;
|
|
33219
|
-
if (!
|
|
33544
|
+
if (!record2.deliveredRecipients?.includes(mailbox)) {
|
|
33220
33545
|
try {
|
|
33221
33546
|
if (storage.update === void 0) throw new Error("atomic sender history update is unavailable");
|
|
33222
|
-
const updated = await storage.update(
|
|
33547
|
+
const updated = await storage.update(record2.shareId, (current) => ({
|
|
33223
33548
|
...current,
|
|
33224
33549
|
deliveredRecipients: current.deliveredRecipients?.includes(mailbox) ? current.deliveredRecipients : [...current.deliveredRecipients ?? [], mailbox]
|
|
33225
33550
|
}));
|
|
@@ -33309,11 +33634,11 @@ function registerShareCommand(program) {
|
|
|
33309
33634
|
}
|
|
33310
33635
|
throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
|
|
33311
33636
|
}
|
|
33312
|
-
const { record, recorded } = await rememberPublishedShare(result);
|
|
33637
|
+
const { record: record2, recorded } = await rememberPublishedShare(result);
|
|
33313
33638
|
let notification;
|
|
33314
33639
|
if (notifyRecipient !== void 0) {
|
|
33315
33640
|
if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
|
|
33316
|
-
notification = await notifyRecordedShare(
|
|
33641
|
+
notification = await notifyRecordedShare(record2, notifyRecipient, shareServices.delivery, recorded ? shareServices.records : void 0);
|
|
33317
33642
|
if (notification.state === "partial-failure") process.exitCode = 9;
|
|
33318
33643
|
if (notification.reason === "delivery-window-expired") process.stderr.write(NOTIFY_WINDOW_MESSAGE);
|
|
33319
33644
|
}
|
|
@@ -33411,19 +33736,19 @@ function registerShareCommand(program) {
|
|
|
33411
33736
|
const json = jsonOutput(options, command);
|
|
33412
33737
|
if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
|
|
33413
33738
|
if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
|
|
33414
|
-
const
|
|
33415
|
-
if (
|
|
33416
|
-
if (!
|
|
33739
|
+
const record2 = await shareServices.records.get(id);
|
|
33740
|
+
if (record2 === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
|
|
33741
|
+
if (!record2.actions.includes("tinycloud.kv/get")) {
|
|
33417
33742
|
throw new CLIError("INVALID_ARGUMENT", "share notify requires a stored share with the read action; no invitation was sent", 2);
|
|
33418
33743
|
}
|
|
33419
33744
|
const mailbox = canonicalMailbox2(options.to);
|
|
33420
33745
|
if (mailbox === void 0) throw new ShareNotifyError("recipient is invalid");
|
|
33421
|
-
if (!recipientMatchesShareRecord(
|
|
33422
|
-
if (
|
|
33746
|
+
if (!recipientMatchesShareRecord(record2, mailbox.email)) throw new ShareNotifyError("recipient does not match the stored share target");
|
|
33747
|
+
if (record2.recipientMatcher.kind === "emailDomain" && shareDeliveryWindowExpiresAt(record2) > Date.now()) {
|
|
33423
33748
|
if (shareServices.assertDomainDelivery === void 0) throw new CLIError("UNAVAILABLE", "could not verify TinyCloud node 1.17.3 domain delivery support; no invitation was sent", 4);
|
|
33424
33749
|
await shareServices.assertDomainDelivery();
|
|
33425
33750
|
}
|
|
33426
|
-
const result = await notifyRecordedShare(
|
|
33751
|
+
const result = await notifyRecordedShare(record2, options.to, shareServices.delivery, shareServices.records);
|
|
33427
33752
|
if (json) writeJson2(result);
|
|
33428
33753
|
else process.stdout.write(`${result.state}
|
|
33429
33754
|
`);
|
|
@@ -33437,9 +33762,9 @@ function registerShareCommand(program) {
|
|
|
33437
33762
|
try {
|
|
33438
33763
|
const json = jsonOutput(options, command);
|
|
33439
33764
|
if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
|
|
33440
|
-
const
|
|
33441
|
-
if (
|
|
33442
|
-
const result = await revokeShare({ record, records: shareServices.records, adapter: shareServices.revocation, scope: options.ancestor ? "ancestor" : "direct" });
|
|
33765
|
+
const record2 = shareServices.getRecord ? await shareServices.getRecord(id) : await shareServices.records.get(id);
|
|
33766
|
+
if (record2 === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
|
|
33767
|
+
const result = await revokeShare({ record: record2, records: shareServices.records, adapter: shareServices.revocation, scope: options.ancestor ? "ancestor" : "direct" });
|
|
33443
33768
|
if (result.state === "unsupported") {
|
|
33444
33769
|
throw new CLIError("UNSUPPORTED_TARGET", result.reason, 2);
|
|
33445
33770
|
}
|
|
@@ -34009,8 +34334,8 @@ async function readHasKey(name, issues) {
|
|
|
34009
34334
|
async function readDelegations(name, issues) {
|
|
34010
34335
|
try {
|
|
34011
34336
|
const entries = await loadAdditionalDelegations(name);
|
|
34012
|
-
const { storedDelegationKind } = await import("@tinycloud/operations/delegation-binding");
|
|
34013
|
-
const readable = entries.filter((entry) =>
|
|
34337
|
+
const { storedDelegationKind: storedDelegationKind2 } = await import("@tinycloud/operations/delegation-binding");
|
|
34338
|
+
const readable = entries.filter((entry) => storedDelegationKind2(entry) !== "refused");
|
|
34014
34339
|
if (readable.length < entries.length) {
|
|
34015
34340
|
issues.push(`delegations: ${entries.length - readable.length} unreadable stored record(s) skipped`);
|
|
34016
34341
|
}
|
|
@@ -34053,12 +34378,12 @@ function sessionPermissionsFromRecap(session) {
|
|
|
34053
34378
|
}
|
|
34054
34379
|
}
|
|
34055
34380
|
function permissionFromRawRecap(value) {
|
|
34056
|
-
const
|
|
34057
|
-
if (!
|
|
34058
|
-
const service = stringValue(
|
|
34059
|
-
const space = stringValue(
|
|
34060
|
-
const path = stringValue(
|
|
34061
|
-
const actions = Array.isArray(
|
|
34381
|
+
const record2 = asRecord(value);
|
|
34382
|
+
if (!record2) return null;
|
|
34383
|
+
const service = stringValue(record2.service);
|
|
34384
|
+
const space = stringValue(record2.space);
|
|
34385
|
+
const path = stringValue(record2.path);
|
|
34386
|
+
const actions = Array.isArray(record2.actions) ? record2.actions.map(String).filter(Boolean) : [];
|
|
34062
34387
|
if (!service || !space || path === null || actions.length === 0) return null;
|
|
34063
34388
|
return {
|
|
34064
34389
|
service: normalizeService2(service),
|
|
@@ -34076,12 +34401,12 @@ function normalizePermissions(entries) {
|
|
|
34076
34401
|
return uniquePermissions(permissions);
|
|
34077
34402
|
}
|
|
34078
34403
|
function permissionFromUnknown(value) {
|
|
34079
|
-
const
|
|
34080
|
-
if (!
|
|
34081
|
-
const service = stringValue(
|
|
34082
|
-
const space = stringValue(
|
|
34083
|
-
const path = stringValue(
|
|
34084
|
-
const actions = Array.isArray(
|
|
34404
|
+
const record2 = asRecord(value);
|
|
34405
|
+
if (!record2) return null;
|
|
34406
|
+
const service = stringValue(record2.service);
|
|
34407
|
+
const space = stringValue(record2.space);
|
|
34408
|
+
const path = stringValue(record2.path);
|
|
34409
|
+
const actions = Array.isArray(record2.actions) ? record2.actions.map(String).filter(Boolean) : [];
|
|
34085
34410
|
if (!service || !space || path === null || actions.length === 0) return null;
|
|
34086
34411
|
return {
|
|
34087
34412
|
service: normalizeService2(service),
|