From e3408c7ef0b9e2a2fa87d4b7768a6e25bf5ff8fe Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Thu, 29 Aug 2024 13:09:20 -0400 Subject: [PATCH] Update connect flow (#856) There were a few different issues with our sync implementation. 1) Records permissions were not issued as `delegate` grants. In the future the OIDC flow can accept actual `PermissionRequests` that also signal if the user wants a delegate grant or not. Currently the system is opinionated in that `Records` grants MUST be delegated, and `Messages` grants must not. 2) There are a few different scenarios the user can land at with regards to configuring protocols. Handle the various scenarios and make sure the remote node also has the configured protocol available as the connecting user will need it immediately to use. Added method to fail if a scope requests a different protocol than expected. Increase test coverage. --- examples/wallet-connect.html | 38 +++- packages/agent/src/connect.ts | 2 +- packages/agent/src/oidc.ts | 109 +++++---- packages/agent/tests/connect.spec.ts | 319 ++++++++++++++++++++++++++- packages/api/src/web5.ts | 5 +- 5 files changed, 420 insertions(+), 53 deletions(-) diff --git a/examples/wallet-connect.html b/examples/wallet-connect.html index ccaa699e7..f6f2e0cf8 100644 --- a/examples/wallet-connect.html +++ b/examples/wallet-connect.html @@ -121,19 +121,34 @@

Success

$actions: [ { who: "anyone", - can: ["create", "update"], + can: ["read"], }, ], }, }, }; + const fooProtocol = { + protocol: "http://foo-protocol.xyz", + published: true, + types: { + foo: { + schema: "http://foo-protocol.xyz/schema/foo", + dataFormats: ["application/json"], + }, + }, + structure: { + foo: {}, + }, + }; + try { - const { delegateDid } = await Web5.connect({ + + const { delegateDid, web5 } = await Web5.connect({ walletConnectOptions: { walletUri: "web5://connect", connectServerUrl: "http://localhost:3000/connect", - permissionRequests: [{ protocolDefinition: profileProtocol }], + permissionRequests: [{ protocolDefinition: profileProtocol }, { protocolDefinition: fooProtocol }], onWalletUriReady: generateQRCode, validatePin: async () => { goToPinScreen(); @@ -144,7 +159,18 @@

Success

}, }); - goToEndScreen(delegateDid); + // attempt to write to the foo protocol + const { record, status } = await web5.dwn.records.create({ + data: { fooData: 'Some Foo Data' }, + message: { + protocol: fooProtocol.protocol, + protocolPath: 'foo', + schema: fooProtocol.types.foo.schema, + dataFormat: fooProtocol.types.foo.dataFormats[0], + } + }); + + goToEndScreen(delegateDid, record, status); } catch (e) { document.getElementById( "errorMessage" @@ -187,12 +213,12 @@

Success

document.getElementById("pinScreen").style.display = "block"; } - function goToEndScreen(delegateDid) { + function goToEndScreen(delegateDid, record, status) { document.getElementById( "didInformation" ).innerText = `delegateDid\n:${JSON.stringify( delegateDid - )}`; + )}\n\n\nRecordsWrite Status:${JSON.stringify(status)}\nRecord:${JSON.stringify(record, null, 2)}`; document.getElementById("pinScreen").style.display = "none"; document.getElementById("endScreen").style.display = "block"; diff --git a/packages/agent/src/connect.ts b/packages/agent/src/connect.ts index fdaa35362..24baf15cc 100644 --- a/packages/agent/src/connect.ts +++ b/packages/agent/src/connect.ts @@ -1,6 +1,6 @@ import type { PushedAuthResponse } from './oidc.js'; -import type { DwnPermissionScope, DwnProtocolDefinition, Web5ConnectAuthResponse } from './index.js'; +import type { DwnPermissionScope, DwnProtocolDefinition, Web5Agent, Web5ConnectAuthResponse } from './index.js'; import { Oidc, diff --git a/packages/agent/src/oidc.ts b/packages/agent/src/oidc.ts index a05873393..3443aabe5 100644 --- a/packages/agent/src/oidc.ts +++ b/packages/agent/src/oidc.ts @@ -12,13 +12,10 @@ import { concatenateUrl } from './utils.js'; import { xchacha20poly1305 } from '@noble/ciphers/chacha'; import type { ConnectPermissionRequest } from './connect.js'; import { DidDocument, DidJwk, PortableDid, type BearerDid } from '@web5/dids'; -import type { - PermissionScope, - RecordsWriteMessage, -} from '@tbd54566975/dwn-sdk-js'; -import { DwnInterface, DwnProtocolDefinition } from './types/dwn.js'; +import { DwnDataEncodedRecordsWriteMessage, DwnInterface, DwnPermissionScope, DwnProtocolDefinition } from './types/dwn.js'; import { AgentPermissionsApi } from './permissions-api.js'; import type { Web5Agent } from './types/agent.js'; +import { isRecordPermissionScope } from './dwn-api.js'; /** * Sent to an OIDC server to authorize a client. Allows clients @@ -161,14 +158,10 @@ export type SIOPv2AuthResponse = { /** An auth response that is compatible with both Web5 Connect and (hopefully, WIP) OIDC SIOPv2 */ export type Web5ConnectAuthResponse = { - delegateGrants: DelegateGrant[]; + delegateGrants: DwnDataEncodedRecordsWriteMessage[]; delegatePortableDid: PortableDid; } & SIOPv2AuthResponse; -export type DelegateGrant = (RecordsWriteMessage & { - encodedData: string; -}) - /** Represents the different OIDC endpoint types. * 1. `pushedAuthorizationRequest`: client sends {@link PushedAuthRequest} receives {@link PushedAuthResponse} * 2. `authorize`: provider gets the {@link Web5ConnectAuthRequest} JWT that was stored by the PAR @@ -615,22 +608,26 @@ async function createPermissionGrants( selectedDid: string, delegateBearerDid: BearerDid, agent: Web5Agent, - scopes: PermissionScope[], + scopes: DwnPermissionScope[], ) { - const permissionsApi = new AgentPermissionsApi({ agent }); // TODO: cleanup all grants if one fails by deleting them from the DWN: https://github.com/TBD54566975/web5-js/issues/849 const permissionGrants = await Promise.all( - scopes.map((scope) => - permissionsApi.createGrant({ + scopes.map((scope) => { + + // check if the scope is a records permission scope, if so it is a delegated permission + const delegated = isRecordPermissionScope(scope); + return permissionsApi.createGrant({ + delegated, store : true, grantedTo : delegateBearerDid.uri, scope, - dateExpires : '2040-06-25T16:09:16.693356Z', + dateExpires : '2040-06-25T16:09:16.693356Z', // TODO: make dateExpires optional author : selectedDid, - }) - ) + }); + + }) ); const messagePromises = permissionGrants.map(async (grant) => { @@ -638,7 +635,7 @@ async function createPermissionGrants( const { encodedData, ...rawMessage } = grant.message; const data = Convert.base64Url(encodedData).toUint8Array(); - const { reply } = await agent.sendDwnRequest({ + const { reply } = await agent.sendDwnRequest({ author : selectedDid, target : selectedDid, messageType : DwnInterface.RecordsWrite, @@ -662,14 +659,14 @@ async function createPermissionGrants( } /** -* Installs the protocols required by the Client on the Provider -* if they don't already exist. -*/ -async function prepareProtocols( + * Installs the protocol required by the Client on the Provider if it doesn't already exist. + */ +async function prepareProtocol( selectedDid: string, agent: Web5Agent, protocolDefinition: DwnProtocolDefinition -) { +): Promise { + const queryMessage = await agent.processDwnRequest({ author : selectedDid, messageType : DwnInterface.ProtocolsQuery, @@ -677,32 +674,48 @@ async function prepareProtocols( messageParams : { filter: { protocol: protocolDefinition.protocol } }, }); - if (queryMessage.reply.status.code === 404) { - const configureMessage = await agent.processDwnRequest({ + if ( queryMessage.reply.status.code !== 200) { + // if the query failed, throw an error + throw new Error( + `Could not fetch protocol: ${queryMessage.reply.status.detail}` + ); + } else if (queryMessage.reply.entries === undefined || queryMessage.reply.entries.length === 0) { + + // send the protocol definition to the remote DWN first, if it passes we can process it locally + const { reply: sendReply, message: configureMessage } = await agent.sendDwnRequest({ author : selectedDid, - messageType : DwnInterface.ProtocolsConfigure, target : selectedDid, + messageType : DwnInterface.ProtocolsConfigure, messageParams : { definition: protocolDefinition }, }); - if (configureMessage.reply.status.code !== 202) { - throw new Error(`Could not install protocol: ${configureMessage.reply.status.detail}`); + // check if the message was sent successfully, if the remote returns 409 the message may have come through already via sync + if (sendReply.status.code !== 202 && sendReply.status.code !== 409) { + throw new Error(`Could not send protocol: ${sendReply.status.detail}`); } - // send the configure message to the remote DWN so that the APP can immediately use it without waiting for a sync cycle from the wallet + // process the protocol locally, we don't have to check if it exists as this is just a convenience over waiting for sync. + await agent.processDwnRequest({ + author : selectedDid, + target : selectedDid, + messageType : DwnInterface.ProtocolsConfigure, + rawMessage : configureMessage + }); + + } else { + + // the protocol already exists, let's make sure it exists on the remote DWN as the requesting app will need it + const configureMessage = queryMessage.reply.entries![0]; const { reply: sendReply } = await agent.sendDwnRequest({ author : selectedDid, target : selectedDid, messageType : DwnInterface.ProtocolsConfigure, - rawMessage : configureMessage.message, + rawMessage : configureMessage, }); - // check if the message was sent successfully, if the remote returns 409 the message may have come through already via sync if (sendReply.status.code !== 202 && sendReply.status.code !== 409) { throw new Error(`Could not send protocol: ${sendReply.status.detail}`); } - } else if (queryMessage.reply.status.code !== 200) { - throw new Error(`Could not fetch protcol: ${queryMessage.reply.status.detail}`); } } @@ -719,20 +732,34 @@ async function submitAuthResponse( selectedDid: string, authRequest: Web5ConnectAuthRequest, randomPin: string, - agent: Web5Agent, + agent: Web5Agent ) { const delegateBearerDid = await DidJwk.create(); const delegatePortableDid = await delegateBearerDid.export(); - const delegateGrantPromises = authRequest.permissionRequests.map(async (permissionRequest) => { - // TODO: validate to make sure the scopes and definition are assigned to the same protocol - const { protocolDefinition, permissionScopes } = permissionRequest; + // TODO: roll back permissions and protocol configurations if an error occurs. Need a way to delete protocols to achieve this. + const delegateGrantPromises = authRequest.permissionRequests.map( + async (permissionRequest) => { + const { protocolDefinition, permissionScopes } = permissionRequest; - await prepareProtocols(selectedDid, agent, protocolDefinition); - const permissionGrants = await Oidc.createPermissionGrants(selectedDid, delegateBearerDid, agent, permissionScopes); + // We validate that all permission scopes match the protocol uri of the protocol definition they are provided with. + const grantsMatchProtocolUri = permissionScopes.every(scope => 'protocol' in scope && scope.protocol === protocolDefinition.protocol); + if (!grantsMatchProtocolUri) { + throw new Error('All permission scopes must match the protocol uri they are provided with.'); + } - return permissionGrants; - }); + await prepareProtocol(selectedDid, agent, protocolDefinition); + + const permissionGrants = await Oidc.createPermissionGrants( + selectedDid, + delegateBearerDid, + agent, + permissionScopes + ); + + return permissionGrants; + } + ); const delegateGrants = (await Promise.all(delegateGrantPromises)).flat(); diff --git a/packages/agent/tests/connect.spec.ts b/packages/agent/tests/connect.spec.ts index 441f80bdd..c56dc8720 100644 --- a/packages/agent/tests/connect.spec.ts +++ b/packages/agent/tests/connect.spec.ts @@ -11,7 +11,7 @@ import { import { PlatformAgentTestHarness } from '../src/test-harness.js'; import { TestAgent } from './utils/test-agent.js'; import { testDwnUrl } from './utils/test-config.js'; -import { BearerIdentity, DwnProtocolDefinition, WalletConnect } from '../src/index.js'; +import { BearerIdentity, DwnInterface, DwnMessage, DwnProtocolDefinition, WalletConnect } from '../src/index.js'; import { RecordsPermissionScope } from '@tbd54566975/dwn-sdk-js'; import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js'; @@ -489,6 +489,323 @@ describe('web5 connect', function () { }); }); + describe('submitAuthResponse', () => { + it('should not attempt to configure the protocol if it already exists', async () => { + // scenario: the wallet gets a request for a protocol that it already has configured + // the wallet should not attempt to re-configure, but instead ensure that the protocol is + // sent to the remote DWN for the requesting client to be able to sync it down later + + sinon.stub(Oidc, 'createPermissionGrants').resolves(permissionGrants as any); + sinon.stub(CryptoUtils, 'randomBytes').returns(encryptionNonce); + sinon.stub(DidJwk, 'create').resolves(delegateBearerDid); + + const callbackUrl = Oidc.buildOidcUrl({ + baseURL : 'http://localhost:3000', + endpoint : 'callback', + }); + + const options = { + client_id : clientEphemeralPortableDid.uri, + scope : 'openid did:jwk', + // code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(), + // code_challenge_method : 'S256' as const, + permissionRequests : [{ protocolDefinition, permissionScopes }], + redirect_uri : callbackUrl, + }; + authRequest = await Oidc.createAuthRequest(options); + + // stub the processDwnRequest method to return a protocol entry + const protocolMessage = {} as DwnMessage[DwnInterface.ProtocolsConfigure]; + + // spy send request + const sendRequestSpy = sinon.stub(testHarness.agent, 'sendDwnRequest').resolves({ + messageCid : '', + reply : { status: { code: 202, detail: 'OK' } } + }); + + const processDwnRequestStub = sinon + .stub(testHarness.agent, 'processDwnRequest') + .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' }, entries: [ protocolMessage ]} }); + + // call submitAuthResponse + await Oidc.submitAuthResponse( + providerIdentity.did.uri, + authRequest, + randomPin, + testHarness.agent + ); + + // expect the process request to only be called once for ProtocolsQuery + expect(processDwnRequestStub.callCount).to.equal(1); + expect(processDwnRequestStub.firstCall.args[0].messageType).to.equal(DwnInterface.ProtocolsQuery); + + // send request should be called once as a ProtocolsConfigure + expect(sendRequestSpy.callCount).to.equal(1); + expect(sendRequestSpy.firstCall.args[0].messageType).to.equal(DwnInterface.ProtocolsConfigure); + }); + + it('should configure the protocol if it does not exist', async () => { + // scenario: the wallet gets a request for a protocol that it does not have configured + // the wallet should attempt to configure the protocol and then send the protocol to the remote DWN + + // looks for a response of 404, empty entries array or missing entries array + + sinon.stub(Oidc, 'createPermissionGrants').resolves(permissionGrants as any); + sinon.stub(CryptoUtils, 'randomBytes').returns(encryptionNonce); + sinon.stub(DidJwk, 'create').resolves(delegateBearerDid); + + const callbackUrl = Oidc.buildOidcUrl({ + baseURL : 'http://localhost:3000', + endpoint : 'callback', + }); + + const options = { + client_id : clientEphemeralPortableDid.uri, + scope : 'openid did:jwk', + // code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(), + // code_challenge_method : 'S256' as const, + permissionRequests : [{ protocolDefinition, permissionScopes }], + redirect_uri : callbackUrl, + }; + authRequest = await Oidc.createAuthRequest(options); + + // spy send request + const sendRequestSpy = sinon.stub(testHarness.agent, 'sendDwnRequest').resolves({ + messageCid : '', + reply : { status: { code: 202, detail: 'OK' } } + }); + + const processDwnRequestStub = sinon + .stub(testHarness.agent, 'processDwnRequest') + .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' }, entries: [ ] } }); + + // call submitAuthResponse + await Oidc.submitAuthResponse( + providerIdentity.did.uri, + authRequest, + randomPin, + testHarness.agent + ); + + // expect the process request to be called for query and configure + expect(processDwnRequestStub.callCount).to.equal(2); + expect(processDwnRequestStub.firstCall.args[0].messageType).to.equal(DwnInterface.ProtocolsQuery); + expect(processDwnRequestStub.secondCall.args[0].messageType).to.equal(DwnInterface.ProtocolsConfigure); + + // send request should be called once as a ProtocolsConfigure + expect(sendRequestSpy.callCount).to.equal(1); + expect(sendRequestSpy.firstCall.args[0].messageType).to.equal(DwnInterface.ProtocolsConfigure); + + // reset the spys + processDwnRequestStub.resetHistory(); + sendRequestSpy.resetHistory(); + + // processDwnRequestStub should resolve a 200 with no entires + processDwnRequestStub.resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' } } }); + + // call submitAuthResponse + await Oidc.submitAuthResponse( + providerIdentity.did.uri, + authRequest, + randomPin, + testHarness.agent + ); + + // expect the process request to be called for query and configure + expect(processDwnRequestStub.callCount).to.equal(2); + expect(processDwnRequestStub.firstCall.args[0].messageType).to.equal(DwnInterface.ProtocolsQuery); + expect(processDwnRequestStub.secondCall.args[0].messageType).to.equal(DwnInterface.ProtocolsConfigure); + + // send request should be called once as a ProtocolsConfigure + expect(sendRequestSpy.callCount).to.equal(1); + expect(sendRequestSpy.firstCall.args[0].messageType).to.equal(DwnInterface.ProtocolsConfigure); + }); + + it('should fail if the send request fails for newly configured protocol', async () => { + sinon.stub(Oidc, 'createPermissionGrants').resolves(permissionGrants as any); + sinon.stub(CryptoUtils, 'randomBytes').returns(encryptionNonce); + sinon.stub(DidJwk, 'create').resolves(delegateBearerDid); + + const callbackUrl = Oidc.buildOidcUrl({ + baseURL : 'http://localhost:3000', + endpoint : 'callback', + }); + + const options = { + client_id : clientEphemeralPortableDid.uri, + scope : 'openid did:jwk', + // code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(), + // code_challenge_method : 'S256' as const, + permissionRequests : [{ protocolDefinition, permissionScopes }], + redirect_uri : callbackUrl, + }; + authRequest = await Oidc.createAuthRequest(options); + + // spy send request + const sendRequestSpy = sinon.stub(testHarness.agent, 'sendDwnRequest').resolves({ + reply : { status: { code: 500, detail: 'Internal Server Error' } }, + messageCid : '' + }); + + // return without any entries + const processDwnRequestStub = sinon + .stub(testHarness.agent, 'processDwnRequest') + .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' } } }); + + try { + // call submitAuthResponse + await Oidc.submitAuthResponse( + providerIdentity.did.uri, + authRequest, + randomPin, + testHarness.agent + ); + + expect.fail('should have thrown an error'); + } catch (error: any) { + expect(error.message).to.equal('Could not send protocol: Internal Server Error'); + expect(sendRequestSpy.callCount).to.equal(1); + } + }); + + it('should fail if the send request fails for existing protocol', async () => { + sinon.stub(Oidc, 'createPermissionGrants').resolves(permissionGrants as any); + sinon.stub(CryptoUtils, 'randomBytes').returns(encryptionNonce); + sinon.stub(DidJwk, 'create').resolves(delegateBearerDid); + + const callbackUrl = Oidc.buildOidcUrl({ + baseURL : 'http://localhost:3000', + endpoint : 'callback', + }); + + const options = { + client_id : clientEphemeralPortableDid.uri, + scope : 'openid did:jwk', + // code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(), + // code_challenge_method : 'S256' as const, + permissionRequests : [{ protocolDefinition, permissionScopes }], + redirect_uri : callbackUrl, + }; + authRequest = await Oidc.createAuthRequest(options); + + // stub the processDwnRequest method to return a protocol entry + const protocolMessage = {} as DwnMessage[DwnInterface.ProtocolsConfigure]; + + // spy send request + const sendRequestSpy = sinon.stub(testHarness.agent, 'sendDwnRequest').resolves({ + reply : { status: { code: 500, detail: 'Internal Server Error' } }, + messageCid : '' + }); + + // mock returning the protocol entry + const processDwnRequestStub = sinon + .stub(testHarness.agent, 'processDwnRequest') + .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' }, entries: [ protocolMessage ] } }); + + try { + // call submitAuthResponse + await Oidc.submitAuthResponse( + providerIdentity.did.uri, + authRequest, + randomPin, + testHarness.agent + ); + + expect.fail('should have thrown an error'); + } catch (error: any) { + expect(error.message).to.equal('Could not send protocol: Internal Server Error'); + expect(processDwnRequestStub.callCount).to.equal(1); + expect(sendRequestSpy.callCount).to.equal(1); + } + }); + + it('should throw if protocol could not be fetched at all', async () => { + sinon.stub(Oidc, 'createPermissionGrants').resolves(permissionGrants as any); + sinon.stub(CryptoUtils, 'randomBytes').returns(encryptionNonce); + sinon.stub(DidJwk, 'create').resolves(delegateBearerDid); + + const callbackUrl = Oidc.buildOidcUrl({ + baseURL : 'http://localhost:3000', + endpoint : 'callback', + }); + + const options = { + client_id : clientEphemeralPortableDid.uri, + scope : 'openid did:jwk', + // code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(), + // code_challenge_method : 'S256' as const, + permissionRequests : [{ protocolDefinition, permissionScopes }], + redirect_uri : callbackUrl, + }; + authRequest = await Oidc.createAuthRequest(options); + + // spy send request + const sendRequestSpy = sinon.stub(testHarness.agent, 'sendDwnRequest').resolves({ + reply : { status: { code: 500, detail: 'Internal Server Error' } }, + messageCid : '' + }); + + // mock returning the protocol entry + const processDwnRequestStub = sinon + .stub(testHarness.agent, 'processDwnRequest') + .resolves({ messageCid: '', reply: { status: { code: 500, detail: 'Some Error'}, } }); + + try { + // call submitAuthResponse + await Oidc.submitAuthResponse( + providerIdentity.did.uri, + authRequest, + randomPin, + testHarness.agent + ); + + expect.fail('should have thrown an error'); + } catch (error: any) { + expect(error.message).to.equal('Could not fetch protocol: Some Error'); + expect(processDwnRequestStub.callCount).to.equal(1); + expect(sendRequestSpy.callCount).to.equal(0); + } + }); + + it('should throw if a grant that is included in the request does not match the protocol definition', async () => { + sinon.stub(Oidc, 'createPermissionGrants').resolves(permissionGrants as any); + sinon.stub(CryptoUtils, 'randomBytes').returns(encryptionNonce); + sinon.stub(DidJwk, 'create').resolves(delegateBearerDid); + + const callbackUrl = Oidc.buildOidcUrl({ + baseURL : 'http://localhost:3000', + endpoint : 'callback', + }); + + const mismatchedScopes = [...permissionScopes]; + mismatchedScopes[0].protocol = 'http://profile-protocol.xyz/other'; + + const options = { + client_id : clientEphemeralPortableDid.uri, + scope : 'openid did:jwk', + // code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(), + // code_challenge_method : 'S256' as const, + permissionRequests : [{ protocolDefinition, permissionScopes }], + redirect_uri : callbackUrl, + }; + authRequest = await Oidc.createAuthRequest(options); + + try { + // call submitAuthResponse + await Oidc.submitAuthResponse( + providerIdentity.did.uri, + authRequest, + randomPin, + testHarness.agent + ); + + expect.fail('should have thrown an error'); + } catch (error: any) { + expect(error.message).to.equal('All permission scopes must match the protocol uri they are provided with.'); + } + }); + }); + describe('createPermissionRequestForProtocol', () => { it('should add sync permissions to all requests', async () => { const protocol:DwnProtocolDefinition = { diff --git a/packages/api/src/web5.ts b/packages/api/src/web5.ts index 6aba6ecef..75468f6d6 100644 --- a/packages/api/src/web5.ts +++ b/packages/api/src/web5.ts @@ -6,7 +6,6 @@ import type { BearerIdentity, - DelegateGrant, DwnDataEncodedRecordsWriteMessage, DwnMessagesPermissionScope, DwnProtocolDefinition, @@ -242,7 +241,6 @@ export class Web5 { walletConnectOptions, }: Web5ConnectOptions = {}): Promise { let delegateDid: string | undefined; - let delegateGrants: DelegateGrant[]; if (agent === undefined) { let registerSync = false; // A custom Web5Agent implementation was not specified, so use default managed user agent. @@ -292,11 +290,10 @@ export class Web5 { 'read', 'write', 'delete', 'query', 'subscribe' ])); - const { delegatePortableDid, connectedDid, delegateGrants: returnedGrants } = await WalletConnect.initClient({ + const { delegatePortableDid, connectedDid, delegateGrants } = await WalletConnect.initClient({ ...connectOptions, permissionRequests: walletPermissionRequests, }); - delegateGrants = returnedGrants; // Import the delegated DID as an Identity in the User Agent. // Setting the connectedDID in the metadata applies a relationship between the signer identity and the one it is impersonating.