@tomato414941/foundation 0.22.0 → 0.23.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/runtime.mjs +9 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomato414941/foundation",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Foundation CLI: give this machine a WebAuthn credential, and hand what is kept to a command without it passing through the agent.",
5
5
  "type": "module",
6
6
  "engines": {
package/runtime.mjs CHANGED
@@ -256,10 +256,6 @@ async function main() {
256
256
  if (action === 'token') { console.log(token); return; }
257
257
  // One request, as this machine, and the answer printed as it came. Nothing here knows the endpoints.
258
258
  if (action === 'api') {
259
- if (!publicSpec && !/[?&]as=/.test(call.target)) {
260
- const me = await send('/v1/principals/me', undefined, { method: 'GET', accept: () => true });
261
- if (me.acts_for?.length === 1) call.target += (call.target.includes('?') ? '&' : '?') + 'as=' + encodeURIComponent(me.acts_for[0]);
262
- }
263
259
  const response = await fetch(url.origin + call.target, { method: call.method, headers: { ...(token ? { authorization: 'Bearer ' + token } : {}), ...(call.body === undefined ? {} : { 'content-type': call.type }) },
264
260
  ...(call.body === undefined ? {} : { body: call.body }), redirect: 'error', signal: AbortSignal.timeout(30_000) });
265
261
  const bytes = Buffer.from(await response.arrayBuffer());
@@ -307,8 +303,8 @@ async function main() {
307
303
  // Reading a secret this machine was handed an envelope for: its own, or one shown to it along a line.
308
304
  if (action === 'read') {
309
305
  if (!key.own) throw new Error('This machine has no key of its own here, so nothing sealed for it can be opened.');
310
- const own = await send('/v1/resources?kind=secret&name=' + encodeURIComponent(call.name), undefined, { method: 'GET', accept: () => true });
311
- const resource = own.resource ?? (await send('/v1/resources?shown=me', undefined, { method: 'GET' })).resources.find(item => item.kind === 'secret' && item.name === call.name);
306
+ const own = await send('/v1/principals/me/resources?kind=secret&name=' + encodeURIComponent(call.name), undefined, { method: 'GET', accept: () => true });
307
+ const resource = own.resource ?? (await send('/v1/principals/me/resources?shown=me', undefined, { method: 'GET' })).resources.find(item => item.kind === 'secret' && item.name === call.name);
312
308
  if (!resource) throw new Error('No secret named ' + JSON.stringify(call.name) + ' is kept by this machine or shown to it.');
313
309
  const kept = await send('/v1/resources/' + resource.id + '/content', undefined, { method: 'GET' });
314
310
  if (!kept.envelope) throw new Error('No envelope was made for this machine: it may read about this secret, but was not handed its key.');
@@ -327,17 +323,17 @@ async function main() {
327
323
  const acting = current.acts_for ?? [];
328
324
  const owner = process.env.FOUNDATION_AS || (acting.length === 1 ? acting[0] : null);
329
325
  if (!owner && acting.length > 1) throw new Error('This key acts for several principals. Set FOUNDATION_AS=<principal id> to say which one this run is for.');
330
- const forHolder = target => owner ? target + (target.includes('?') ? '&' : '?') + 'as=' + encodeURIComponent(owner) : target;
326
+ const holder = '/v1/principals/' + encodeURIComponent(owner || 'me');
331
327
  // A secret is sealed here, with a key of its own, for each of the owner's recipients: the server keeps what it
332
328
  // cannot open. This machine is not among them; it places the bytes and does not read them back.
333
329
  const sealedFor = async bytes => {
334
- const { recipients } = await send('/v1/principals/' + encodeURIComponent(owner || 'me') + '/recipients', undefined, { method: 'GET' });
330
+ const { recipients } = await send(holder + '/recipients', undefined, { method: 'GET' });
335
331
  if (!Array.isArray(recipients) || !recipients.length) throw new Error('Nobody can open a secret kept for this owner yet: the owner needs a key, or Foundation needs to act for them.');
336
332
  const contentKey = newContentKey();
337
333
  return { content: sealContent(contentKey, bytes).toString('base64url'), envelopes: Object.fromEntries(recipients.map(item => [item.principal_id, seal(contentKey, Buffer.from(item.public_key, 'base64url')).toString('base64url')])) };
338
334
  };
339
335
  if (action === 'keep') {
340
- const saved = await send(forHolder('/v1/resources?kind=secret&name=' + encodeURIComponent(call.name)), await sealedFor(call.body), { method: 'PUT' });
336
+ const saved = await send(holder + '/resources?kind=secret&name=' + encodeURIComponent(call.name), await sealedFor(call.body), { method: 'PUT' });
341
337
  try { await send('/v1/principals/me/relations', { relation: 'editor', object_type: 'resource', object_id: saved.resource.id }, { method: 'DELETE' }); } catch {}
342
338
  console.log(JSON.stringify(saved));
343
339
  return;
@@ -348,13 +344,13 @@ async function main() {
348
344
  const handed = { environment: {}, files: [] };
349
345
  let asked = names;
350
346
  if (key.own && names.some(item => typeof item.name === 'string')) {
351
- const shown = owner ? (await send('/v1/resources?shown=me', undefined, { method: 'GET', accept: () => true })).resources ?? [] : [];
347
+ const shown = owner ? (await send('/v1/principals/me/resources?shown=me', undefined, { method: 'GET', accept: () => true })).resources ?? [] : [];
352
348
  asked = [];
353
349
  for (const item of names) {
354
350
  let resource = null;
355
351
  if (typeof item.name === 'string') {
356
352
  resource = owner ? shown.find(row => row.kind === 'secret' && row.name === item.name && row.owner_id === owner)
357
- : (await send('/v1/resources?kind=secret&name=' + encodeURIComponent(item.name), undefined, { method: 'GET', accept: () => true })).resource;
353
+ : (await send('/v1/principals/me/resources?kind=secret&name=' + encodeURIComponent(item.name), undefined, { method: 'GET', accept: () => true })).resource;
358
354
  }
359
355
  const kept = resource ? await send('/v1/resources/' + resource.id + '/content', undefined, { method: 'GET', accept: () => true }) : null;
360
356
  if (!kept?.envelope) { asked.push(item); continue; }
@@ -364,7 +360,7 @@ async function main() {
364
360
  }
365
361
  }
366
362
  let injection;
367
- if (asked.length) ({ injection } = await send(forHolder('/v1/injections'), { names: asked }));
363
+ if (asked.length) ({ injection } = await send(holder + '/injections', { names: asked }));
368
364
  else injection = { environment: {}, files: [] };
369
365
  if (!injection || typeof injection.environment !== 'object' || !Array.isArray(injection.files)) throw new Error('Foundation returned an invalid injection.');
370
366
  injection = { environment: { ...injection.environment, ...handed.environment }, files: [...injection.files, ...handed.files] };
@@ -434,7 +430,7 @@ async function main() {
434
430
  retainOutput = true;
435
431
  // The command wrote it; the agent never saw it, and keeps it that way: the line drawn for the one who kept it is declined.
436
432
  let saved;
437
- try { saved = await send(forHolder('/v1/resources?kind=secret&name=' + encodeURIComponent(output.name)), await sealedFor(bytes), { method: 'PUT' }); }
433
+ try { saved = await send(holder + '/resources?kind=secret&name=' + encodeURIComponent(output.name), await sealedFor(bytes), { method: 'PUT' }); }
438
434
  catch { throw new Error(recovery()); }
439
435
  try { await send('/v1/principals/me/relations', { relation: 'editor', object_type: 'resource', object_id: saved.resource.id }, { method: 'DELETE' }); } catch {}
440
436
  retainOutput = false;