Skip to content

Commit

Permalink
initial pass at breaking up submit auth response method
Browse files Browse the repository at this point in the history
  • Loading branch information
LiranCohen committed Oct 11, 2024
1 parent aaf4b4a commit 086c3c7
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 61 deletions.
63 changes: 34 additions & 29 deletions packages/agent/src/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ function shouldUseDelegatePermission(scope: DwnPermissionScope): boolean {
*/
async function createPermissionGrants(
selectedDid: string,
delegateBearerDid: BearerDid,
delegatedPortableDid: PortableDid,
agent: Web5Agent,
scopes: DwnPermissionScope[],
) {
Expand All @@ -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,
Expand Down Expand Up @@ -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<DwnDataEncodedRecordsWriteMessage[]> => {
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({
Expand Down Expand Up @@ -853,5 +857,6 @@ export const Oidc = {
verifyJwt,
buildOidcUrl,
generateCodeChallenge,
createAuthResponseGrants,
submitAuthResponse,
};
117 changes: 85 additions & 32 deletions packages/agent/tests/connect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
});
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
);

Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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.');
Expand Down

0 comments on commit 086c3c7

Please sign in to comment.