@tomato414941/foundation 0.26.0 → 0.28.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 +12 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomato414941/foundation",
3
- "version": "0.26.0",
3
+ "version": "0.28.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
@@ -251,9 +251,9 @@ async function main() {
251
251
  let key = publicSpec ? null : await readKey(keyPath, { missingOk: action === 'init' }), token = key?.token ?? null;
252
252
  // The credential proves this machine for an hour at a time: the challenge is answered for the server actually reached.
253
253
  async function prove() {
254
- const begin = await fetch(url.origin + '/v1/session', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ kind: 'webauthn' }), redirect: 'error', signal: AbortSignal.timeout(30_000) });
254
+ const begin = await fetch(url.origin + '/v1/session/challenges', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ kind: 'webauthn' }), redirect: 'error', signal: AbortSignal.timeout(30_000) });
255
255
  const { options } = await begin.json();
256
- const response = await fetch(url.origin + '/v1/session', { method: 'PUT', headers: { 'content-type': 'application/json' },
256
+ const response = await fetch(url.origin + '/v1/session', { method: 'POST', headers: { 'content-type': 'application/json' },
257
257
  body: JSON.stringify({ kind: 'webauthn', credential: answer(options, key.credential, url.origin), session: 'token' }), redirect: 'error', signal: AbortSignal.timeout(30_000) });
258
258
  const proven = await response.json();
259
259
  if (!response.ok || typeof proven.token !== 'string') throw new Error('Foundation did not accept this machine\'s credential (' + response.status + ', ' + (proven.error?.code || 'unknown') + ').');
@@ -271,9 +271,9 @@ async function main() {
271
271
  async function upgrade(label) {
272
272
  const me = await send('/v1/principals/me', undefined, { method: 'GET' });
273
273
  if ((await send('/v1/session', undefined, { method: 'GET' })).current?.via.environment) return;
274
- const { options } = await send('/v1/principals/' + encodeURIComponent(me.principal.id) + '/credentials', { kind: 'webauthn' });
274
+ const { options } = await send('/v1/principals/' + encodeURIComponent(me.principal.id) + '/credentials/challenges', { kind: 'webauthn' });
275
275
  const made = createCredential(options, url.origin);
276
- await send('/v1/principals/' + encodeURIComponent(me.principal.id) + '/credentials', { kind: 'webauthn', name: label, credential: made.response }, { method: 'PUT' });
276
+ await send('/v1/principals/' + encodeURIComponent(me.principal.id) + '/credentials', { kind: 'webauthn', name: label, credential: made.response });
277
277
  await writeKey(keyPath, JSON.stringify({ webauthn_credential: made.credential }), !process.env.FOUNDATION_RUNTIME_KEY_FILE);
278
278
  key = { credential: made.credential };
279
279
  }
@@ -282,7 +282,7 @@ async function main() {
282
282
  async function publishKey() {
283
283
  if (key.own) return;
284
284
  const made = generateKey();
285
- const published = await send('/v1/principals/me/key', { public_key: made.publicKey.toString('base64url') }, { method: 'PUT', accept: data => data.error?.code === 'key_exists' });
285
+ const published = await send('/v1/principals/me/encryption-key', { public_key: made.publicKey.toString('base64url') }, { method: 'PUT', accept: data => data.error?.code === 'key_exists' });
286
286
  if (published.error) return;
287
287
  key.own = { private_key: made.privateKey.toString('base64url') };
288
288
  await writeKey(keyPath, JSON.stringify({ webauthn_credential: key.credential, key: key.own }), !process.env.FOUNDATION_RUNTIME_KEY_FILE);
@@ -315,12 +315,12 @@ async function main() {
315
315
  // No key, or one this server does not know: a WebAuthn credential made here, proven by nobody, makes this machine a
316
316
  // principal there, of nobody's - the same call a browser's passkey makes.
317
317
  key = null; me = null;
318
- const post = (method, payload) => fetch(url.origin + '/v1/principals', { method, headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload), redirect: 'error', signal: AbortSignal.timeout(30_000) });
319
- const begun = await post('POST', { kind: 'webauthn', name: wanted });
318
+ const post = (path, payload) => fetch(url.origin + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload), redirect: 'error', signal: AbortSignal.timeout(30_000) });
319
+ const begun = await post('/v1/principals/challenges', { kind: 'webauthn', name: wanted });
320
320
  const { options, error } = await begun.json();
321
321
  if (!begun.ok) throw new Error('Foundation did not begin a credential (' + begun.status + ', ' + (error?.code || 'unknown') + ').');
322
322
  const made = createCredential(options, url.origin);
323
- const done = await post('PUT', { kind: 'webauthn', name: wanted, principal_name: wanted, credential: made.response, session: 'token' });
323
+ const done = await post('/v1/principals', { kind: 'webauthn', name: wanted, principal_name: wanted, credential: made.response, session: 'token' });
324
324
  const became = await done.json();
325
325
  if (!done.ok || typeof became.token !== 'string') throw new Error('Foundation did not accept this machine\'s credential (' + done.status + ', ' + (became.error?.code || 'unknown') + ').');
326
326
  key = { credential: made.credential }; token = became.token;
@@ -337,7 +337,10 @@ async function main() {
337
337
  if (action === 'join') {
338
338
  const me = await send('/v1/principals/me', undefined, { method: 'GET', accept: data => data.error?.code === 'not_approved' });
339
339
  if (me.principal?.acts_for?.length) { console.log('Already approved on ' + url.origin + '.'); return; }
340
- const answer = await send('/v1/requests', { authorization_details: [{ type: 'relation', relation: 'agent' }] });
340
+ // The one call it may ask for: a line from itself, agent, onto whoever answers (me, to them).
341
+ // One someone already owns asks them; one nobody knows asks whoever opens the page it shows.
342
+ const self = me.principal?.id ?? (await send('/v1/session', undefined, { method: 'GET' })).current?.principal_id, owner = me.principal?.owners?.[0];
343
+ const answer = await send('/v1/requests', { ...(owner ? { to: owner } : {}), operations: [{ method: 'POST', path: '/v1/principals/' + encodeURIComponent(self) + '/relations', body: { relation: 'agent', object_type: 'principal', object_id: 'me' } }] });
341
344
  console.log(JSON.stringify(answer, null, 2));
342
345
  return;
343
346
  }