@tinycloud/cli 1.3.0-beta.52 → 1.3.0-beta.53
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/legacy-entry.js +136 -36
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +1 -1
- package/skills/tc-cli/AUTH.md +2 -0
- package/skills/tc-cli/SKILL.md +2 -1
package/dist/legacy-entry.js
CHANGED
|
@@ -29616,6 +29616,9 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
|
29616
29616
|
if (!scopeCovers(requested, approved, signed.ownerDid)) {
|
|
29617
29617
|
throw new CLIError("OPENKEY_GRANT_BROADENED", `The signed grant contains authority beyond the requested ${expected.purpose === "grant" ? "grant" : "manifest"}. ${notStored}`, ExitCode.PERMISSION_DENIED);
|
|
29618
29618
|
}
|
|
29619
|
+
if (expected.exactApproval && !scopeCovers(approved, requested, signed.ownerDid)) {
|
|
29620
|
+
throw new CLIError("OPENKEY_APPROVAL_NARROWER", `The approval was narrower than the request. Approve all of it, or ask the agent to request less. ${notStored}`, ExitCode.PERMISSION_DENIED);
|
|
29621
|
+
}
|
|
29619
29622
|
const session = withVerifiedAuthority({ ...data, jwk: key }, { ...signed, permissions: approved });
|
|
29620
29623
|
return { session, legacyNested };
|
|
29621
29624
|
}
|
|
@@ -31384,7 +31387,7 @@ function withinPresetSet(entry, applicationsSpace) {
|
|
|
31384
31387
|
const grants = Object.values(APPS_AGENT_VARIANTS).flatMap(({ grants: variantGrants }) => variantGrants.filter((grant) => grant.service === entry.service));
|
|
31385
31388
|
return entry.actions.length > 0 && entry.actions.every((action) => grants.some((grant) => grant.actions.includes(action) && (!(action.endsWith("/put") || action.endsWith("/write")) || folder !== void 0 && grant.within.some((within) => folder.startsWith(within)))));
|
|
31386
31389
|
}
|
|
31387
|
-
function describeGrantScope(requested, requestedExpiry, applicationsSpace) {
|
|
31390
|
+
function describeGrantScope(requested, requestedExpiry, applicationsSpace, maxLifetimeMs) {
|
|
31388
31391
|
const preset = matchAppsAgentRequest(requested, requestedExpiry, applicationsSpace);
|
|
31389
31392
|
if (preset !== void 0) {
|
|
31390
31393
|
return { text: appsAgentScopeNotice(preset.mode, preset.appIds), entries: requested, extras: [] };
|
|
@@ -31401,7 +31404,7 @@ function describeGrantScope(requested, requestedExpiry, applicationsSpace) {
|
|
|
31401
31404
|
lines.push("Because of how TinyCloud permissions work today (TC-1581), SQL write access can also change or delete tables (structure).");
|
|
31402
31405
|
}
|
|
31403
31406
|
const entryLifetimes = requested.filter((entry) => entry.expiry !== void 0).map((entry) => lifetimeMs(entry.expiry));
|
|
31404
|
-
const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0);
|
|
31407
|
+
const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0).map((ms2) => Math.min(ms2, maxLifetimeMs ?? Number.POSITIVE_INFINITY));
|
|
31405
31408
|
const longest = Math.max(...durations);
|
|
31406
31409
|
lines.push(
|
|
31407
31410
|
durations.length === 0 || !Number.isFinite(longest) ? "Its lifetime could not be read from the request." : `It lasts up to ${lifetimePhrase(longest)}${requestedExpiry === void 0 ? " (no lifetime was requested; this is the default)" : ""}; revoke it any time with \`${CLI_NAME16} delegation revoke <id>\`.`
|
|
@@ -32511,7 +32514,12 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
32511
32514
|
[{ service: "tinycloud.kv", space: APPS_AGENT_SPACE, path: "", actions: ["tinycloud.kv/get"] }],
|
|
32512
32515
|
ctx.profile
|
|
32513
32516
|
);
|
|
32514
|
-
const scope = describeGrantScope(
|
|
32517
|
+
const scope = describeGrantScope(
|
|
32518
|
+
requested,
|
|
32519
|
+
parsed.requestedExpiry,
|
|
32520
|
+
applications.space ?? APPS_AGENT_SPACE,
|
|
32521
|
+
profile.authMethod === "openkey" ? OPENKEY_AGENT_MAX_LIFETIME_MS : void 0
|
|
32522
|
+
);
|
|
32515
32523
|
process.stderr.write(`
|
|
32516
32524
|
${scope.text}
|
|
32517
32525
|
|
|
@@ -32530,16 +32538,43 @@ ${scope.text}
|
|
|
32530
32538
|
}
|
|
32531
32539
|
const resolvedRequest = { ...parsed, requested };
|
|
32532
32540
|
const node = await ensureAuthenticated(ctx);
|
|
32533
|
-
|
|
32534
|
-
|
|
32535
|
-
|
|
32536
|
-
|
|
32537
|
-
|
|
32538
|
-
|
|
32539
|
-
|
|
32540
|
-
|
|
32541
|
-
|
|
32542
|
-
|
|
32541
|
+
let grant;
|
|
32542
|
+
if (profile.authMethod === "openkey") {
|
|
32543
|
+
assertOpenKeyAgentRequest(requested);
|
|
32544
|
+
const approval = await acquireOpenKeyApproval({
|
|
32545
|
+
ctx,
|
|
32546
|
+
profile,
|
|
32547
|
+
requested,
|
|
32548
|
+
expiryOption: Math.min(
|
|
32549
|
+
parseRequestedExpiry(parsed.requestedExpiry ?? APPS_AGENT_EXPIRY).durationMs,
|
|
32550
|
+
OPENKEY_AGENT_MAX_LIFETIME_MS
|
|
32551
|
+
),
|
|
32552
|
+
reason: "Grant permissions requested by a TinyCloud auth request artifact.",
|
|
32553
|
+
exactApproval: true,
|
|
32554
|
+
noPopup: !(process.stdin.isTTY || process.stderr.isTTY)
|
|
32555
|
+
});
|
|
32556
|
+
const approvalNode = await nodeFromOpenKeyApproval(ctx.host, approval.session);
|
|
32557
|
+
grant = {
|
|
32558
|
+
...await grantAuthRequest(approvalNode, resolvedRequest, {
|
|
32559
|
+
expiry: approval.delegation.expiry.getTime() - Date.now()
|
|
32560
|
+
}),
|
|
32561
|
+
// The owner approved this grant on the OpenKey approval screen, so it was prompted.
|
|
32562
|
+
prompted: true
|
|
32563
|
+
};
|
|
32564
|
+
process.stderr.write(`Grant lasts until ${grant.expiry}, the lifetime of the OpenKey approval.
|
|
32565
|
+
`);
|
|
32566
|
+
} else {
|
|
32567
|
+
await ensureDelegationAuthority({
|
|
32568
|
+
ctx,
|
|
32569
|
+
profile,
|
|
32570
|
+
node,
|
|
32571
|
+
requested,
|
|
32572
|
+
expiryOption: parsed.requestedExpiry,
|
|
32573
|
+
reason: "Grant permissions requested by a TinyCloud auth request artifact.",
|
|
32574
|
+
yes: options2.yes === true || confirmedExtras
|
|
32575
|
+
});
|
|
32576
|
+
grant = await grantAuthRequest(node, resolvedRequest);
|
|
32577
|
+
}
|
|
32543
32578
|
await appendLocalGrantArtifact(ctx.profile, grant.delegation);
|
|
32544
32579
|
await appendGrantHistory(ctx.profile, {
|
|
32545
32580
|
addedCaps: grant.permissions,
|
|
@@ -32919,12 +32954,72 @@ async function activateAndStoreOpenKeyGrants(profileName2, host, node, grants, s
|
|
|
32919
32954
|
await appendAdditionalDelegations(profileName2, records);
|
|
32920
32955
|
});
|
|
32921
32956
|
}
|
|
32957
|
+
async function acquireOpenKeyApproval(params) {
|
|
32958
|
+
if (params.requested.some(isRawCidRevocationPermission)) {
|
|
32959
|
+
throw new CLIError(
|
|
32960
|
+
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
32961
|
+
"OpenKey cannot sign raw urn:cid revocation authority.",
|
|
32962
|
+
ExitCode.PERMISSION_DENIED
|
|
32963
|
+
);
|
|
32964
|
+
}
|
|
32965
|
+
const key = await ProfileManager.getKey(params.ctx.profile);
|
|
32966
|
+
if (!key) {
|
|
32967
|
+
throw new CLIError(
|
|
32968
|
+
"NO_KEY",
|
|
32969
|
+
`No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME17} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
|
|
32970
|
+
ExitCode.AUTH_REQUIRED
|
|
32971
|
+
);
|
|
32972
|
+
}
|
|
32973
|
+
const expiryCap = params.expiryOption === void 0 ? void 0 : parseRequestedExpiry(params.expiryOption);
|
|
32974
|
+
const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
|
|
32975
|
+
const request = grantRequestPermissions(params.requested, anchorSpace);
|
|
32976
|
+
const acquireOpenKey = params.openKeyAcquisition ?? startAuthFlow;
|
|
32977
|
+
const delegationData = await acquireOpenKey(params.profile.did, {
|
|
32978
|
+
jwk: key,
|
|
32979
|
+
host: params.ctx.host,
|
|
32980
|
+
permissions: request,
|
|
32981
|
+
reason: permissionGrantReason(params.reason, request),
|
|
32982
|
+
openkeyHost: resolveOpenKeyHost(params.profile),
|
|
32983
|
+
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
|
|
32984
|
+
noPopup: params.noPopup
|
|
32985
|
+
});
|
|
32986
|
+
return verifyOpenKeyApproval(delegationData, request, params.ctx.host, {
|
|
32987
|
+
key,
|
|
32988
|
+
sessionDid: params.profile.sessionDid ?? params.profile.did,
|
|
32989
|
+
expectedOwner: pinnedOwner(params.profile),
|
|
32990
|
+
expiry: expiryCap,
|
|
32991
|
+
exactApproval: params.exactApproval === true
|
|
32992
|
+
});
|
|
32993
|
+
}
|
|
32994
|
+
async function nodeFromOpenKeyApproval(host, session) {
|
|
32995
|
+
const ownerParts = session.ownerDid.split(":");
|
|
32996
|
+
const node = new TinyCloudNode3({ host });
|
|
32997
|
+
await node.restoreSession({
|
|
32998
|
+
delegationHeader: session.delegationHeader,
|
|
32999
|
+
delegationCid: session.delegationCid,
|
|
33000
|
+
spaceId: session.spaceId,
|
|
33001
|
+
jwk: session.jwk,
|
|
33002
|
+
verificationMethod: session.verificationMethod,
|
|
33003
|
+
address: ownerParts[4],
|
|
33004
|
+
chainId: Number(ownerParts[3]),
|
|
33005
|
+
siwe: session.siwe,
|
|
33006
|
+
signature: session.signature,
|
|
33007
|
+
expiresAt: session.expiresAt,
|
|
33008
|
+
tinycloudHosts: [host]
|
|
33009
|
+
});
|
|
33010
|
+
return node;
|
|
33011
|
+
}
|
|
32922
33012
|
async function ensureDelegationAuthority(params) {
|
|
32923
33013
|
if (!params.force && params.node.hasRuntimePermissions(params.requested)) return void 0;
|
|
32924
33014
|
if (!params.consentAlreadyGiven && (params.persist === false || params.profile.authMethod !== "openkey")) {
|
|
32925
33015
|
await requirePermissionConsent(params.requested, params.yes);
|
|
32926
33016
|
}
|
|
32927
33017
|
if (params.profile.authMethod === "openkey") {
|
|
33018
|
+
if (params.persist === false) {
|
|
33019
|
+
const { delegation } = await acquireOpenKeyApproval(params);
|
|
33020
|
+
await params.node.useRuntimeDelegation(delegation);
|
|
33021
|
+
return delegation;
|
|
33022
|
+
}
|
|
32928
33023
|
if (params.requested.some(isRawCidRevocationPermission)) {
|
|
32929
33024
|
throw new CLIError(
|
|
32930
33025
|
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
@@ -32947,28 +33042,10 @@ async function ensureDelegationAuthority(params) {
|
|
|
32947
33042
|
key,
|
|
32948
33043
|
sessionDid: params.profile.sessionDid ?? params.profile.did,
|
|
32949
33044
|
expectedOwner: pinnedOwner(params.profile),
|
|
32950
|
-
expiry: expiryCap
|
|
33045
|
+
expiry: expiryCap,
|
|
33046
|
+
exactApproval: params.exactApproval === true
|
|
32951
33047
|
};
|
|
32952
33048
|
const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
|
|
32953
|
-
if (params.persist === false) {
|
|
32954
|
-
const request = grantRequestPermissions(params.requested, anchorSpace);
|
|
32955
|
-
const delegationData = await acquireOpenKey(params.profile.did, {
|
|
32956
|
-
jwk: key,
|
|
32957
|
-
host: params.ctx.host,
|
|
32958
|
-
permissions: request,
|
|
32959
|
-
reason: permissionGrantReason(params.reason, request),
|
|
32960
|
-
openkeyHost,
|
|
32961
|
-
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
|
|
32962
|
-
});
|
|
32963
|
-
const delegation = portableFromOpenKeyDelegation(
|
|
32964
|
-
delegationData,
|
|
32965
|
-
request,
|
|
32966
|
-
params.ctx.host,
|
|
32967
|
-
proof
|
|
32968
|
-
);
|
|
32969
|
-
await params.node.useRuntimeDelegation(delegation);
|
|
32970
|
-
return delegation;
|
|
32971
|
-
}
|
|
32972
33049
|
const grants = [];
|
|
32973
33050
|
for (const group of groupPermissionsBySpace(params.requested)) {
|
|
32974
33051
|
const request = grantRequestPermissions(group, anchorSpace);
|
|
@@ -32978,7 +33055,8 @@ async function ensureDelegationAuthority(params) {
|
|
|
32978
33055
|
permissions: request,
|
|
32979
33056
|
reason: permissionGrantReason(params.reason, group),
|
|
32980
33057
|
openkeyHost,
|
|
32981
|
-
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
|
|
33058
|
+
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
|
|
33059
|
+
noPopup: params.noPopup
|
|
32982
33060
|
});
|
|
32983
33061
|
const delegation = portableFromOpenKeyDelegation(delegationData, request, params.ctx.host, proof);
|
|
32984
33062
|
grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
|
|
@@ -33005,6 +33083,23 @@ async function ensureDelegationAuthority(params) {
|
|
|
33005
33083
|
});
|
|
33006
33084
|
}
|
|
33007
33085
|
}
|
|
33086
|
+
var OPENKEY_AGENT_MAX_LIFETIME_MS = parseRequestedExpiry(APPS_AGENT_EXPIRY).durationMs;
|
|
33087
|
+
function assertOpenKeyAgentRequest(requested) {
|
|
33088
|
+
if (requested.some((permission) => isRawEncryptionPermission(permission) || isRawCidRevocationPermission(permission))) {
|
|
33089
|
+
throw new CLIError(
|
|
33090
|
+
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
33091
|
+
"OpenKey cannot approve raw urn:cid or raw encryption authority for an agent request.",
|
|
33092
|
+
ExitCode.PERMISSION_DENIED
|
|
33093
|
+
);
|
|
33094
|
+
}
|
|
33095
|
+
if (groupPermissionsBySpace(requested).length !== 1) {
|
|
33096
|
+
throw new CLIError(
|
|
33097
|
+
"AGENT_GRANT_MULTIPLE_SPACES",
|
|
33098
|
+
"An agent grant must cover exactly one space. Request one space per auth request.",
|
|
33099
|
+
ExitCode.PERMISSION_DENIED
|
|
33100
|
+
);
|
|
33101
|
+
}
|
|
33102
|
+
}
|
|
33008
33103
|
function permissionGrantReason(context, permissions) {
|
|
33009
33104
|
const first = permissions[0];
|
|
33010
33105
|
const summary = first ? compactPermission(first) : "no permissions";
|
|
@@ -33127,10 +33222,14 @@ function groupPermissionsBySpace(permissions) {
|
|
|
33127
33222
|
return grouped;
|
|
33128
33223
|
}
|
|
33129
33224
|
function portableFromOpenKeyDelegation(data, requested, host, proof) {
|
|
33225
|
+
return verifyOpenKeyApproval(data, requested, host, proof).delegation;
|
|
33226
|
+
}
|
|
33227
|
+
function verifyOpenKeyApproval(data, requested, host, proof) {
|
|
33130
33228
|
const { session, legacyNested } = verifyScopedLogin(data, proof.key, proof.sessionDid, requested, {
|
|
33131
33229
|
expectedOwner: proof.expectedOwner,
|
|
33132
33230
|
expiry: proof.expiry,
|
|
33133
|
-
purpose: "grant"
|
|
33231
|
+
purpose: "grant",
|
|
33232
|
+
exactApproval: proof.exactApproval
|
|
33134
33233
|
});
|
|
33135
33234
|
if (legacyNested.length > 0) {
|
|
33136
33235
|
throw new CLIError(
|
|
@@ -33153,7 +33252,7 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
|
|
|
33153
33252
|
...permission.caveats === void 0 ? {} : { caveats: structuredClone(permission.caveats) }
|
|
33154
33253
|
}));
|
|
33155
33254
|
const ownerParts = session.ownerDid.split(":");
|
|
33156
|
-
|
|
33255
|
+
const delegation = {
|
|
33157
33256
|
cid: data.delegationCid,
|
|
33158
33257
|
delegationHeader: data.delegationHeader,
|
|
33159
33258
|
spaceId: returnedSpace,
|
|
@@ -33168,6 +33267,7 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
|
|
|
33168
33267
|
// Verified above; replay rebuilds the CACAO from it before trusting the grant.
|
|
33169
33268
|
siweProof: { siwe: data.siwe, signature: data.signature }
|
|
33170
33269
|
};
|
|
33270
|
+
return { delegation, session };
|
|
33171
33271
|
}
|
|
33172
33272
|
async function rotateAuthKey(profileName2, host, options = {}) {
|
|
33173
33273
|
await recoverPendingLogout(profileName2);
|