diff --git a/.github/workflows/tests-ci.yml b/.github/workflows/tests-ci.yml index 6b9704716..cf98b6b5f 100644 --- a/.github/workflows/tests-ci.yml +++ b/.github/workflows/tests-ci.yml @@ -94,12 +94,6 @@ jobs: - name: Install docker run: brew install docker && brew install docker-compose # && colima start - - name: Update qemu to work around https://github.com/actions/runner-images/issues/8104 - run: | - brew remove --ignore-dependencies qemu && - curl -o ./qemu.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/dc0669eca9479e9eeb495397ba3a7480aaa45c2e/Formula/qemu.rb && - brew install ./qemu.rb - - name: Start docker run: colima start diff --git a/package-lock.json b/package-lock.json index 51a9ac96b..f29f206be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -731,84 +731,6 @@ "@stablelib/xchacha20": "^1.0.1" } }, - "node_modules/@tbd54566975/dwn-sdk-js": { - "version": "0.2.1", - "license": "Apache-2.0", - "dependencies": { - "@ipld/dag-cbor": "9.0.3", - "@js-temporal/polyfill": "0.4.4", - "@noble/ed25519": "2.0.0", - "@noble/secp256k1": "2.0.0", - "abstract-level": "1.0.3", - "ajv": "8.12.0", - "blockstore-core": "4.2.0", - "cross-fetch": "4.0.0", - "eciesjs": "0.4.0", - "flat": "5.0.2", - "interface-blockstore": "5.2.3", - "interface-store": "5.1.2", - "ipfs-unixfs-exporter": "13.1.5", - "ipfs-unixfs-importer": "15.1.5", - "level": "8.0.0", - "lodash": "4.17.21", - "lru-cache": "9.1.2", - "ms": "2.1.3", - "multiformats": "11.0.2", - "randombytes": "2.1.0", - "readable-stream": "4.4.0", - "ulid": "2.3.0", - "uuid": "8.3.2", - "varint": "6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@tbd54566975/dwn-sdk-js/node_modules/cross-fetch": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@tbd54566975/dwn-sdk-js/node_modules/lru-cache": { - "version": "9.1.2", - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/@tbd54566975/dwn-sdk-js/node_modules/node-fetch": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/@tbd54566975/dwn-sdk-js/node_modules/readable-stream": { - "version": "4.4.0", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@types/chai": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", @@ -5078,6 +5000,11 @@ "node": ">=10" } }, + "node_modules/layerr": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layerr/-/layerr-2.0.1.tgz", + "integrity": "sha512-z0730CwG/JO24evdORnyDkwG1Q7b7mF2Tp1qRQ0YvrMMARbt1DFG694SOv439Gm7hYKolyZyaB49YIrYIfZBdg==" + }, "node_modules/level": { "version": "8.0.0", "license": "MIT", @@ -7776,11 +7703,15 @@ "npm": ">=7.0.0" } }, - "node_modules/ulid": { - "version": "2.3.0", - "license": "MIT", - "bin": { - "ulid": "bin/cli.js" + "node_modules/ulidx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ulidx/-/ulidx-2.1.0.tgz", + "integrity": "sha512-DlMi97oP9HASI3kLCjBlOhAG1SoisUrEqC2PJ7itiFbq9q5Zo0JejupXeu2Gke99W62epNzA4MFNToNiq8A5LA==", + "dependencies": { + "layerr": "^2.0.1" + }, + "engines": { + "node": ">=16" } }, "node_modules/unbox-primitive": { @@ -8339,10 +8270,10 @@ }, "packages/agent": { "name": "@web5/agent", - "version": "0.2.0", + "version": "0.2.1", "license": "Apache-2.0", "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.1", + "@tbd54566975/dwn-sdk-js": "0.2.3", "@web5/common": "0.2.0", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0", @@ -8383,16 +8314,99 @@ "node": ">=18.0.0" } }, + "packages/agent/node_modules/@tbd54566975/dwn-sdk-js": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.2.3.tgz", + "integrity": "sha512-T3Yy6kY6zftdVgsX2C0D2bIAmWQQVCFrLB95+BN/zoAAA29LogOr807Kx15QHrKmILWidVfYt/ZwsPHl4k5bDQ==", + "dependencies": { + "@ipld/dag-cbor": "9.0.3", + "@js-temporal/polyfill": "0.4.4", + "@noble/ed25519": "2.0.0", + "@noble/secp256k1": "2.0.0", + "abstract-level": "1.0.3", + "ajv": "8.12.0", + "blockstore-core": "4.2.0", + "cross-fetch": "4.0.0", + "eciesjs": "0.4.0", + "flat": "5.0.2", + "interface-blockstore": "5.2.3", + "interface-store": "5.1.2", + "ipfs-unixfs-exporter": "13.1.5", + "ipfs-unixfs-importer": "15.1.5", + "level": "8.0.0", + "lodash": "4.17.21", + "lru-cache": "9.1.2", + "ms": "2.1.3", + "multiformats": "11.0.2", + "randombytes": "2.1.0", + "readable-stream": "4.4.0", + "ulidx": "2.1.0", + "uuid": "8.3.2", + "varint": "6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "packages/agent/node_modules/@tbd54566975/dwn-sdk-js/node_modules/readable-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", + "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "packages/agent/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "packages/agent/node_modules/lru-cache": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", + "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", + "engines": { + "node": "14 || >=16.14" + } + }, + "packages/agent/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "packages/api": { "name": "@web5/api", "version": "0.8.1", "license": "Apache-2.0", "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.1", - "@web5/agent": "0.2.0", + "@tbd54566975/dwn-sdk-js": "0.2.3", + "@web5/agent": "0.2.1", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0", - "@web5/user-agent": "0.2.0", + "@web5/user-agent": "0.2.1", "level": "8.0.0", "ms": "2.1.3", "readable-stream": "4.4.2", @@ -8435,6 +8449,89 @@ "node": ">=18.0.0" } }, + "packages/api/node_modules/@tbd54566975/dwn-sdk-js": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.2.3.tgz", + "integrity": "sha512-T3Yy6kY6zftdVgsX2C0D2bIAmWQQVCFrLB95+BN/zoAAA29LogOr807Kx15QHrKmILWidVfYt/ZwsPHl4k5bDQ==", + "dependencies": { + "@ipld/dag-cbor": "9.0.3", + "@js-temporal/polyfill": "0.4.4", + "@noble/ed25519": "2.0.0", + "@noble/secp256k1": "2.0.0", + "abstract-level": "1.0.3", + "ajv": "8.12.0", + "blockstore-core": "4.2.0", + "cross-fetch": "4.0.0", + "eciesjs": "0.4.0", + "flat": "5.0.2", + "interface-blockstore": "5.2.3", + "interface-store": "5.1.2", + "ipfs-unixfs-exporter": "13.1.5", + "ipfs-unixfs-importer": "15.1.5", + "level": "8.0.0", + "lodash": "4.17.21", + "lru-cache": "9.1.2", + "ms": "2.1.3", + "multiformats": "11.0.2", + "randombytes": "2.1.0", + "readable-stream": "4.4.0", + "ulidx": "2.1.0", + "uuid": "8.3.2", + "varint": "6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "packages/api/node_modules/@tbd54566975/dwn-sdk-js/node_modules/readable-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", + "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "packages/api/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "packages/api/node_modules/lru-cache": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", + "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", + "engines": { + "node": "14 || >=16.14" + } + }, + "packages/api/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "packages/common": { "name": "@web5/common", "version": "0.2.0", @@ -8663,10 +8760,10 @@ }, "packages/identity-agent": { "name": "@web5/identity-agent", - "version": "0.2.0", + "version": "0.2.1", "license": "Apache-2.0", "dependencies": { - "@web5/agent": "0.2.0", + "@web5/agent": "0.2.1", "@web5/api": "0.8.1" }, "devDependencies": { @@ -8703,10 +8800,10 @@ }, "packages/proxy-agent": { "name": "@web5/proxy-agent", - "version": "0.2.0", + "version": "0.2.1", "license": "Apache-2.0", "dependencies": { - "@web5/agent": "0.2.0", + "@web5/agent": "0.2.1", "@web5/common": "0.2.0", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0" @@ -8745,10 +8842,10 @@ }, "packages/user-agent": { "name": "@web5/user-agent", - "version": "0.2.0", + "version": "0.2.1", "license": "Apache-2.0", "dependencies": { - "@web5/agent": "0.2.0", + "@web5/agent": "0.2.1", "@web5/common": "0.2.0", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0" diff --git a/packages/agent/package.json b/packages/agent/package.json index f5ce9b741..025ba75e7 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@web5/agent", - "version": "0.2.0", + "version": "0.2.1", "type": "module", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -67,7 +67,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.1", + "@tbd54566975/dwn-sdk-js": "0.2.3", "@web5/common": "0.2.0", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0", diff --git a/packages/agent/src/dwn-manager.ts b/packages/agent/src/dwn-manager.ts index 756badf7e..d610cd84c 100644 --- a/packages/agent/src/dwn-manager.ts +++ b/packages/agent/src/dwn-manager.ts @@ -1,26 +1,19 @@ -import type { +import { GenericMessage, - SignatureInput, MessagesGetReply, RecordsReadReply, UnionMessageReply, - EncryptionProperty, RecordsWriteMessage, RecordsWriteOptions, - PrivateJwk as DwnPrivateKeyJwk, + Signer, } from '@tbd54566975/dwn-sdk-js'; import { Jose } from '@web5/crypto'; import { DidResolver } from '@web5/dids'; import { Readable } from 'readable-stream'; import * as didUtils from '@web5/dids/utils'; -import { Convert, removeUndefinedProperties } from '@web5/common'; +import { Convert } from '@web5/common'; -import { - EventLogLevel, - DataStoreLevel, - MessageStoreLevel, -} from '@tbd54566975/dwn-sdk-js/stores'; import { Cid, Dwn, @@ -36,6 +29,9 @@ import { ProtocolsQuery, DwnInterfaceName, ProtocolsConfigure, + EventLogLevel, + DataStoreLevel, + MessageStoreLevel, } from '@tbd54566975/dwn-sdk-js'; import type { DwnRpcRequest, DwnResponse,ProcessDwnRequest, SendDwnRequest, Web5ManagedAgent } from './types/agent.js'; @@ -276,42 +272,13 @@ export class DwnManager { } } - const signingKeyId = await this.getAuthorSigningKeyId({ did: request.author }); - - // ! TODO: Remove this once DWN SDK supports external signers. - const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId }); - - // TODO: Figure out how to narrow this type. - const messageCreateInput = { - ...request.messageOptions, - authorizationSignatureInput: dwnSignatureInput - }; + const dwnAuthorizationSigner = await this.constructDwnAuthorizationSigner(request.author); const messageCreator = dwnMessageCreators[request.messageType]; - - // ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. - // MP Step 1: Store the original methods. - const originalCreateAuthorization = RecordsWrite.createAuthorization; - const originalSignAsAuthorization = Message.signAsAuthorization; - // MP Step 2: Replace the methods. - RecordsWrite.createAuthorization = ( - recordId: string, - contextId: string | undefined, - descriptorCid: string, - attestation: GeneralJws | undefined, - encryption: EncryptionProperty | undefined, - ) => this.createAuthorization(recordId, contextId, descriptorCid, attestation, encryption, signingKeyId); - Message.signAsAuthorization = ( - descriptor: GenericMessage['descriptor'], - signatureInput: SignatureInput, - permissionsGrantId?: string, - ) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId); - // MP Step 3: Call the method that required monkey patching. - const dwnMessage = await messageCreator.create(messageCreateInput as any); - // MP Step 4: Restore the original methods. - RecordsWrite.createAuthorization = originalCreateAuthorization; - Message.signAsAuthorization = originalSignAsAuthorization; - // ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. + const dwnMessage = await messageCreator.create({ + ...request.messageOptions, + authorizationSigner: dwnAuthorizationSigner + }); return { message: dwnMessage.toJSON(), dataStream: readableStream }; } @@ -321,11 +288,8 @@ export class DwnManager { }): Promise { const { did } = options; - // Get the agent instance. - const agent = this.agent; - // Get the method-specific default signing key. - const signingKeyId = await agent.didManager.getDefaultSigningKey({ did }); + const signingKeyId = await this.agent.didManager.getDefaultSigningKey({ did }); if (!signingKeyId) { throw new Error (`DwnManager: Unable to determine signing key for author: '${did}'`); @@ -334,6 +298,41 @@ export class DwnManager { return signingKeyId; } + private async constructDwnAuthorizationSigner(author: string): Promise { + const signingKeyId = await this.getAuthorSigningKeyId({ did: author }); + + /** + * DID keys stored in KeyManager use the canonicalId as an alias, so + * normalize the signing key ID before attempting to retrieve the key. + */ + const parsedDid = didUtils.parseDid({ didUrl: signingKeyId }); + if (!parsedDid) throw new Error(`DidIonMethod: Unable to parse DID: ${signingKeyId}`); + const normalizedDid = parsedDid.did.split(':', 3).join(':'); + const normalizedSigningKeyId = `${normalizedDid}#${parsedDid.fragment}`; + + const signingKey = await this.agent.keyManager.getKey({ keyRef: normalizedSigningKeyId }); + if (!isManagedKeyPair(signingKey)) { + throw new Error(`DwnManager: Signing key not found for author: '${author}'`); + } + + const { alg } = Jose.webCryptoToJose(signingKey.privateKey.algorithm); + if (alg === undefined) { + throw Error(`No algorithm provided to sign with key ID ${signingKeyId}`); + } + + return { + keyId : signingKeyId, + algorithm : alg, + sign : async (content: Uint8Array): Promise => { + return await this.agent.keyManager.sign({ + algorithm : signingKey.privateKey.algorithm, + data : content, + keyRef : normalizedSigningKeyId + }); + } + }; + } + private async getDwnMessage(options: { author: string, messageType: string, @@ -341,28 +340,14 @@ export class DwnManager { }): Promise { const { author, messageType, messageCid } = options; - const signingKeyId = await this.getAuthorSigningKeyId({ did: author }); + const dwnAuthorizationSigner = await this.constructDwnAuthorizationSigner(author); - // ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. - const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId }); - // MP Step 1: Store the original methods. - const originalSignAsAuthorization = Message.signAsAuthorization; - // MP Step 2: Replace the methods. - Message.signAsAuthorization = ( - descriptor: GenericMessage['descriptor'], - signatureInput: SignatureInput, - permissionsGrantId?: string, - ) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId); - // MP Step 3: Call the method that required monkey patching. const messagesGet = await MessagesGet.create({ - authorizationSignatureInput : dwnSignatureInput, - messageCids : [messageCid] + authorizationSigner : dwnAuthorizationSigner, + messageCids : [messageCid] }); - // MP Step 4: Restore the original methods. - Message.signAsAuthorization = originalSignAsAuthorization; - // ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. - const result: MessagesGetReply = await this._dwn.processMessage(author, messagesGet.toJSON()); + const result: MessagesGetReply = await this._dwn.processMessage(author, messagesGet.message); if (!(result.messages && result.messages.length === 1)) { throw new Error('TODO: figure out error message'); @@ -388,8 +373,10 @@ export class DwnManager { dwnMessage.data = new Blob([dataBytes]); } else { const recordsRead = await RecordsRead.create({ - authorizationSignatureInput : dwnSignatureInput, - recordId : writeMessage.recordId + authorizationSigner : dwnAuthorizationSigner, + filter : { + recordId: writeMessage.recordId + } }); const reply = await this._dwn.processMessage(author, recordsRead.toJSON()) as RecordsReadReply; @@ -406,157 +393,6 @@ export class DwnManager { return dwnMessage; } - - /** - * The following methods are a temporary workaround that should be removed - * once DWN SDK implements support for an external signer. - * - * - createAuthorization() - * - createJws() - * - getTempSignatureInput() - * - signAsAuthorization() - */ - - private async createAuthorization( - recordId: string, - contextId: string | undefined, - descriptorCid: string, - attestation: GeneralJws | undefined, - encryption: EncryptionProperty | undefined, - signingKeyId: string, - ): Promise { - const authorizationPayload: RecordsWriteAuthorizationPayload = { - recordId, - descriptorCid - }; - - const attestationCid = attestation ? await Cid.computeCid(attestation) : undefined; - const encryptionCid = encryption ? await Cid.computeCid(encryption) : undefined; - - if (contextId !== undefined) { authorizationPayload.contextId = contextId; } // assign `contextId` only if it is defined - if (attestationCid !== undefined) { authorizationPayload.attestationCid = attestationCid; } // assign `attestationCid` only if it is defined - if (encryptionCid !== undefined) { authorizationPayload.encryptionCid = encryptionCid; } // assign `encryptionCid` only if it is defined - - const authorizationPayloadU8A = Convert.object(authorizationPayload).toUint8Array(); - - const jws = await this.createJws(authorizationPayloadU8A, [signingKeyId]); - - return jws; - } - - private async createJws(payload: Uint8Array, signingKeyIds: string[] = []): Promise { - // Get the agent instance. - const agent = this.agent; - - // Begin constructing a JWS. - const jws: GeneralJws = { - payload : Convert.uint8Array(payload).toBase64Url(), - signatures : [] - }; - - for (const signingKeyId of signingKeyIds) { - - /** DID keys stored in KeyManager use the canonicalId as an alias, so - * normalize the signing key ID before attempting to retrieve the key. **/ - const parsedDid = didUtils.parseDid({ didUrl: signingKeyId }); - if (!parsedDid) throw new Error(`DidIonMethod: Unable to parse DID: ${signingKeyId}`); - const normalizedDid = parsedDid.did.split(':', 3).join(':'); - const normalizedSigningKeyId = `${normalizedDid}#${parsedDid.fragment}`; - - // Attempt to retrieve the signing key. - const signingKey = await agent.keyManager.getKey({ keyRef: normalizedSigningKeyId }); - - if (!isManagedKeyPair(signingKey)) { - throw new Error (`DwnManager: Signing key not found for author: '${normalizedDid}'`); - } - - // Get the JWS alg parameter given the key pair. - const { alg } = Jose.webCryptoToJose(signingKey.privateKey.algorithm); - - // Construct the JWS protected header. - const protectedHeader = Convert.object( - { alg, kid: signingKeyId } - ).toBase64Url(); - - // Concatenate the dot-separated header and payload and convert to bytes. - const headerAndPayload = Convert.string( - `${protectedHeader}.${jws.payload}` - ).toUint8Array(); - - // Sign the JWS. - const signatureBytes = await agent.keyManager.sign({ - algorithm : signingKey.privateKey.algorithm, - data : headerAndPayload, - keyRef : signingKey.privateKey.id - }); - const signature = Convert.uint8Array(signatureBytes).toBase64Url(); - - jws.signatures.push({ protected: protectedHeader, signature }); - } - - return jws; - } - - private getTempSignatureInput({ signingKeyId }: { signingKeyId: string }): SignatureInput { - const privateJwk: DwnPrivateKeyJwk = { - alg : 'placeholder', - d : 'placeholder', - crv : 'Ed25519', - kty : 'placeholder', - x : 'placeholder' - }; - - const protectedHeader = { - alg : 'placeholder', - kid : signingKeyId - }; - - const dwnSignatureInput: SignatureInput = { privateJwk, protectedHeader }; - - return dwnSignatureInput; - } - - private async signAsAuthorization( - descriptor: GenericMessage['descriptor'], - signingKeyId: string, - permissionsGrantId?: string - ): Promise { - const descriptorCid = await Cid.computeCid(descriptor); - - const authorizationPayload = { descriptorCid, permissionsGrantId }; - removeUndefinedProperties(authorizationPayload); - - const authorizationPayloadU8A = Convert.object(authorizationPayload).toUint8Array(); - - const jws = await this.createJws(authorizationPayloadU8A, [signingKeyId]); - - return jws; - } - - - - - - - - - - - - - - - - - - - - - - - - - /** * ADDED TO GET SYNC WORKING @@ -572,42 +408,14 @@ export class DwnManager { }): Promise { const { author, messageOptions, messageType } = options; - const signingKeyId = await this.getAuthorSigningKeyId({ did: author }); - - // ! TODO: Remove this once DWN SDK supports external signers. - const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId }); - - // TODO: Figure out how to narrow this type. - const messageCreateInput = { - ...messageOptions, - authorizationSignatureInput: dwnSignatureInput - }; + const dwnAuthorizationSigner = await this.constructDwnAuthorizationSigner(author); const messageCreator = dwnMessageCreators[messageType]; - // ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. - // MP Step 1: Store the original methods. - const originalCreateAuthorization = RecordsWrite.createAuthorization; - const originalSignAsAuthorization = Message.signAsAuthorization; - // MP Step 2: Replace the methods. - RecordsWrite.createAuthorization = ( - recordId: string, - contextId: string | undefined, - descriptorCid: string, - attestation: GeneralJws | undefined, - encryption: EncryptionProperty | undefined, - ) => this.createAuthorization(recordId, contextId, descriptorCid, attestation, encryption, signingKeyId); - Message.signAsAuthorization = ( - descriptor: GenericMessage['descriptor'], - signatureInput: SignatureInput, - permissionsGrantId?: string, - ) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId); - // MP Step 3: Call the method that required monkey patching. - const dwnMessage = await messageCreator.create(messageCreateInput as any); - // MP Step 4: Restore the original methods. - RecordsWrite.createAuthorization = originalCreateAuthorization; - Message.signAsAuthorization = originalSignAsAuthorization; - // ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. + const dwnMessage = await messageCreator.create({ + ...messageOptions, + authorizationSigner: dwnAuthorizationSigner + }); return dwnMessage; } diff --git a/packages/agent/src/test-managed-agent.ts b/packages/agent/src/test-managed-agent.ts index 9e50e783c..c5eda8b77 100644 --- a/packages/agent/src/test-managed-agent.ts +++ b/packages/agent/src/test-managed-agent.ts @@ -3,10 +3,9 @@ import type { DidResolutionResult, DidResolverCache, PortableDid } from '@web5/d import { Level } from 'level'; import { Jose } from '@web5/crypto'; -import { Dwn } from '@tbd54566975/dwn-sdk-js'; +import { Dwn, MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js'; import { LevelStore, MemoryStore } from '@web5/common'; import { DidIonMethod, DidKeyMethod, DidResolver, DidResolverCacheLevel } from '@web5/dids'; -import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/stores'; import type { Web5ManagedAgent } from './types/agent.js'; diff --git a/packages/agent/tests/dwn-manager.spec.ts b/packages/agent/tests/dwn-manager.spec.ts index b491e2c30..105110058 100644 --- a/packages/agent/tests/dwn-manager.spec.ts +++ b/packages/agent/tests/dwn-manager.spec.ts @@ -73,6 +73,13 @@ describe('DwnManager', () => { }); }); + describe('#create', () => { + it('works with no options provided', async () => { + const dwnManager = await DwnManager.create(); + expect(dwnManager).to.not.be.undefined; + }); + }); + describe(`with dwn data stores`, () => { let testAgent: TestManagedAgent; @@ -355,7 +362,9 @@ describe('DwnManager', () => { target : identity.did, messageType : 'RecordsRead', messageOptions : { - recordId: writeMessage.recordId + filter: { + recordId: writeMessage.recordId + } } }); @@ -530,7 +539,9 @@ describe('DwnManager', () => { target : identity.did, messageType : 'RecordsRead', messageOptions : { - recordId: message.recordId + filter: { + recordId: message.recordId + } } }); diff --git a/packages/agent/tests/utils/test-agent.ts b/packages/agent/tests/utils/test-agent.ts index aef599935..730c9fa0b 100644 --- a/packages/agent/tests/utils/test-agent.ts +++ b/packages/agent/tests/utils/test-agent.ts @@ -1,7 +1,6 @@ import { Level } from 'level'; -import { Dwn } from '@tbd54566975/dwn-sdk-js'; +import { Dwn, MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js'; import { DidIonMethod, DidKeyMethod, DidResolver } from '@web5/dids'; -import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/stores'; import type { AppDataStore } from '../../src/app-data-store.js'; import type { diff --git a/packages/api/package.json b/packages/api/package.json index 71adba928..26aaf0751 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -75,11 +75,11 @@ "node": ">=18.0.0" }, "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.1", - "@web5/agent": "0.2.0", + "@tbd54566975/dwn-sdk-js": "0.2.3", + "@web5/agent": "0.2.1", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0", - "@web5/user-agent": "0.2.0", + "@web5/user-agent": "0.2.1", "level": "8.0.0", "ms": "2.1.3", "readable-stream": "4.4.2", diff --git a/packages/api/src/dwn-api.ts b/packages/api/src/dwn-api.ts index dc70c5884..155cae9ce 100644 --- a/packages/api/src/dwn-api.ts +++ b/packages/api/src/dwn-api.ts @@ -21,7 +21,7 @@ import { Protocol } from './protocol.js'; import { dataToBlob } from './utils.js'; export type ProtocolsConfigureRequest = { - message: Omit; + message: Omit; } export type ProtocolsConfigureResponse = { @@ -35,7 +35,7 @@ export type ProtocolsQueryReplyEntry = { export type ProtocolsQueryRequest = { from?: string; - message: Omit + message: Omit } export type ProtocolsQueryResponse = { @@ -50,13 +50,13 @@ export type RecordsCreateResponse = RecordsWriteResponse; export type RecordsCreateFromRequest = { author: string; data: unknown; - message?: Omit; + message?: Omit; record: Record; } export type RecordsDeleteRequest = { from?: string; - message: Omit; + message: Omit; } export type RecordsDeleteResponse = { @@ -66,7 +66,7 @@ export type RecordsDeleteResponse = { export type RecordsQueryRequest = { /** The from property indicates the DID to query from and return results. */ from?: string; - message: Omit; + message: Omit; } export type RecordsQueryResponse = { @@ -77,7 +77,7 @@ export type RecordsQueryResponse = { export type RecordsReadRequest = { /** The from property indicates the DID to read from and return results fro. */ from?: string; - message: Omit; + message: Omit; } export type RecordsReadResponse = { @@ -87,7 +87,7 @@ export type RecordsReadResponse = { export type RecordsWriteRequest = { data: unknown; - message?: Omit, 'authorizationSignatureInput'>; + message?: Omit, 'authorizationSigner'>; store?: boolean; } diff --git a/packages/api/tests/dwn-api.spec.ts b/packages/api/tests/dwn-api.spec.ts index d9d5d718f..2374a7899 100644 --- a/packages/api/tests/dwn-api.spec.ts +++ b/packages/api/tests/dwn-api.spec.ts @@ -478,7 +478,9 @@ describe('DwnApi', () => { const result = await dwn.records.read({ message: { - recordId: writeResult.record!.id + filter: { + recordId: writeResult.record!.id + } } }); @@ -503,7 +505,9 @@ describe('DwnApi', () => { const result = await dwn.records.read({ message: { - recordId: writeResult.record!.id + filter: { + recordId: writeResult.record!.id + } } }); @@ -534,7 +538,9 @@ describe('DwnApi', () => { const result = await dwn.records.read({ from : alice.did, message : { - recordId: writeResult.record!.id + filter: { + recordId: writeResult.record!.id + } } }); @@ -551,7 +557,9 @@ describe('DwnApi', () => { const result = await dwn.records.read({ from : bob.did, message : { - recordId: 'non-existent-id' + filter: { + recordId: 'non-existent-id' + } } }); diff --git a/packages/api/tests/record.spec.ts b/packages/api/tests/record.spec.ts index bfb4aac6f..ba3e881d8 100644 --- a/packages/api/tests/record.spec.ts +++ b/packages/api/tests/record.spec.ts @@ -34,6 +34,7 @@ chai.use(chaiAsPromised); // NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage // Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule import { webcrypto } from 'node:crypto'; +import { PrivateKeySigner } from '@tbd54566975/dwn-sdk-js'; // @ts-ignore if (!globalThis.crypto) globalThis.crypto = webcrypto; @@ -105,21 +106,17 @@ describe('Record', () => { const encryptionPublicKeyJwk = aliceDid.keySet.verificationMethodKeys!.find(keyPair => keyPair.publicKeyJwk.kid === encryptionKeyIdFragment)!.publicKeyJwk; // RecordsWriteMessage properties that can be pre-defined - const attestation = [{ - privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, - protectedHeader : { - alg : signingPrivateKeyJwk.alg as string, - kid : signingKeyId - } - }]; - - const authorization = { - privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, - protectedHeader : { - alg : signingPrivateKeyJwk.alg as string, - kid : signingKeyId - } - }; + const attestation = [new PrivateKeySigner({ + privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, + algorithm : signingPrivateKeyJwk.alg as string, + keyId : signingKeyId, + })]; + + const authorization = new PrivateKeySigner({ + privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, + algorithm : signingPrivateKeyJwk.alg as string, + keyId : signingKeyId, + }); const encryptionInput: EncryptionInput = { algorithm : EncryptionAlgorithm.Aes256Ctr, @@ -149,8 +146,8 @@ describe('Record', () => { // Create a parent record to reference in the RecordsWriteMessage used for validation const parentRecorsWrite = await RecordsWrite.create({ - authorizationSignatureInput : authorization, - data : new Uint8Array(await dataBlob.arrayBuffer()), + authorizationSigner : authorization, + data : new Uint8Array(await dataBlob.arrayBuffer()), dataFormat, protocol, protocolPath, @@ -159,12 +156,12 @@ describe('Record', () => { // Create a RecordsWriteMessage const recordsWrite = await RecordsWrite.create({ - attestationSignatureInputs : attestation, - authorizationSignatureInput : authorization, - data : new Uint8Array(await dataBlob.arrayBuffer()), + attestationSigners : attestation, + authorizationSigner : authorization, + data : new Uint8Array(await dataBlob.arrayBuffer()), dataFormat, encryptionInput, - parentId : parentRecorsWrite.recordId, + parentId : parentRecorsWrite.recordId, protocol, protocolPath, published, @@ -241,7 +238,7 @@ describe('Record', () => { expect(status.code).to.equal(202); // Read the record that was just created. - const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { filter: { recordId: record!.id }}}); expect(readRecordStatus.code).to.equal(200); @@ -286,7 +283,7 @@ describe('Record', () => { expect(status.code).to.equal(202); // Read the record that was just created. - const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { filter: { recordId: record!.id }}}); expect(readRecordStatus.code).to.equal(200); @@ -333,7 +330,7 @@ describe('Record', () => { expect(status.code).to.equal(202); // Read the record that was just created. - const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { filter: { recordId: record!.id }}}); expect(readRecordStatus.code).to.equal(200); @@ -378,7 +375,7 @@ describe('Record', () => { expect(status.code).to.equal(202); // Read the record that was just created. - const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { filter: { recordId: record!.id }}}); expect(readRecordStatus.code).to.equal(200); @@ -422,7 +419,7 @@ describe('Record', () => { expect(status.code).to.equal(202); // Read the record that was just created. - const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { filter: { recordId: record!.id }}}); expect(readRecordStatus.code).to.equal(200); @@ -463,7 +460,7 @@ describe('Record', () => { expect(status.code).to.equal(202); // Read the record that was just created. - const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { filter: { recordId: record!.id }}}); expect(readRecordStatus.code).to.equal(200); @@ -871,21 +868,17 @@ describe('Record', () => { const encryptionPublicKeyJwk = aliceDid.keySet.verificationMethodKeys!.find(keyPair => keyPair.publicKeyJwk.kid === encryptionKeyIdFragment)!.publicKeyJwk; // RecordsWriteMessage properties that can be pre-defined - const attestation = [{ - privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, - protectedHeader : { - alg : signingPrivateKeyJwk.alg as string, - kid : signingKeyId - } - }]; - - const authorization = { - privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, - protectedHeader : { - alg : signingPrivateKeyJwk.alg as string, - kid : signingKeyId - } - }; + const attestation = [new PrivateKeySigner({ + privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, + algorithm : signingPrivateKeyJwk.alg as string, + keyId : signingKeyId, + })]; + + const authorization = new PrivateKeySigner({ + privateJwk : signingPrivateKeyJwk as DwnPrivateKeyJwk, + algorithm : signingPrivateKeyJwk.alg as string, + keyId : signingKeyId, + }); const encryptionInput: EncryptionInput = { algorithm : EncryptionAlgorithm.Aes256Ctr, @@ -915,8 +908,8 @@ describe('Record', () => { // Create a parent record to reference in the RecordsWriteMessage used for validation const parentRecorsWrite = await RecordsWrite.create({ - authorizationSignatureInput : authorization, - data : new Uint8Array(await dataBlob.arrayBuffer()), + authorizationSigner : authorization, + data : new Uint8Array(await dataBlob.arrayBuffer()), dataFormat, protocol, protocolPath, @@ -925,12 +918,12 @@ describe('Record', () => { // Create a RecordsWriteMessage const recordsWrite = await RecordsWrite.create({ - attestationSignatureInputs : attestation, - authorizationSignatureInput : authorization, - data : new Uint8Array(await dataBlob.arrayBuffer()), + attestationSigners : attestation, + authorizationSigner : authorization, + data : new Uint8Array(await dataBlob.arrayBuffer()), dataFormat, encryptionInput, - parentId : parentRecorsWrite.recordId, + parentId : parentRecorsWrite.recordId, protocol, protocolPath, published, @@ -1000,7 +993,9 @@ describe('Record', () => { const readResult = await dwn.records.read({ message: { - recordId: record!.id + filter: { + recordId: record!.id + } } }); diff --git a/packages/api/tests/utils/test-user-agent.ts b/packages/api/tests/utils/test-user-agent.ts index f09a2d1d3..34b85c58e 100644 --- a/packages/api/tests/utils/test-user-agent.ts +++ b/packages/api/tests/utils/test-user-agent.ts @@ -14,16 +14,13 @@ import type { SyncManager, } from '@web5/agent'; -import { Dwn } from '@tbd54566975/dwn-sdk-js'; +import { Dwn, EventLogLevel, + DataStoreLevel, + MessageStoreLevel, } from '@tbd54566975/dwn-sdk-js'; import { DidResolver, DidKeyMethod, } from '@web5/dids'; -import { - EventLogLevel, - DataStoreLevel, - MessageStoreLevel, -} from '@tbd54566975/dwn-sdk-js/stores'; import { LocalKms, DidManager, diff --git a/packages/dev-env/docker-compose.yaml b/packages/dev-env/docker-compose.yaml index 54a5ebab6..98e08847e 100644 --- a/packages/dev-env/docker-compose.yaml +++ b/packages/dev-env/docker-compose.yaml @@ -3,6 +3,6 @@ version: "3.98" services: dwn-server: container_name: dwn-server - image: ghcr.io/tbd54566975/dwn-server:dwn-sdk-0.2.1 + image: ghcr.io/tbd54566975/dwn-server:dwn-sdk-0.2.3 ports: - "3000:3000" diff --git a/packages/identity-agent/package.json b/packages/identity-agent/package.json index 032b0e2ad..ad96a137b 100644 --- a/packages/identity-agent/package.json +++ b/packages/identity-agent/package.json @@ -1,6 +1,6 @@ { "name": "@web5/identity-agent", - "version": "0.2.0", + "version": "0.2.1", "type": "module", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -67,7 +67,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@web5/agent": "0.2.0", + "@web5/agent": "0.2.1", "@web5/api": "0.8.1" }, "devDependencies": { diff --git a/packages/old/web5/src/dwn-api.ts b/packages/old/web5/src/dwn-api.ts index 66228e05a..85367c2bf 100644 --- a/packages/old/web5/src/dwn-api.ts +++ b/packages/old/web5/src/dwn-api.ts @@ -20,7 +20,7 @@ import { Protocol } from './protocol.js'; import { dataToBlob, isEmptyObject } from './utils.js'; export type ProtocolsConfigureRequest = { - message: Omit; + message: Omit; } export type ProtocolsConfigureResponse = { @@ -34,7 +34,7 @@ export type ProtocolsQueryReplyEntry = { export type ProtocolsQueryRequest = { from?: string; - message: Omit + message: Omit } export type ProtocolsQueryResponse = { @@ -49,13 +49,13 @@ export type RecordsCreateResponse = RecordsWriteResponse; export type RecordsCreateFromRequest = { author: string; data: unknown; - message?: Omit; + message?: Omit; record: Record; } export type RecordsDeleteRequest = { from?: string; - message: Omit; + message: Omit; } export type RecordsDeleteResponse = { @@ -65,7 +65,7 @@ export type RecordsDeleteResponse = { export type RecordsQueryRequest = { /** The from property indicates the DID to query from and return results. */ from?: string; - message: Omit; + message: Omit; } export type RecordsQueryResponse = { @@ -76,7 +76,7 @@ export type RecordsQueryResponse = { export type RecordsReadRequest = { /** The from property indicates the DID to read from and return results fro. */ from?: string; - message: Omit; + message: Omit; } export type RecordsReadResponse = { @@ -86,7 +86,7 @@ export type RecordsReadResponse = { export type RecordsWriteRequest = { data: unknown; - message?: Omit, 'authorizationSignatureInput'>; + message?: Omit, 'authorizationSigner'>; store?: boolean; } diff --git a/packages/proxy-agent/package.json b/packages/proxy-agent/package.json index e7f161753..21cf9c59e 100644 --- a/packages/proxy-agent/package.json +++ b/packages/proxy-agent/package.json @@ -1,6 +1,6 @@ { "name": "@web5/proxy-agent", - "version": "0.2.0", + "version": "0.2.1", "type": "module", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -67,7 +67,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@web5/agent": "0.2.0", + "@web5/agent": "0.2.1", "@web5/common": "0.2.0", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0" diff --git a/packages/proxy-agent/tests/proxy-agent.spec.ts b/packages/proxy-agent/tests/proxy-agent.spec.ts index 01cdc2810..9a6dc66e0 100644 --- a/packages/proxy-agent/tests/proxy-agent.spec.ts +++ b/packages/proxy-agent/tests/proxy-agent.spec.ts @@ -65,7 +65,7 @@ describe('Web5ProxyAgent', () => { }); // Simulate terminating and restarting an app. - testAgent.closeStorage(); + await testAgent.closeStorage(); testAgent = await TestManagedAgent.create({ agentClass : Web5ProxyAgent, agentStores : 'dwn' diff --git a/packages/user-agent/package.json b/packages/user-agent/package.json index 2350c6823..58ba977b1 100644 --- a/packages/user-agent/package.json +++ b/packages/user-agent/package.json @@ -1,6 +1,6 @@ { "name": "@web5/user-agent", - "version": "0.2.0", + "version": "0.2.1", "type": "module", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -67,7 +67,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@web5/agent": "0.2.0", + "@web5/agent": "0.2.1", "@web5/common": "0.2.0", "@web5/crypto": "0.2.0", "@web5/dids": "0.2.0" diff --git a/packages/user-agent/tests/user-agent.spec.ts b/packages/user-agent/tests/user-agent.spec.ts index 320f10d85..5faae1530 100644 --- a/packages/user-agent/tests/user-agent.spec.ts +++ b/packages/user-agent/tests/user-agent.spec.ts @@ -65,7 +65,7 @@ describe('Web5UserAgent', () => { }); // Simulate terminating and restarting an app. - testAgent.closeStorage(); + await testAgent.closeStorage(); testAgent = await TestManagedAgent.create({ agentClass : Web5UserAgent, agentStores : 'dwn'