From 086c3c7e65d4b5d7e5073deb9c54710fd502ee9b Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Fri, 11 Oct 2024 14:20:55 -0400 Subject: [PATCH] initial pass at breaking up submit auth response method --- packages/agent/src/oidc.ts | 63 ++++++++------- packages/agent/tests/connect.spec.ts | 117 +++++++++++++++++++-------- 2 files changed, 119 insertions(+), 61 deletions(-) diff --git a/packages/agent/src/oidc.ts b/packages/agent/src/oidc.ts index 076a0b72a..7aec60172 100644 --- a/packages/agent/src/oidc.ts +++ b/packages/agent/src/oidc.ts @@ -624,7 +624,7 @@ function shouldUseDelegatePermission(scope: DwnPermissionScope): boolean { */ async function createPermissionGrants( selectedDid: string, - delegateBearerDid: BearerDid, + delegatedPortableDid: PortableDid, agent: Web5Agent, scopes: DwnPermissionScope[], ) { @@ -639,7 +639,7 @@ async function createPermissionGrants( return permissionsApi.createGrant({ delegated, store : true, - grantedTo : delegateBearerDid.uri, + grantedTo : delegatedPortableDid.uri, scope, dateExpires : '2040-06-25T16:09:16.693356Z', // TODO: make dateExpires optional author : selectedDid, @@ -745,49 +745,53 @@ async function prepareProtocol( } } -/** - * Creates a delegate did which the web app will use as its future indentity. - * Assigns to that DID the level of permissions that the web app requested in - * the {@link Web5ConnectAuthRequest}. Encrypts via ECDH key that the web app - * will have access to because the web app has the public key which it provided - * in the {@link Web5ConnectAuthRequest}. Then sends the ciphertext of this - * {@link Web5ConnectAuthResponse} to the callback endpoint. Which the - * web app will need to retrieve from the token endpoint and decrypt with the pin to access. - */ -async function submitAuthResponse( +async function createAuthResponseGrants( + delegatedPortableDid: PortableDid, selectedDid: string, - authRequest: Web5ConnectAuthRequest, - randomPin: string, + permissionRequests: ConnectPermissionRequest[], agent: Web5Agent ) { - const delegateBearerDid = await DidJwk.create(); - const delegatePortableDid = await delegateBearerDid.export(); - - // 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) => { + // TODO: roll back permissions and protocol configurations if an error occurs. Need a way to delete protocols to achieve this. + const processGrant = async (permissionRequest: ConnectPermissionRequest): Promise => { const { protocolDefinition, permissionScopes } = permissionRequest; - + // 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.'); } - + await prepareProtocol(selectedDid, agent, protocolDefinition); - - const permissionGrants = await Oidc.createPermissionGrants( + + return await Oidc.createPermissionGrants( selectedDid, - delegateBearerDid, + delegatedPortableDid, agent, permissionScopes ); + }; - return permissionGrants; - } - ); + const delegateGrants = await Promise.all(permissionRequests.map(processGrant)); + return delegateGrants .flat(); +} - const delegateGrants = (await Promise.all(delegateGrantPromises)).flat(); +/** + * Creates a delegate did which the web app will use as its future indentity. + * Assigns to that DID the level of permissions that the web app requested in + * the {@link Web5ConnectAuthRequest}. Encrypts via ECDH key that the web app + * will have access to because the web app has the public key which it provided + * in the {@link Web5ConnectAuthRequest}. Then sends the ciphertext of this + * {@link Web5ConnectAuthResponse} to the callback endpoint. Which the + * web app will need to retrieve from the token endpoint and decrypt with the pin to access. + */ +async function submitAuthResponse( + selectedDid: string, + authRequest: Web5ConnectAuthRequest, + randomPin: string, + delegateBearerDid: BearerDid, + delegateGrants: DwnDataEncodedRecordsWriteMessage[] +) { + const delegatePortableDid = await delegateBearerDid.export(); logger.log('Generating auth response object...'); const responseObject = await Oidc.createResponseObject({ @@ -853,5 +857,6 @@ export const Oidc = { verifyJwt, buildOidcUrl, generateCodeChallenge, + createAuthResponseGrants, submitAuthResponse, }; diff --git a/packages/agent/tests/connect.spec.ts b/packages/agent/tests/connect.spec.ts index 9f34a8e10..4c1315f09 100644 --- a/packages/agent/tests/connect.spec.ts +++ b/packages/agent/tests/connect.spec.ts @@ -363,7 +363,6 @@ describe('web5 connect', function () { it('should send the encrypted jwe authresponse to the server', async () => { sinon.stub(Oidc, 'createPermissionGrants').resolves(permissionGrants as any); sinon.stub(CryptoUtils, 'randomBytes').returns(encryptionNonce); - sinon.stub(DidJwk, 'create').resolves(delegateBearerDid); const formEncodedRequest = new URLSearchParams({ id_token : authResponseJwe, @@ -388,11 +387,23 @@ describe('web5 connect', function () { ); const selectedDid = providerIdentity.did.uri; + + // generate the DID + const delegatePortableDid = await delegateBearerDid.export(); + + const delegatedGrants = await Oidc.createAuthResponseGrants( + delegatePortableDid, + selectedDid, + authRequest.permissionRequests, + testHarness.agent + ); + await Oidc.submitAuthResponse( selectedDid, authRequest, randomPin, - testHarness.agent + delegateBearerDid, + delegatedGrants ); expect(fetchSpy.calledOnce).to.be.true; }); @@ -499,7 +510,6 @@ describe('web5 connect', function () { 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', @@ -530,12 +540,22 @@ describe('web5 connect', function () { .stub(testHarness.agent, 'processDwnRequest') .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' }, entries: [ protocolMessage ]} }); + const delegatePortableDid = await delegateBearerDid.export(); + + const delegatedGrants = await Oidc.createAuthResponseGrants( + delegatePortableDid, + providerIdentity.did.uri, + authRequest.permissionRequests, + testHarness.agent + ); + // call submitAuthResponse await Oidc.submitAuthResponse( providerIdentity.did.uri, authRequest, randomPin, - testHarness.agent + delegateBearerDid, + delegatedGrants ); // expect the process request to only be called once for ProtocolsQuery @@ -555,7 +575,6 @@ describe('web5 connect', function () { 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', @@ -583,12 +602,23 @@ describe('web5 connect', function () { .stub(testHarness.agent, 'processDwnRequest') .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' }, entries: [ ] } }); + // generate the DID + const delegatePortableDid = await delegateBearerDid.export(); + + const delegatedGrants = await Oidc.createAuthResponseGrants( + delegatePortableDid, + providerIdentity.did.uri, + authRequest.permissionRequests, + testHarness.agent + ); + // call submitAuthResponse await Oidc.submitAuthResponse( providerIdentity.did.uri, authRequest, randomPin, - testHarness.agent + delegateBearerDid, + delegatedGrants ); // expect the process request to be called for query and configure @@ -607,12 +637,25 @@ describe('web5 connect', function () { // processDwnRequestStub should resolve a 200 with no entires processDwnRequestStub.resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' } } }); + + // generate the DID + const delegateBearerDid2 = await DidJwk.create(); + const delegatePortableDid2 = await delegateBearerDid2.export(); + + const delegatedGrants2 = await Oidc.createAuthResponseGrants( + delegatePortableDid2, + providerIdentity.did.uri, + authRequest.permissionRequests, + testHarness.agent + ); + // call submitAuthResponse await Oidc.submitAuthResponse( providerIdentity.did.uri, authRequest, randomPin, - testHarness.agent + delegateBearerDid2, + delegatedGrants2 ); // expect the process request to be called for query and configure @@ -658,13 +701,16 @@ describe('web5 connect', function () { .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' } } }); try { - // call submitAuthResponse - await Oidc.submitAuthResponse( - providerIdentity.did.uri, - authRequest, - randomPin, - testHarness.agent - ); + // generate the DID + const delegateBearerDid = await DidJwk.create(); + const delegatePortableDid = await delegateBearerDid.export(); + + await Oidc.createAuthResponseGrants( + delegatePortableDid, + providerIdentity.did.uri, + authRequest.permissionRequests, + testHarness.agent + ); expect.fail('should have thrown an error'); } catch (error: any) { @@ -709,11 +755,14 @@ describe('web5 connect', function () { .resolves({ messageCid: '', reply: { status: { code: 200, detail: 'OK' }, entries: [ protocolMessage ] } }); try { - // call submitAuthResponse - await Oidc.submitAuthResponse( + // generate the DID + const delegateBearerDid = await DidJwk.create(); + const delegatePortableDid = await delegateBearerDid.export(); + + await Oidc.createAuthResponseGrants( + delegatePortableDid, providerIdentity.did.uri, - authRequest, - randomPin, + authRequest.permissionRequests, testHarness.agent ); @@ -758,14 +807,16 @@ describe('web5 connect', function () { .resolves({ messageCid: '', reply: { status: { code: 500, detail: 'Some Error'}, } }); try { - // call submitAuthResponse - await Oidc.submitAuthResponse( - providerIdentity.did.uri, - authRequest, - randomPin, - testHarness.agent - ); + // generate the DID + const delegateBearerDid = await DidJwk.create(); + const delegatePortableDid = await delegateBearerDid.export(); + await Oidc.createAuthResponseGrants( + delegatePortableDid, + providerIdentity.did.uri, + authRequest.permissionRequests, + testHarness.agent + ); expect.fail('should have thrown an error'); } catch (error: any) { expect(error.message).to.equal('Could not fetch protocol: Some Error'); @@ -799,14 +850,16 @@ describe('web5 connect', function () { authRequest = await Oidc.createAuthRequest(options); try { - // call submitAuthResponse - await Oidc.submitAuthResponse( - providerIdentity.did.uri, - authRequest, - randomPin, - testHarness.agent - ); + // generate the DID + const delegateBearerDid = await DidJwk.create(); + const delegatePortableDid = await delegateBearerDid.export(); + await Oidc.createAuthResponseGrants( + delegatePortableDid, + providerIdentity.did.uri, + authRequest.permissionRequests, + 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.');