@tinycloud/cli 0.9.0-beta.8 → 0.9.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29979,9 +29979,52 @@ function buildAuthUrl(did, options = {}) {
29979
29979
  if (options.expiry !== void 0) {
29980
29980
  params.set("expiry", String(options.expiry));
29981
29981
  }
29982
+ params.set("protocolVersion", "1");
29982
29983
  const base4 = options.openkeyHost ?? DEFAULT_OPENKEY_HOST;
29983
29984
  return `${base4}/delegate?${params.toString()}`;
29984
29985
  }
29986
+ function validateDelegationCallbackPayload(value) {
29987
+ if (!value || typeof value !== "object") return "expected an object";
29988
+ const v = value;
29989
+ if (!v.delegationHeader || typeof v.delegationHeader !== "object") {
29990
+ return "delegationHeader must be an object";
29991
+ }
29992
+ const auth = v.delegationHeader.Authorization;
29993
+ if (typeof auth !== "string" || !auth) {
29994
+ return "delegationHeader.Authorization must be a non-empty string";
29995
+ }
29996
+ if (typeof v.delegationCid !== "string" || !v.delegationCid) {
29997
+ return "delegationCid must be a non-empty string";
29998
+ }
29999
+ if (typeof v.spaceId !== "string" || !v.spaceId) {
30000
+ return "spaceId must be a non-empty string";
30001
+ }
30002
+ if (v.permissions !== void 0) {
30003
+ if (!Array.isArray(v.permissions)) {
30004
+ return "permissions, when present, must be an array";
30005
+ }
30006
+ for (let i = 0; i < v.permissions.length; i++) {
30007
+ const entry = v.permissions[i];
30008
+ if (!entry || typeof entry !== "object") {
30009
+ return `permissions[${i}] must be an object`;
30010
+ }
30011
+ const e = entry;
30012
+ if (typeof e.service !== "string" || !e.service) {
30013
+ return `permissions[${i}].service must be a non-empty string`;
30014
+ }
30015
+ if (typeof e.space !== "string") {
30016
+ return `permissions[${i}].space must be a string`;
30017
+ }
30018
+ if (typeof e.path !== "string") {
30019
+ return `permissions[${i}].path must be a string`;
30020
+ }
30021
+ if (!Array.isArray(e.actions) || e.actions.some((a) => typeof a !== "string" || !a)) {
30022
+ return `permissions[${i}].actions must be a non-empty string[]`;
30023
+ }
30024
+ }
30025
+ }
30026
+ return null;
30027
+ }
29985
30028
  function shouldOpenBrowser(options) {
29986
30029
  if (options.noPopup) return false;
29987
30030
  const env = process.env.TC_AUTH_NO_POPUP ?? process.env.TC_NO_POPUP;
@@ -30008,12 +30051,18 @@ async function callbackFlow(did, options = {}) {
30008
30051
  }
30009
30052
  function parsePasteInput(input) {
30010
30053
  const trimmed = input.trim();
30054
+ let parsed;
30011
30055
  try {
30012
- return JSON.parse(trimmed);
30056
+ parsed = JSON.parse(trimmed);
30013
30057
  } catch {
30014
30058
  const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
30015
- return JSON.parse(decoded);
30059
+ parsed = JSON.parse(decoded);
30060
+ }
30061
+ const invalid = validateDelegationCallbackPayload(parsed);
30062
+ if (invalid) {
30063
+ throw new Error(`Invalid delegation code: ${invalid}`);
30016
30064
  }
30065
+ return parsed;
30017
30066
  }
30018
30067
  const server = createServer((req, res) => {
30019
30068
  if (req.method === "POST" && req.url === "/callback") {
@@ -30024,6 +30073,13 @@ async function callbackFlow(did, options = {}) {
30024
30073
  req.on("end", () => {
30025
30074
  try {
30026
30075
  const data = JSON.parse(body);
30076
+ const invalid = validateDelegationCallbackPayload(data);
30077
+ if (invalid) {
30078
+ res.writeHead(400, { "Content-Type": "application/json" });
30079
+ res.end(JSON.stringify({ error: invalid }));
30080
+ settle({ error: new Error(`Invalid delegation payload: ${invalid}`) });
30081
+ return;
30082
+ }
30027
30083
  res.writeHead(200, {
30028
30084
  "Content-Type": "application/json",
30029
30085
  "Access-Control-Allow-Origin": "*"
@@ -30110,18 +30166,24 @@ Open this URL in a browser to authenticate:
30110
30166
  return new Promise((resolve4, reject) => {
30111
30167
  rl.question("Paste delegation code: ", (input) => {
30112
30168
  rl.close();
30169
+ let parsed;
30113
30170
  try {
30114
- const data = JSON.parse(input.trim());
30115
- resolve4(data);
30171
+ parsed = JSON.parse(input.trim());
30116
30172
  } catch {
30117
30173
  try {
30118
30174
  const decoded = Buffer.from(input.trim(), "base64").toString("utf-8");
30119
- const data = JSON.parse(decoded);
30120
- resolve4(data);
30175
+ parsed = JSON.parse(decoded);
30121
30176
  } catch {
30122
30177
  reject(new Error("Invalid delegation code. Expected JSON or base64-encoded JSON."));
30178
+ return;
30123
30179
  }
30124
30180
  }
30181
+ const invalid = validateDelegationCallbackPayload(parsed);
30182
+ if (invalid) {
30183
+ reject(new Error(`Invalid delegation code: ${invalid}`));
30184
+ return;
30185
+ }
30186
+ resolve4(parsed);
30125
30187
  });
30126
30188
  });
30127
30189
  }
@@ -30376,6 +30438,7 @@ function registerAuthCommand(program) {
30376
30438
  const delegationCids2 = [];
30377
30439
  let expiry2;
30378
30440
  const openkeyHost = resolveOpenKeyHost(profile);
30441
+ const openkeyEffective = [];
30379
30442
  for (const group of groupPermissionsBySpace(requested)) {
30380
30443
  const delegationData = await startAuthFlow(profile.did, {
30381
30444
  jwk: key,
@@ -30390,13 +30453,15 @@ function registerAuthCommand(program) {
30390
30453
  noPopup: options.popup === false
30391
30454
  });
30392
30455
  const delegation = portableFromOpenKeyDelegation(delegationData, group, ctx.host);
30393
- const stored = storedAdditionalDelegation(delegation, group);
30456
+ const effective = permissionsFromDelegation(delegation);
30457
+ openkeyEffective.push(...effective);
30458
+ const stored = storedAdditionalDelegation(delegation, effective);
30394
30459
  await appendAdditionalDelegation(ctx.profile, stored);
30395
30460
  await node.useRuntimeDelegation(delegation);
30396
30461
  delegationCids2.push(delegation.cid);
30397
30462
  expiry2 = delegation.expiry.toISOString();
30398
30463
  await appendGrantHistory(ctx.profile, {
30399
- addedCaps: group,
30464
+ addedCaps: effective,
30400
30465
  source: options.manifest ? "manifest" : "cli",
30401
30466
  delegationCid: delegation.cid,
30402
30467
  expiry: expiry2
@@ -30404,7 +30469,7 @@ function registerAuthCommand(program) {
30404
30469
  }
30405
30470
  outputJson({
30406
30471
  changed: delegationCids2.length > 0,
30407
- added: requested,
30472
+ added: openkeyEffective,
30408
30473
  delegationCid: delegationCids2[0],
30409
30474
  delegationCids: delegationCids2,
30410
30475
  expiry: expiry2
@@ -30429,8 +30494,10 @@ function registerAuthCommand(program) {
30429
30494
  await persistCurrentLocalSession(ctx.profile, profile, node.restorableSession);
30430
30495
  const delegationCids = [];
30431
30496
  let expiry;
30497
+ const localEffective = [];
30432
30498
  for (const delegation of delegations) {
30433
30499
  const covering = permissionsFromDelegation(delegation);
30500
+ localEffective.push(...covering);
30434
30501
  const stored = storedAdditionalDelegation(delegation, covering);
30435
30502
  await appendAdditionalDelegation(ctx.profile, stored);
30436
30503
  delegationCids.push(delegation.cid);
@@ -30448,7 +30515,7 @@ function registerAuthCommand(program) {
30448
30515
  }
30449
30516
  outputJson({
30450
30517
  changed: true,
30451
- added: requested,
30518
+ added: localEffective,
30452
30519
  delegationCid: delegationCids[0],
30453
30520
  delegationCids,
30454
30521
  expiry
@@ -30907,13 +30974,14 @@ async function ensureDelegationAuthority(params) {
30907
30974
  expiry: params.expiryOption
30908
30975
  });
30909
30976
  const delegation = portableFromOpenKeyDelegation(delegationData, group, params.ctx.host);
30977
+ const effective = permissionsFromDelegation(delegation);
30910
30978
  await appendAdditionalDelegation(
30911
30979
  params.ctx.profile,
30912
- storedAdditionalDelegation(delegation, group)
30980
+ storedAdditionalDelegation(delegation, effective)
30913
30981
  );
30914
30982
  await params.node.useRuntimeDelegation(delegation);
30915
30983
  await appendGrantHistory(params.ctx.profile, {
30916
- addedCaps: group,
30984
+ addedCaps: effective,
30917
30985
  source: "cli",
30918
30986
  delegationCid: delegation.cid,
30919
30987
  expiry: delegation.expiry.toISOString()
@@ -31045,7 +31113,7 @@ function groupPermissionsBySpace(permissions) {
31045
31113
  rawEntries.push(permission);
31046
31114
  continue;
31047
31115
  }
31048
- const key = normalizeSpaceForCompare(permission.space);
31116
+ const key = normalizeSpaceForCompare(permission.space ?? "");
31049
31117
  const group = groups.get(key) ?? [];
31050
31118
  group.push(permission);
31051
31119
  groups.set(key, group);
@@ -31078,7 +31146,7 @@ function portableFromOpenKeyDelegation(data, permissions, host) {
31078
31146
  const primary = permissions.find((permission) => !isRawPermission(permission)) ?? permissions[0];
31079
31147
  const returnedSpace = String(data.spaceId ?? primary.space ?? "encryption");
31080
31148
  const expectedSpaces = new Set(
31081
- permissions.filter((permission) => !isRawPermission(permission)).map((permission) => normalizeSpaceForCompare(permission.space))
31149
+ permissions.filter((permission) => !isRawPermission(permission)).map((permission) => normalizeSpaceForCompare(permission.space ?? ""))
31082
31150
  );
31083
31151
  const matchesExpectedSpace = expectedSpaces.size === 1 && returnedSpaceMatchesExpected(returnedSpace, Array.from(expectedSpaces)[0]);
31084
31152
  if (expectedSpaces.size > 0 && !matchesExpectedSpace) {
@@ -31089,18 +31157,54 @@ function portableFromOpenKeyDelegation(data, permissions, host) {
31089
31157
  );
31090
31158
  }
31091
31159
  const expiry = inferDelegationExpiry(data);
31160
+ const requestedPairs = new Set(
31161
+ permissions.flatMap(
31162
+ (p) => isRawPermission(p) ? p.actions.map((a) => `${p.service}|${p.space ?? ""}|${p.path}|${a}`) : p.actions.map((a) => `${p.service}|${normalizeSpaceForCompare(p.space ?? "")}|${p.path}|${a}`)
31163
+ )
31164
+ );
31165
+ const returnedPermissions = Array.isArray(data.permissions) ? data.permissions : null;
31166
+ const resources = (returnedPermissions ?? permissions).map((permission) => {
31167
+ const service = permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service;
31168
+ const rawService = permission.service.startsWith("tinycloud.") ? permission.service : `tinycloud.${service}`;
31169
+ const permSpace = permission.space ?? "";
31170
+ if (returnedPermissions) {
31171
+ const rawSpace = isRawPermission({
31172
+ service: rawService,
31173
+ space: permSpace,
31174
+ path: permission.path,
31175
+ actions: []
31176
+ }) ? permSpace : normalizeSpaceForCompare(permSpace);
31177
+ for (const action of permission.actions) {
31178
+ const key = `${rawService}|${rawSpace}|${permission.path}|${action}`;
31179
+ if (!requestedPairs.has(key)) {
31180
+ throw new CLIError(
31181
+ "OPENKEY_GRANT_BROADENED",
31182
+ `OpenKey returned grant ${rawService}/${action} on ${permSpace}/${permission.path} that was not requested.`,
31183
+ ExitCode.PERMISSION_DENIED
31184
+ );
31185
+ }
31186
+ }
31187
+ }
31188
+ const resolvedSpace = isRawPermission({
31189
+ service: rawService,
31190
+ space: permSpace,
31191
+ path: permission.path,
31192
+ actions: []
31193
+ }) ? permSpace : returnedSpace;
31194
+ return {
31195
+ service,
31196
+ space: resolvedSpace,
31197
+ path: permission.path,
31198
+ actions: [...permission.actions]
31199
+ };
31200
+ });
31092
31201
  return {
31093
31202
  cid: String(data.delegationCid),
31094
31203
  delegationHeader: data.delegationHeader,
31095
31204
  spaceId: returnedSpace,
31096
31205
  path: primary.path,
31097
31206
  actions: primary.actions,
31098
- resources: permissions.map((permission) => ({
31099
- service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
31100
- space: isRawPermission(permission) ? permission.space : returnedSpace,
31101
- path: permission.path,
31102
- actions: [...permission.actions]
31103
- })),
31207
+ resources,
31104
31208
  expiry,
31105
31209
  delegateDID: String(data.verificationMethod),
31106
31210
  ownerAddress: String(data.address ?? ""),