Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: w3up client login #1120

Merged
merged 4 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/access-client/src/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
}

/**
Expand Down
33 changes: 29 additions & 4 deletions packages/access-client/src/space.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@ import * as Access from './access.js'
* @typedef {object} Model
* @property {ED25519.EdSigner} signer
* @property {string} name
* @property {API.Agent} [agent]
*/

/**
* Generates a new space.
*
* @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 })
}

/**
Expand All @@ -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 })
}

/**
Expand Down Expand Up @@ -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<API.Result<API.Unit, Error>>}
*/
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
Expand Down Expand Up @@ -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
*/
Expand Down
6 changes: 5 additions & 1 deletion packages/w3up-client/src/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<API.Result<Account, Error>>}
*/
export const login = async ({ agent }, email) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/w3up-client/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
*
Expand All @@ -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 */

/**
Expand Down
28 changes: 28 additions & 0 deletions packages/w3up-client/test/account.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('[email protected]')

await grantAccess(await mail.take())

const alice = await account
assert.deepEqual(alice.toEmail(), '[email protected]')

const accounts = client.accounts()
assert.deepEqual(Object.keys(accounts), [alice.did()])
},

'create account and provision space': async (
assert,
{ client, mail, grantAccess }
Expand Down Expand Up @@ -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 })
Loading