Skip to content

Commit 40272d7

Browse files
authored
fix/delegation chain document (#563)
* should not add delegation chain document to the credential store * handle PR feedback * update delegation chain order * handle message ack
1 parent 159f753 commit 40272d7

3 files changed

Lines changed: 73 additions & 26 deletions

File tree

integration-tests/delegation-offer.test.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -169,26 +169,27 @@ describe('Credential Distribution', () => {
169169
expect(updatedOffer.holderDID).toBe(holderWallet.did);
170170

171171
// Step 4: Holder receive the delegatable credential from the issuer
172-
const delegatableCredential = await holderWallet.messageProvider.waitForMessage();
172+
const delegationIssuanceMessage =
173+
await holderWallet.messageProvider.waitForMessage();
173174

174-
console.log('[holder] received message after credential request:', delegatableCredential);
175+
console.log('[holder] received message after credential request:', delegationIssuanceMessage);
175176

176-
expect(delegatableCredential).toBeDefined();
177-
expect(delegatableCredential.type).toBe('https://didcomm.org/issue-credential/3.0/issue-credential');
178-
expect(delegatableCredential.from).toBe(issuerWallet.did);
179-
expect(delegatableCredential.body.delegationOfferId).toBe(acceptedOffer.id);
180-
expect(Array.isArray(delegatableCredential.body.credentials)).toBe(true);
181-
expect(delegatableCredential.body.credentials.length).toBeGreaterThan(0);
177+
expect(delegationIssuanceMessage).toBeDefined();
178+
expect(delegationIssuanceMessage.type).toBe('https://didcomm.org/issue-credential/3.0/issue-credential');
179+
expect(delegationIssuanceMessage.from).toBe(issuerWallet.did);
180+
expect(delegationIssuanceMessage.body.delegationOfferId).toBe(acceptedOffer.id);
181+
expect(Array.isArray(delegationIssuanceMessage.body.credentials)).toBe(true);
182+
expect(delegationIssuanceMessage.body.credentials.length).toBeGreaterThan(0);
182183

183-
const [issuedCredential] = delegatableCredential.body.credentials;
184+
const [issuedCredential] = delegationIssuanceMessage.body.credentials;
184185
expect(issuedCredential.type).toContain('DelegationCredential');
185186
expect(issuedCredential.rootCredentialId).toBe(rootCredential.id);
186187
expect(issuedCredential.delegationRoleId).toBe('e79c0d16-8739-4e54-94d7-53d9f1c97c71');
187188

188189
// Re-delegated credential must carry the same credentialSchema as the root credential.
189190
expect(issuedCredential.credentialSchema).toEqual(rootCredential.credentialSchema);
190191

191-
const delegationChain = delegatableCredential.body.delegationChain;
192+
const delegationChain = delegationIssuanceMessage.body.delegationChain;
192193
expect(Array.isArray(delegationChain)).toBe(true);
193194
expect(delegationChain.length).toBeGreaterThan(0);
194195
expect(delegationChain[0].id).toBe(rootCredential.id);
@@ -205,7 +206,7 @@ describe('Credential Distribution', () => {
205206
);
206207
});
207208

208-
await handleMessage(delegatableCredential, {
209+
await handleMessage(delegationIssuanceMessage, {
209210
wallet: holderWallet.wallet,
210211
messageProvider: holderWallet.messageProvider,
211212
});
@@ -214,13 +215,22 @@ describe('Credential Distribution', () => {
214215
expect(receivedPayload.delegationOfferId).toBe(acceptedOffer.id);
215216
expect(receivedPayload.credentials[0].id).toBe(issuedCredential.id);
216217

218+
const allCredentials =
219+
await holderWallet.credentialProvider.getCredentials();
220+
221+
// Delegated credential should be added to the credential store
222+
expect(allCredentials.some(c => c.id === issuedCredential.id)).toBe(true);
223+
// Delegation chain credentials should NOT be added to the credential store
224+
expect(allCredentials.some(c => c.id === rootCredential.id)).toBe(false);
225+
// Delegation chain documents should be stored in the wallet as separate document
226+
const delegationChainDoc = await holderWallet.wallet.getDocumentById(`${issuedCredential.id}#delegationChain`);
227+
expect(delegationChainDoc).toBeDefined();
228+
expect(delegationChainDoc.delegationChain).toEqual(delegationChain);
229+
217230
// Verify the delegated credential was persisted on the holder side.
218231
const storedCredential = await holderWallet.wallet.getDocumentById(
219232
issuedCredential.id,
220233
);
221-
expect(storedCredential).toBeDefined();
222-
expect(storedCredential.rootCredentialId).toBe(rootCredential.id);
223-
224234

225235
// Resolve the full delegation details for the stored credential and verify
226236
// it carries the capabilities the holder was delegated.
@@ -252,9 +262,8 @@ describe('Credential Distribution', () => {
252262
].sort(),
253263
);
254264

255-
// Chain starts with the holder's own delegated credential.
256-
expect(delegationDetails.delegationChain[0].id).toBe(storedCredential.id);
257-
expect(delegationDetails.delegationChain[0].rootCredentialId).toBe(rootCredential.id);
265+
expect(delegationDetails.delegationChain[1].id).toBe(storedCredential.id);
266+
expect(delegationDetails.delegationChain[1].rootCredentialId).toBe(rootCredential.id);
258267

259268
// Verify the holder's stored offer was advanced to 'accepted'.
260269
const holderStoredOffer = await holderWallet.wallet.getDocumentById(
@@ -269,5 +278,16 @@ describe('Credential Distribution', () => {
269278
expect(ackMessage.from).toBe(holderWallet.did);
270279
expect(ackMessage.body.delegationOfferId).toBe(acceptedOffer.id);
271280
expect(ackMessage.body.status).toBe('OK');
281+
282+
await handleMessage(ackMessage, {
283+
wallet: issuerWallet.wallet,
284+
messageProvider: issuerWallet.messageProvider,
285+
});
286+
287+
const delegationOfferDoc = await issuerWallet.wallet.getDocumentById(
288+
acceptedOffer.id,
289+
);
290+
291+
expect(delegationOfferDoc.status).toBe('accepted');
272292
}, 60_000);
273293
});

packages/core/src/delegation/delegation-chain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export async function getDelegationChain(credential, wallet) {
1010
? document.delegationChain
1111
: [];
1212

13-
return [credential, ...delegationChain];
13+
return [...delegationChain, credential];
1414
}
1515

1616
export async function addDelegationChain(credential, delegationChain, wallet) {

packages/core/src/delegation/delegation-offer.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {v4 as uuid} from 'uuid';
33
import {logger} from '@docknetwork/wallet-sdk-data-store/src/logger';
44
import {getAllDIDs, getDefaultDID} from '../did-provider';
55
import {delegateCredential} from './delegation-issuance';
6-
import {getDelegationChain} from './delegation-chain';
6+
import {addDelegationChain, getDelegationChain} from './delegation-chain';
77
import {getDelegationDetails, getRole} from './delegation-policy';
88
import {isDelegatableCredential} from './delegation-utils';
99
import {
@@ -382,8 +382,7 @@ export const DELEGATION_REQUEST_HANDLER = {
382382
export const ISSUE_CREDENTIAL_HANDLER = {
383383
check: function (message) {
384384
return (
385-
message.type === ISSUE_CREDENTIAL &&
386-
message.body?.goal_code === GOAL_CODE
385+
message.type === ISSUE_CREDENTIAL && message.body?.goal_code === GOAL_CODE
387386
);
388387
},
389388
handle: async function (message, {wallet, messageProvider}) {
@@ -426,11 +425,12 @@ export const ISSUE_CREDENTIAL_HANDLER = {
426425
await wallet.addDocument(credential);
427426
}
428427

429-
for (const ancestor of delegationChain) {
430-
const existing = await wallet.getDocumentById(ancestor.id);
431-
if (!existing) {
432-
await wallet.addDocument(ancestor);
433-
}
428+
const [delegatedCredential] = credentials;
429+
const existingChain = await wallet.getDocumentById(
430+
`${delegatedCredential.id}#delegationChain`,
431+
);
432+
if (!existingChain) {
433+
await addDelegationChain(delegatedCredential, delegationChain, wallet);
434434
}
435435

436436
await wallet.updateDocument({
@@ -462,10 +462,37 @@ export const ISSUE_CREDENTIAL_HANDLER = {
462462
},
463463
};
464464

465+
export const DELEGATION_ACK_HANDLER = {
466+
check: function (message) {
467+
return message.type === ACK && message.body?.goal_code === GOAL_CODE;
468+
},
469+
handle: async function (message, {wallet}) {
470+
const offerId = message.body.delegationOfferId;
471+
472+
const storedOffer = await wallet.getDocumentById(offerId);
473+
if (!storedOffer) {
474+
logger.debug(
475+
`DELEGATION_ACK_HANDLER: no stored offer found for ${offerId}`,
476+
);
477+
return;
478+
}
479+
480+
storedOffer.status = 'accepted';
481+
storedOffer.updatedAt = new Date().toISOString();
482+
483+
await wallet.updateDocument(storedOffer);
484+
485+
logger.debug(
486+
`DELEGATION_ACK_HANDLER: delegation offer ${offerId} marked as accepted`,
487+
);
488+
},
489+
};
490+
465491
export const messageHandlers = [
466492
INVITATION_HANDLER,
467493
DELEGATION_REQUEST_HANDLER,
468494
ISSUE_CREDENTIAL_HANDLER,
495+
DELEGATION_ACK_HANDLER,
469496
];
470497

471498
export async function handleMessage(

0 commit comments

Comments
 (0)