@tomato414941/foundation 0.23.0 → 0.25.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 +20 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomato414941/foundation",
3
- "version": "0.23.0",
3
+ "version": "0.25.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
@@ -230,7 +230,7 @@ async function main() {
230
230
  // A lent machine's key stays as it is; it ends with the machine.
231
231
  async function upgrade(label) {
232
232
  const me = await send('/v1/principals/me', undefined, { method: 'GET' });
233
- if (me.key?.environment) return;
233
+ if ((await send('/v1/session', undefined, { method: 'GET' })).current?.via.environment) return;
234
234
  const { options } = await send('/v1/principals/' + encodeURIComponent(me.principal.id) + '/credentials', { kind: 'webauthn' });
235
235
  const made = createCredential(options, url.origin);
236
236
  await send('/v1/principals/' + encodeURIComponent(me.principal.id) + '/credentials', { kind: 'webauthn', name: label, credential: made.response }, { method: 'PUT' });
@@ -288,23 +288,32 @@ async function main() {
288
288
  } else if (key?.token) { await upgrade(wanted); token = await prove(); await publishKey(); }
289
289
  if (serverGiven !== undefined) await saveUrl(url.origin);
290
290
  if (!me) me = await send('/v1/principals/me', undefined, { method: 'GET' });
291
- console.log(JSON.stringify({ principal: me.principal, acts_for: me.acts_for ?? [] }, null, 2));
291
+ console.log(JSON.stringify({ principal: { id: me.principal.id, name: me.principal.name, created_at: me.principal.created_at }, acts_for: me.principal.acts_for ?? [] }, null, 2));
292
292
  console.log('\nKey file: ' + keyPath + '\nServer: ' + url.origin + (serverGiven !== undefined ? ' (saved to ' + configPath() + ')' : '') + '\nTo act for someone: foundation join\nEverything else is HTTP: Authorization: Bearer $(foundation token)');
293
293
  return;
294
294
  }
295
295
  // Asking a person to make this machine their agent. One they already approved has nothing to ask.
296
296
  if (action === 'join') {
297
297
  const me = await send('/v1/principals/me', undefined, { method: 'GET', accept: data => data.error?.code === 'not_approved' });
298
- if (me.acts_for?.length) { console.log('Already approved on ' + url.origin + '.'); return; }
298
+ if (me.principal?.acts_for?.length) { console.log('Already approved on ' + url.origin + '.'); return; }
299
299
  const answer = await send('/v1/requests', { authorization_details: [{ type: 'relation', relation: 'agent' }] });
300
300
  console.log(JSON.stringify(answer, null, 2));
301
301
  return;
302
302
  }
303
+ // What others hold and this machine has a line onto: the things at the far end of the lines it is at the near end of.
304
+ const shownToMe = async () => {
305
+ const things = [];
306
+ for (let after = null; ;) {
307
+ const page = await send('/v1/principals/me/relations?direction=from&limit=200' + (after ? '&after=' + encodeURIComponent(after) : ''), undefined, { method: 'GET', accept: () => true });
308
+ things.push(...(page.relations ?? []).filter(line => line.resource).map(line => line.resource));
309
+ if (!(after = page.next)) return things;
310
+ }
311
+ };
303
312
  // Reading a secret this machine was handed an envelope for: its own, or one shown to it along a line.
304
313
  if (action === 'read') {
305
314
  if (!key.own) throw new Error('This machine has no key of its own here, so nothing sealed for it can be opened.');
306
315
  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);
316
+ const resource = own.resource ?? (await shownToMe()).find(item => item.kind === 'secret' && item.name === call.name);
308
317
  if (!resource) throw new Error('No secret named ' + JSON.stringify(call.name) + ' is kept by this machine or shown to it.');
309
318
  const kept = await send('/v1/resources/' + resource.id + '/content', undefined, { method: 'GET' });
310
319
  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.');
@@ -314,11 +323,14 @@ async function main() {
314
323
  }
315
324
  // Nothing runs before someone has accepted this key: a key that acts for nobody reaches only its own empty resources,
316
325
  // and the person it asked has yet to answer.
317
- const current = await send('/v1/principals/me', undefined, { method: 'GET' });
326
+ const current = (await send('/v1/principals/me', undefined, { method: 'GET' })).principal;
318
327
  // A key given to a lent machine acts as its principal's own self. Any other key acts for someone once approved;
319
328
  // until then, whether waiting or refused, it has nothing to run with.
320
- const own = Boolean(current.key?.environment);
321
- if (!own && !current.acts_for?.length) throw new Error('Foundation request failed (401, not_approved). This key acts for nobody yet' + (current.requests?.[0] ? '; it is waiting for approval at ' + current.requests[0].verification_uri : '') + '.');
329
+ const own = Boolean((await send('/v1/session', undefined, { method: 'GET' })).current?.via.environment);
330
+ if (!own && !current.acts_for?.length) {
331
+ const waiting = (await send('/v1/requests?status=pending', undefined, { method: 'GET', accept: () => true })).requests?.[0];
332
+ throw new Error('Foundation request failed (401, not_approved). This key acts for nobody yet' + (waiting ? '; it is waiting for approval at ' + waiting.verification_uri : '') + '.');
333
+ }
322
334
  // Whose resources a run reaches: the one this key acts for, the one named when it acts for several, or its own.
323
335
  const acting = current.acts_for ?? [];
324
336
  const owner = process.env.FOUNDATION_AS || (acting.length === 1 ? acting[0] : null);
@@ -344,7 +356,7 @@ async function main() {
344
356
  const handed = { environment: {}, files: [] };
345
357
  let asked = names;
346
358
  if (key.own && names.some(item => typeof item.name === 'string')) {
347
- const shown = owner ? (await send('/v1/principals/me/resources?shown=me', undefined, { method: 'GET', accept: () => true })).resources ?? [] : [];
359
+ const shown = owner ? await shownToMe() : [];
348
360
  asked = [];
349
361
  for (const item of names) {
350
362
  let resource = null;