diff --git a/integration-tests/delegation-offer.test.ts b/integration-tests/delegation-offer.test.ts index 3f4c65d2..a499fc99 100644 --- a/integration-tests/delegation-offer.test.ts +++ b/integration-tests/delegation-offer.test.ts @@ -169,18 +169,19 @@ describe('Credential Distribution', () => { expect(updatedOffer.holderDID).toBe(holderWallet.did); // Step 4: Holder receive the delegatable credential from the issuer - const delegatableCredential = await holderWallet.messageProvider.waitForMessage(); + const delegationIssuanceMessage = + await holderWallet.messageProvider.waitForMessage(); - console.log('[holder] received message after credential request:', delegatableCredential); + console.log('[holder] received message after credential request:', delegationIssuanceMessage); - expect(delegatableCredential).toBeDefined(); - expect(delegatableCredential.type).toBe('https://didcomm.org/issue-credential/3.0/issue-credential'); - expect(delegatableCredential.from).toBe(issuerWallet.did); - expect(delegatableCredential.body.delegationOfferId).toBe(acceptedOffer.id); - expect(Array.isArray(delegatableCredential.body.credentials)).toBe(true); - expect(delegatableCredential.body.credentials.length).toBeGreaterThan(0); + expect(delegationIssuanceMessage).toBeDefined(); + expect(delegationIssuanceMessage.type).toBe('https://didcomm.org/issue-credential/3.0/issue-credential'); + expect(delegationIssuanceMessage.from).toBe(issuerWallet.did); + expect(delegationIssuanceMessage.body.delegationOfferId).toBe(acceptedOffer.id); + expect(Array.isArray(delegationIssuanceMessage.body.credentials)).toBe(true); + expect(delegationIssuanceMessage.body.credentials.length).toBeGreaterThan(0); - const [issuedCredential] = delegatableCredential.body.credentials; + const [issuedCredential] = delegationIssuanceMessage.body.credentials; expect(issuedCredential.type).toContain('DelegationCredential'); expect(issuedCredential.rootCredentialId).toBe(rootCredential.id); expect(issuedCredential.delegationRoleId).toBe('e79c0d16-8739-4e54-94d7-53d9f1c97c71'); @@ -188,7 +189,7 @@ describe('Credential Distribution', () => { // Re-delegated credential must carry the same credentialSchema as the root credential. expect(issuedCredential.credentialSchema).toEqual(rootCredential.credentialSchema); - const delegationChain = delegatableCredential.body.delegationChain; + const delegationChain = delegationIssuanceMessage.body.delegationChain; expect(Array.isArray(delegationChain)).toBe(true); expect(delegationChain.length).toBeGreaterThan(0); expect(delegationChain[0].id).toBe(rootCredential.id); @@ -205,7 +206,7 @@ describe('Credential Distribution', () => { ); }); - await handleMessage(delegatableCredential, { + await handleMessage(delegationIssuanceMessage, { wallet: holderWallet.wallet, messageProvider: holderWallet.messageProvider, }); @@ -214,13 +215,22 @@ describe('Credential Distribution', () => { expect(receivedPayload.delegationOfferId).toBe(acceptedOffer.id); expect(receivedPayload.credentials[0].id).toBe(issuedCredential.id); + const allCredentials = + await holderWallet.credentialProvider.getCredentials(); + + // Delegated credential should be added to the credential store + expect(allCredentials.some(c => c.id === issuedCredential.id)).toBe(true); + // Delegation chain credentials should NOT be added to the credential store + expect(allCredentials.some(c => c.id === rootCredential.id)).toBe(false); + // Delegation chain documents should be stored in the wallet as separate document + const delegationChainDoc = await holderWallet.wallet.getDocumentById(`${issuedCredential.id}#delegationChain`); + expect(delegationChainDoc).toBeDefined(); + expect(delegationChainDoc.delegationChain).toEqual(delegationChain); + // Verify the delegated credential was persisted on the holder side. const storedCredential = await holderWallet.wallet.getDocumentById( issuedCredential.id, ); - expect(storedCredential).toBeDefined(); - expect(storedCredential.rootCredentialId).toBe(rootCredential.id); - // Resolve the full delegation details for the stored credential and verify // it carries the capabilities the holder was delegated. @@ -252,9 +262,8 @@ describe('Credential Distribution', () => { ].sort(), ); - // Chain starts with the holder's own delegated credential. - expect(delegationDetails.delegationChain[0].id).toBe(storedCredential.id); - expect(delegationDetails.delegationChain[0].rootCredentialId).toBe(rootCredential.id); + expect(delegationDetails.delegationChain[1].id).toBe(storedCredential.id); + expect(delegationDetails.delegationChain[1].rootCredentialId).toBe(rootCredential.id); // Verify the holder's stored offer was advanced to 'accepted'. const holderStoredOffer = await holderWallet.wallet.getDocumentById( @@ -269,5 +278,16 @@ describe('Credential Distribution', () => { expect(ackMessage.from).toBe(holderWallet.did); expect(ackMessage.body.delegationOfferId).toBe(acceptedOffer.id); expect(ackMessage.body.status).toBe('OK'); + + await handleMessage(ackMessage, { + wallet: issuerWallet.wallet, + messageProvider: issuerWallet.messageProvider, + }); + + const delegationOfferDoc = await issuerWallet.wallet.getDocumentById( + acceptedOffer.id, + ); + + expect(delegationOfferDoc.status).toBe('accepted'); }, 60_000); }); diff --git a/packages/core/src/delegation/delegation-chain.ts b/packages/core/src/delegation/delegation-chain.ts index 42aa38e5..baa678a4 100644 --- a/packages/core/src/delegation/delegation-chain.ts +++ b/packages/core/src/delegation/delegation-chain.ts @@ -10,7 +10,7 @@ export async function getDelegationChain(credential, wallet) { ? document.delegationChain : []; - return [credential, ...delegationChain]; + return [...delegationChain, credential]; } export async function addDelegationChain(credential, delegationChain, wallet) { diff --git a/packages/core/src/delegation/delegation-offer.ts b/packages/core/src/delegation/delegation-offer.ts index f03ea9cd..056b8180 100644 --- a/packages/core/src/delegation/delegation-offer.ts +++ b/packages/core/src/delegation/delegation-offer.ts @@ -3,7 +3,7 @@ import {v4 as uuid} from 'uuid'; import {logger} from '@docknetwork/wallet-sdk-data-store/src/logger'; import {getAllDIDs, getDefaultDID} from '../did-provider'; import {delegateCredential} from './delegation-issuance'; -import {getDelegationChain} from './delegation-chain'; +import {addDelegationChain, getDelegationChain} from './delegation-chain'; import {getDelegationDetails, getRole} from './delegation-policy'; import {isDelegatableCredential} from './delegation-utils'; import { @@ -382,8 +382,7 @@ export const DELEGATION_REQUEST_HANDLER = { export const ISSUE_CREDENTIAL_HANDLER = { check: function (message) { return ( - message.type === ISSUE_CREDENTIAL && - message.body?.goal_code === GOAL_CODE + message.type === ISSUE_CREDENTIAL && message.body?.goal_code === GOAL_CODE ); }, handle: async function (message, {wallet, messageProvider}) { @@ -426,11 +425,12 @@ export const ISSUE_CREDENTIAL_HANDLER = { await wallet.addDocument(credential); } - for (const ancestor of delegationChain) { - const existing = await wallet.getDocumentById(ancestor.id); - if (!existing) { - await wallet.addDocument(ancestor); - } + const [delegatedCredential] = credentials; + const existingChain = await wallet.getDocumentById( + `${delegatedCredential.id}#delegationChain`, + ); + if (!existingChain) { + await addDelegationChain(delegatedCredential, delegationChain, wallet); } await wallet.updateDocument({ @@ -462,10 +462,37 @@ export const ISSUE_CREDENTIAL_HANDLER = { }, }; +export const DELEGATION_ACK_HANDLER = { + check: function (message) { + return message.type === ACK && message.body?.goal_code === GOAL_CODE; + }, + handle: async function (message, {wallet}) { + const offerId = message.body.delegationOfferId; + + const storedOffer = await wallet.getDocumentById(offerId); + if (!storedOffer) { + logger.debug( + `DELEGATION_ACK_HANDLER: no stored offer found for ${offerId}`, + ); + return; + } + + storedOffer.status = 'accepted'; + storedOffer.updatedAt = new Date().toISOString(); + + await wallet.updateDocument(storedOffer); + + logger.debug( + `DELEGATION_ACK_HANDLER: delegation offer ${offerId} marked as accepted`, + ); + }, +}; + export const messageHandlers = [ INVITATION_HANDLER, DELEGATION_REQUEST_HANDLER, ISSUE_CREDENTIAL_HANDLER, + DELEGATION_ACK_HANDLER, ]; export async function handleMessage(