Skip to content

Commit

Permalink
feat: bring your own principal (#672)
Browse files Browse the repository at this point in the history
This is web3-storage/w3up-client#74 ported here.

---

This PR allows passing your own principal to the client `create` factory
function instead of loading one from a pre-existing store. The caveat is
that if you pass your own and the store is not empty then it needs to
match the principal that was loaded from the store (otherwise any saved
delegations will be unusable).

This makes
https://gist.github.com/alanshaw/e949abfcf6728f590ac9fa083dba5648#on-the-server
a little easier, you won't need to install `@web3-storage/access` or
deal with the `AgentData` class.

Currently:

```js
import * as Signer from '@ucanto/principal/ed25519'
import { AgentData } from '@web3-storage/access/agent'
import { Client } from '@web3-storage/w3up-client'

const principal = Signer.parse(process.env.KEY)
const data = await AgentData.create({ principal })
const client = new Client(data)
```

After this PR:

```js
import * as Signer from '@ucanto/principal/ed25519'
import * as Client from '@web3-storage/w3up-client'

const principal = Signer.parse(process.env.KEY)
const client = await Client.create({ principal })
```
  • Loading branch information
Alan Shaw authored Mar 29, 2023
1 parent a16f412 commit 4586df2
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 3 deletions.
12 changes: 10 additions & 2 deletions packages/w3up-client/src/index.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ import { Client } from './client.js'
export async function create(options = {}) {
const store = options.store ?? new StoreConf({ profile: 'w3up-client' })
const raw = await store.load()
if (raw) return new Client(AgentData.fromExport(raw, { store }), options)
const principal = await generate()
if (raw) {
const data = AgentData.fromExport(raw, { store })
if (options.principal && data.principal.did() !== options.principal.did()) {
throw new Error(
`store cannot be used with ${options.principal.did()}, stored principal and passed principal must match`
)
}
return new Client(data, options)
}
const principal = options.principal ?? (await generate())
const data = await AgentData.create({ principal }, { store })
return new Client(data, options)
}
Expand Down
8 changes: 7 additions & 1 deletion packages/w3up-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
type AgentDataExport,
} from '@web3-storage/access/types'
import { type Service as UploadService } from '@web3-storage/upload-client/types'
import { type ConnectionView } from '@ucanto/interface'
import type { ConnectionView, Signer, DID } from '@ucanto/interface'
import { type Client } from './client'

export interface ServiceConf {
Expand All @@ -21,6 +21,12 @@ export interface ClientFactoryOptions {
* Service DID and URL configuration.
*/
serviceConf?: ServiceConf
/**
* Use this principal to sign UCANs. Note: if the store is non-empty and the
* principal saved in the store is not the same principal as the one passed
* here an error will be thrown.
*/
principal?: Signer<DID<'key'>>
}

export type ClientFactory = (options?: ClientFactoryOptions) => Promise<Client>
Expand Down
24 changes: 24 additions & 0 deletions packages/w3up-client/test/index.node.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import assert from 'assert'
import { Signer } from '@ucanto/principal/ed25519'
import { EdDSA } from '@ipld/dag-ucan/signature'
import { StoreConf } from '@web3-storage/access/stores/store-conf'
import { create } from '../src/index.node.js'
Expand All @@ -20,4 +21,27 @@ describe('create', () => {

assert.equal(client0.agent().did(), client1.agent().did())
})

it('should allow BYO principal', async () => {
const store = new StoreConf({ profile: 'w3up-client-test' })
await store.reset()

const principal = await Signer.generate()
const client = await create({ principal, store })

assert.equal(client.agent().did(), principal.did())
})

it('should throw for mismatched BYO principal', async () => {
const store = new StoreConf({ profile: 'w3up-client-test' })
await store.reset()

const principal0 = await Signer.generate()
await create({ principal: principal0, store })

const principal1 = await Signer.generate()
await assert.rejects(create({ principal: principal1, store }), {
message: `store cannot be used with ${principal1.did()}, stored principal and passed principal must match`,
})
})
})

0 comments on commit 4586df2

Please sign in to comment.