Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions integration-tests/delegation-offer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,26 +169,27 @@ 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');

// 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);
Expand All @@ -205,7 +206,7 @@ describe('Credential Distribution', () => {
);
});

await handleMessage(delegatableCredential, {
await handleMessage(delegationIssuanceMessage, {
wallet: holderWallet.wallet,
messageProvider: holderWallet.messageProvider,
});
Expand All @@ -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`);
Comment thread
mike-parkhill marked this conversation as resolved.
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.
Expand Down Expand Up @@ -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(
Expand All @@ -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);
});
2 changes: 1 addition & 1 deletion packages/core/src/delegation/delegation-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
43 changes: 35 additions & 8 deletions packages/core/src/delegation/delegation-offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}) {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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(
Expand Down
Loading