Skip to content

Commit

Permalink
feat: adds support for linked attachments (#320)
Browse files Browse the repository at this point in the history
* feat: add support for linked attachments
* fix: linkedAttachment do not support JSON data anymore

Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
berendsliedrecht authored Jul 10, 2021
1 parent a79e4f4 commit ea91559
Show file tree
Hide file tree
Showing 24 changed files with 623 additions and 87 deletions.
248 changes: 245 additions & 3 deletions src/__tests__/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@ import type { ConnectionRecord } from '../modules/connections'
import { Subject } from 'rxjs'

import { Agent } from '../agent/Agent'
import { Attachment, AttachmentData } from '../decorators/attachment/Attachment'
import {
CredentialRecord,
CredentialState,
CredentialPreview,
CredentialPreviewAttribute,
} from '../modules/credentials'
import { JsonTransformer } from '../utils/JsonTransformer'
import { LinkedAttachment } from '../utils/LinkedAttachment'

import {
ensurePublicDidIsOnLedger,
genesisPath,
getBaseConfig,
makeConnection,
registerDefinition,
registerSchema,
SubjectInboundTransporter,
SubjectOutboundTransporter,
waitForCredentialRecord,
genesisPath,
getBaseConfig,
closeAndDeleteWallet,
} from './helpers'
import testLogger from './logger'
Expand Down Expand Up @@ -74,7 +76,7 @@ describe('credentials', () => {

const schemaTemplate = {
name: `test-schema-${Date.now()}`,
attributes: ['name', 'age'],
attributes: ['name', 'age', 'profile_picture', 'x-ray'],
version: '1.0',
}
const schema = await registerSchema(faberAgent, schemaTemplate)
Expand Down Expand Up @@ -320,4 +322,244 @@ describe('credentials', () => {
connectionId: expect.any(String),
})
})

test('Alice starts with credential proposal, with attachments, to Faber', async () => {
testLogger.test('Alice sends credential proposal to Faber')
let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential(aliceConnection.id, {
credentialProposal: credentialPreview,
credentialDefinitionId: credDefId,
linkedAttachments: [
new LinkedAttachment({
name: 'profile_picture',
attachment: new Attachment({
mimeType: 'image/png',
data: new AttachmentData({ base64: 'base64encodedpic' }),
}),
}),
],
})

testLogger.test('Faber waits for credential proposal from Alice')
let faberCredentialRecord = await waitForCredentialRecord(faberAgent, {
threadId: aliceCredentialRecord.threadId,
state: CredentialState.ProposalReceived,
})

testLogger.test('Faber sends credential offer to Alice')
faberCredentialRecord = await faberAgent.credentials.acceptProposal(faberCredentialRecord.id, {
comment: 'some comment about credential',
})

testLogger.test('Alice waits for credential offer from Faber')
aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, {
threadId: faberCredentialRecord.threadId,
state: CredentialState.OfferReceived,
})

expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({
createdAt: expect.any(Date),
offerMessage: {
'@id': expect.any(String),
'@type': 'https://didcomm.org/issue-credential/1.0/offer-credential',
comment: 'some comment about credential',
credential_preview: {
'@type': 'https://didcomm.org/issue-credential/1.0/credential-preview',
attributes: [
{
name: 'name',
'mime-type': 'text/plain',
value: 'John',
},
{
name: 'age',
'mime-type': 'text/plain',
value: '99',
},
{
name: 'profile_picture',
'mime-type': 'image/png',
value: 'hl:zQmcKEWE6eZWpVqGKhbmhd8SxWBa9fgLX7aYW8RJzeHQMZg',
},
],
},
'~attach': [{ '@id': 'zQmcKEWE6eZWpVqGKhbmhd8SxWBa9fgLX7aYW8RJzeHQMZg' }],
'offers~attach': expect.any(Array),
},
state: CredentialState.OfferReceived,
})

// below values are not in json object
expect(aliceCredentialRecord.id).not.toBeNull()
expect(aliceCredentialRecord.getTags()).toEqual({
state: aliceCredentialRecord.state,
threadId: faberCredentialRecord.threadId,
connectionId: aliceCredentialRecord.connectionId,
})
expect(aliceCredentialRecord.type).toBe(CredentialRecord.name)

testLogger.test('Alice sends credential request to Faber')
aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id)

testLogger.test('Faber waits for credential request from Alice')
faberCredentialRecord = await waitForCredentialRecord(faberAgent, {
threadId: aliceCredentialRecord.threadId,
state: CredentialState.RequestReceived,
})

testLogger.test('Faber sends credential to Alice')
faberCredentialRecord = await faberAgent.credentials.acceptRequest(faberCredentialRecord.id)

testLogger.test('Alice waits for credential from Faber')
aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, {
threadId: faberCredentialRecord.threadId,
state: CredentialState.CredentialReceived,
})

testLogger.test('Alice sends credential ack to Faber')
aliceCredentialRecord = await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id)

testLogger.test('Faber waits for credential ack from Alice')
faberCredentialRecord = await waitForCredentialRecord(faberAgent, {
threadId: faberCredentialRecord.threadId,
state: CredentialState.Done,
})

expect(aliceCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Date),
offerMessage: expect.any(Object),
requestMessage: expect.any(Object),
metadata: {
requestMetadata: expect.any(Object),
schemaId,
credentialDefinitionId: credDefId,
},
credentialId: expect.any(String),
state: CredentialState.Done,
})

expect(faberCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Date),
metadata: {
schemaId,
credentialDefinitionId: credDefId,
},
offerMessage: expect.any(Object),
requestMessage: expect.any(Object),
state: CredentialState.Done,
})
})

test('Faber starts with credential, with attachments, offer to Alice', async () => {
testLogger.test('Faber sends credential offer to Alice')
faberCredentialRecord = await faberAgent.credentials.offerCredential(faberConnection.id, {
preview: credentialPreview,
credentialDefinitionId: credDefId,
comment: 'some comment about credential',
linkedAttachments: [
new LinkedAttachment({
name: 'x-ray',
attachment: new Attachment({
data: new AttachmentData({
base64: 'secondbase64encodedpic',
}),
}),
}),
],
})

testLogger.test('Alice waits for credential offer from Faber')
aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, {
threadId: faberCredentialRecord.threadId,
state: CredentialState.OfferReceived,
})

expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({
createdAt: expect.any(Date),
offerMessage: {
'@id': expect.any(String),
'@type': 'https://didcomm.org/issue-credential/1.0/offer-credential',
comment: 'some comment about credential',
credential_preview: {
'@type': 'https://didcomm.org/issue-credential/1.0/credential-preview',
attributes: [
{
name: 'name',
'mime-type': 'text/plain',
value: 'John',
},
{
name: 'age',
'mime-type': 'text/plain',
value: '99',
},
{
name: 'x-ray',
value: 'hl:zQmVYZR9aDF47we8cmAaCP1vpXNoF1R5whSwaQUmVAZAjnG',
},
],
},
'~attach': [{ '@id': 'zQmVYZR9aDF47we8cmAaCP1vpXNoF1R5whSwaQUmVAZAjnG' }],
'offers~attach': expect.any(Array),
},
state: CredentialState.OfferReceived,
})

// below values are not in json object
expect(aliceCredentialRecord.id).not.toBeNull()
expect(aliceCredentialRecord.getTags()).toEqual({
state: aliceCredentialRecord.state,
threadId: faberCredentialRecord.threadId,
connectionId: aliceCredentialRecord.connectionId,
})
expect(aliceCredentialRecord.type).toBe(CredentialRecord.name)

testLogger.test('Alice sends credential request to Faber')
aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id)

testLogger.test('Faber waits for credential request from Alice')
faberCredentialRecord = await waitForCredentialRecord(faberAgent, {
threadId: aliceCredentialRecord.threadId,
state: CredentialState.RequestReceived,
})

testLogger.test('Faber sends credential to Alice')
faberCredentialRecord = await faberAgent.credentials.acceptRequest(faberCredentialRecord.id)

testLogger.test('Alice waits for credential from Faber')
aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, {
threadId: faberCredentialRecord.threadId,
state: CredentialState.CredentialReceived,
})

testLogger.test('Alice sends credential ack to Faber')
aliceCredentialRecord = await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id)

testLogger.test('Faber waits for credential ack from Alice')
faberCredentialRecord = await waitForCredentialRecord(faberAgent, {
threadId: faberCredentialRecord.threadId,
state: CredentialState.Done,
})

expect(aliceCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Date),
requestMessage: expect.any(Object),
metadata: { requestMetadata: expect.any(Object) },
credentialId: expect.any(String),
state: CredentialState.Done,
})

expect(faberCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Date),
requestMessage: expect.any(Object),
state: CredentialState.Done,
})
})
})
40 changes: 39 additions & 1 deletion src/__tests__/proofs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { CredDefId } from 'indy-sdk'
import { Subject } from 'rxjs'

import { Agent } from '../agent/Agent'
import { Attachment, AttachmentData } from '../decorators/attachment/Attachment'
import { CredentialPreview, CredentialPreviewAttribute } from '../modules/credentials'
import {
PredicateType,
Expand All @@ -15,6 +16,7 @@ import {
AttributeFilter,
ProofPredicateInfo,
} from '../modules/proofs'
import { LinkedAttachment } from '../utils/LinkedAttachment'

import {
ensurePublicDidIsOnLedger,
Expand Down Expand Up @@ -73,7 +75,7 @@ describe('Present Proof', () => {

const schemaTemplate = {
name: `test-schema-${Date.now()}`,
attributes: ['name', 'age'],
attributes: ['name', 'age', 'image_0', 'image_1'],
version: '1.0',
}
const schema = await registerSchema(faberAgent, schemaTemplate)
Expand Down Expand Up @@ -102,6 +104,10 @@ describe('Present Proof', () => {
referent: '0',
value: 'John',
}),
new PresentationPreviewAttribute({
name: 'image_0',
credentialDefinitionId: credDefId,
}),
],
predicates: [
new PresentationPreviewPredicate({
Expand All @@ -121,6 +127,22 @@ describe('Present Proof', () => {
credentialDefinitionId: credDefId,
comment: 'some comment about credential',
preview: credentialPreview,
linkedAttachments: [
new LinkedAttachment({
name: 'image_0',
attachment: new Attachment({
filename: 'picture-of-a-cat.png',
data: new AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }),
}),
}),
new LinkedAttachment({
name: 'image_1',
attachment: new Attachment({
filename: 'picture-of-a-dog.png',
data: new AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }),
}),
}),
],
},
})
})
Expand Down Expand Up @@ -189,6 +211,22 @@ describe('Present Proof', () => {
}),
],
}),
image_0: new ProofAttributeInfo({
name: 'image_0',
restrictions: [
new AttributeFilter({
credentialDefinitionId: credDefId,
}),
],
}),
image_1: new ProofAttributeInfo({
name: 'image_1',
restrictions: [
new AttributeFilter({
credentialDefinitionId: credDefId,
}),
],
}),
}

const predicates = {
Expand Down
2 changes: 1 addition & 1 deletion src/decorators/attachment/AttachmentExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Attachment } from './Attachment'
export function AttachmentDecorated<T extends BaseMessageConstructor>(Base: T) {
class AttachmentDecoratorExtension extends Base {
/**
* The ~attach decorator is required for appending attachments to a credential
* The ~attach decorator is required for appending attachments to a preview
*/
@Expose({ name: '~attach' })
@Type(() => Attachment)
Expand Down
Loading

0 comments on commit ea91559

Please sign in to comment.