From 4586df25fc8b43dab0191c77ef70620fbf276e1c Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 29 Mar 2023 14:22:39 +0100 Subject: [PATCH] feat: bring your own principal (#672) This is https://github.com/web3-storage/w3up-client/pull/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 }) ``` --- packages/w3up-client/src/index.node.js | 12 ++++++++-- packages/w3up-client/src/types.ts | 8 ++++++- packages/w3up-client/test/index.node.test.js | 24 ++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/w3up-client/src/index.node.js b/packages/w3up-client/src/index.node.js index 198795878..c7dd0deb6 100644 --- a/packages/w3up-client/src/index.node.js +++ b/packages/w3up-client/src/index.node.js @@ -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) } diff --git a/packages/w3up-client/src/types.ts b/packages/w3up-client/src/types.ts index ab25f737e..697194a6e 100644 --- a/packages/w3up-client/src/types.ts +++ b/packages/w3up-client/src/types.ts @@ -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 { @@ -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> } export type ClientFactory = (options?: ClientFactoryOptions) => Promise diff --git a/packages/w3up-client/test/index.node.test.js b/packages/w3up-client/test/index.node.test.js index 09aedc98e..5a13b960a 100644 --- a/packages/w3up-client/test/index.node.test.js +++ b/packages/w3up-client/test/index.node.test.js @@ -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' @@ -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`, + }) + }) })