From 8279bf6371182709b46e83e5ac86d89ed1f292e8 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Tue, 14 Nov 2023 16:35:23 +0300 Subject: [PATCH] feat: w3up client login (#1120) --- packages/access-client/src/agent.js | 11 +++++++- packages/access-client/src/space.js | 33 ++++++++++++++++++++--- packages/w3up-client/src/account.js | 6 ++++- packages/w3up-client/src/client.js | 12 +++++++++ packages/w3up-client/test/account.test.js | 28 +++++++++++++++++++ 5 files changed, 84 insertions(+), 6 deletions(-) diff --git a/packages/access-client/src/agent.js b/packages/access-client/src/agent.js index fca9a67bf..22ff0fa23 100644 --- a/packages/access-client/src/agent.js +++ b/packages/access-client/src/agent.js @@ -321,7 +321,16 @@ export class Agent { * @param {string} name */ async createSpace(name) { - return await Space.generate({ name }) + return await Space.generate({ name, agent: this }) + } + + /** + * @param {string} secret + * @param {object} options + * @param {string} options.name + */ + async recoverSpace(secret, { name }) { + return await Space.fromMnemonic(secret, { name, agent: this }) } /** diff --git a/packages/access-client/src/space.js b/packages/access-client/src/space.js index a338d15c9..21600da17 100644 --- a/packages/access-client/src/space.js +++ b/packages/access-client/src/space.js @@ -11,6 +11,7 @@ import * as Access from './access.js' * @typedef {object} Model * @property {ED25519.EdSigner} signer * @property {string} name + * @property {API.Agent} [agent] */ /** @@ -18,11 +19,12 @@ import * as Access from './access.js' * * @param {object} options * @param {string} options.name + * @param {API.Agent} [options.agent] */ -export const generate = async ({ name }) => { +export const generate = async ({ name, agent }) => { const { signer } = await ED25519.generate() - return new OwnedSpace({ signer, name }) + return new OwnedSpace({ signer, name, agent }) } /** @@ -31,11 +33,12 @@ export const generate = async ({ name }) => { * @param {string} mnemonic * @param {object} options * @param {string} options.name - Name to give to the recovered space. + * @param {API.Agent} [options.agent] */ -export const fromMnemonic = async (mnemonic, { name }) => { +export const fromMnemonic = async (mnemonic, { name, agent }) => { const secret = BIP39.mnemonicToEntropy(mnemonic, wordlist) const signer = await ED25519.derive(secret) - return new OwnedSpace({ signer, name }) + return new OwnedSpace({ signer, name, agent }) } /** @@ -158,6 +161,27 @@ class OwnedSpace { return new OwnedSpace({ signer: this.signer, name }) } + /** + * Saves account in the agent store so it can be accessed across sessions. + * + * @param {object} input + * @param {API.Agent} [input.agent] + * @returns {Promise>} + */ + async save({ agent = this.model.agent } = {}) { + if (!agent) { + return { + error: new Error('Please provide an agent to save the space into'), + } + } + + const proof = await createAuthorization(this, { agent }) + await agent.importSpaceFromDelegation(proof) + agent.setCurrentSpace(this.did()) + + return { ok: {} } + } + /** * Creates a (UCAN) delegation that gives full access to the space to the * specified `account`. At the moment we only allow `did:mailto` principal @@ -229,6 +253,7 @@ class SharedSpace { * @property {API.SpaceDID} id * @property {API.Delegation} delegation * @property {{name?:string}} meta + * @property {API.Agent} [agent] * * @param {SharedSpaceModel} model */ diff --git a/packages/w3up-client/src/account.js b/packages/w3up-client/src/account.js index d4329211c..82f2bb23f 100644 --- a/packages/w3up-client/src/account.js +++ b/packages/w3up-client/src/account.js @@ -7,6 +7,10 @@ import { fromEmail, toEmail } from '@web3-storage/did-mailto' export { fromEmail } +/** + * @typedef {import('@web3-storage/did-mailto').EmailAddress} EmailAddress + */ + /** * List all accounts that agent has stored access to. Returns a dictionary * of accounts keyed by their `did:mailto` identifier. @@ -71,7 +75,7 @@ export const list = ({ agent }, { account } = {}) => { * resolve to an error. * * @param {{agent: API.Agent}} client - * @param {API.EmailAddress} email + * @param {EmailAddress} email * @returns {Promise>} */ export const login = async ({ agent }, email) => { diff --git a/packages/w3up-client/src/client.js b/packages/w3up-client/src/client.js index 8c1615514..577da1563 100644 --- a/packages/w3up-client/src/client.js +++ b/packages/w3up-client/src/client.js @@ -19,6 +19,7 @@ import { UsageClient } from './capability/usage.js' import { AccessClient } from './capability/access.js' import { FilecoinClient } from './capability/filecoin.js' export * as Access from './capability/access.js' +import * as Result from './result.js' export { AccessClient, @@ -55,6 +56,8 @@ export class Client extends Base { /* c8 ignore start - testing websockets is hard */ /** + * @deprecated - Use client.login instead. + * * Authorize the current agent to use capabilities granted to the passed * email account. * @@ -66,6 +69,15 @@ export class Client extends Base { async authorize(email, options) { await this.capability.access.authorize(email, options) } + + /** + * @param {Account.EmailAddress} email + */ + async login(email) { + const account = Result.unwrap(await Account.login(this, email)) + Result.unwrap(await account.save()) + return account + } /* c8 ignore stop */ /** diff --git a/packages/w3up-client/test/account.test.js b/packages/w3up-client/test/account.test.js index 9e1c706f8..9336b682c 100644 --- a/packages/w3up-client/test/account.test.js +++ b/packages/w3up-client/test/account.test.js @@ -67,6 +67,18 @@ export const testAccount = { assert.ok(two[Account.fromEmail(bobEmail)].toEmail(), bobEmail) }, + 'client.login': async (assert, { client, mail, grantAccess }) => { + const account = client.login('alice@web.mail') + + await grantAccess(await mail.take()) + + const alice = await account + assert.deepEqual(alice.toEmail(), 'alice@web.mail') + + const accounts = client.accounts() + assert.deepEqual(Object.keys(accounts), [alice.did()]) + }, + 'create account and provision space': async ( assert, { client, mail, grantAccess } @@ -200,6 +212,22 @@ export const testAccount = { assert.ok(plan?.product, 'did:web:free.web3.storage') }, + + 'space.save': async (assert, { client, mail, grantAccess }) => { + const space = await client.createSpace('test') + assert.deepEqual(client.spaces(), []) + + console.log(space) + + const result = await space.save() + assert.ok(result.ok) + + const spaces = client.spaces() + assert.deepEqual(spaces.length, 1) + assert.deepEqual(spaces[0].did(), space.did()) + + assert.deepEqual(client.currentSpace()?.did(), space.did()) + }, } Test.test({ Account: testAccount })