From 8393ff004e1cd189ca81bd876f526627c7dca4c5 Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Fri, 11 Nov 2022 15:03:09 +0200 Subject: [PATCH] feat: issue credentials v2 (W3C/JSON-LD) (#1092) Signed-off-by: Mike Richardson --- .gitignore | 1 - ...proof.credentials.propose-offerBbs.test.ts | 310 ++++++++++ .../src/modules/credentials/CredentialsApi.ts | 3 +- .../modules/credentials/CredentialsModule.ts | 2 + .../__tests__/CredentialsModule.test.ts | 5 +- .../formats/CredentialFormatService.ts | 3 +- .../formats/CredentialFormatServiceOptions.ts | 10 + .../IndyCredentialFormatService.test.ts | 437 ++++++++++++++ .../JsonLdCredentialFormatService.test.ts | 567 ++++++++++++++++++ .../src/modules/credentials/formats/index.ts | 1 + .../indy/IndyCredentialFormatService.ts | 7 +- .../formats/jsonld/JsonLdCredentialFormat.ts | 25 + .../jsonld/JsonLdCredentialFormatService.ts | 435 ++++++++++++++ .../formats/jsonld/JsonLdCredentialOptions.ts | 30 + .../formats/jsonld/JsonLdOptionsRFC0593.ts | 59 ++ .../credentials/formats/jsonld/index.ts | 4 + .../__tests__/V1CredentialServiceCred.test.ts | 4 +- .../v1-credentials-auto-accept.e2e.test.ts | 111 ++-- .../v2/CredentialFormatCoordinator.ts | 8 + .../protocol/v2/V2CredentialService.ts | 25 +- .../__tests__/V2CredentialServiceCred.test.ts | 9 + .../V2CredentialServiceOffer.test.ts | 8 + .../v2-credentials-auto-accept.e2e.test.ts | 3 - ...ldproof.connectionless-credentials.test.ts | 167 ++++++ ...v2.ldproof.credentials-auto-accept.test.ts | 382 ++++++++++++ ...f.credentials.propose-offerED25519.test.ts | 551 +++++++++++++++++ .../v2/handlers/V2IssueCredentialHandler.ts | 1 - .../v2/handlers/V2OfferCredentialHandler.ts | 1 - .../v2/handlers/V2RequestCredentialHandler.ts | 2 +- .../src/modules/dids/domain/DidDocument.ts | 3 +- .../proofs/formats/ProofFormatService.ts | 4 +- .../formats/indy/IndyProofFormatService.ts | 4 +- .../models/ProofFormatServiceOptions.ts | 2 +- .../vc/models/W3cCredentialServiceOptions.ts | 5 - packages/core/src/utils/objEqual.ts | 23 + packages/core/tests/helpers.ts | 33 +- yarn.lock | 326 +++++----- 37 files changed, 3305 insertions(+), 266 deletions(-) create mode 100644 packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts create mode 100644 packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts create mode 100644 packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/index.ts create mode 100644 packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts create mode 100644 packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts create mode 100644 packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts create mode 100644 packages/core/src/utils/objEqual.ts diff --git a/.gitignore b/.gitignore index 76a2db60c0..4d15c7409b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,4 @@ coverage .DS_Store logs.txt logs/ -packages/core/src/__tests__/genesis-von.txn lerna-debug.log \ No newline at end of file diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts new file mode 100644 index 0000000000..2ca92faa8d --- /dev/null +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -0,0 +1,310 @@ +import type { Agent } from '../../core/src/agent/Agent' +import type { ConnectionRecord } from '../../core/src/modules/connections' +import type { SignCredentialOptionsRFC0593 } from '../../core/src/modules/credentials/formats/jsonld' +import type { Wallet } from '../../core/src/wallet' + +import { InjectionSymbols } from '../../core/src/constants' +import { KeyType } from '../../core/src/crypto' +import { CredentialState } from '../../core/src/modules/credentials/models' +import { V2IssueCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage' +import { V2OfferCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage' +import { CredentialExchangeRecord } from '../../core/src/modules/credentials/repository/CredentialExchangeRecord' +import { DidKey } from '../../core/src/modules/dids' +import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '../../core/src/modules/vc' +import { W3cCredential } from '../../core/src/modules/vc/models/credential/W3cCredential' +import { DidCommMessageRepository } from '../../core/src/storage' +import { JsonTransformer } from '../../core/src/utils/JsonTransformer' +import { setupCredentialTests, waitForCredentialRecord } from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' + +import { describeSkipNode17And18 } from './util' + +let faberAgent: Agent +let aliceAgent: Agent +let aliceConnection: ConnectionRecord +let aliceCredentialRecord: CredentialExchangeRecord +let faberCredentialRecord: CredentialExchangeRecord + +const TEST_LD_DOCUMENT = { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: '', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, +} + +describeSkipNode17And18('credentials, BBS+ signature', () => { + let wallet + let issuerDidKey: DidKey + let didCommMessageRepository: DidCommMessageRepository + let signCredentialOptions: SignCredentialOptionsRFC0593 + const seed = 'testseed000000000000000000000001' + beforeAll(async () => { + ;({ faberAgent, aliceAgent, aliceConnection } = await setupCredentialTests( + 'Faber Agent Credentials LD BBS+', + 'Alice Agent Credentials LD BBS+' + )) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) + + issuerDidKey = new DidKey(key) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 (ld format, BbsBlsSignature2020 signature) credential proposal to Faber', async () => { + testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') + // set the propose options + + const credentialJson = TEST_LD_DOCUMENT + credentialJson.issuer = issuerDidKey.did + + const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) + + signCredentialOptions = { + credential, + options: { + proofType: 'BbsBlsSignature2020', + proofPurpose: 'assertionMethod', + }, + } + + testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') + + const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test for W3C Credentials', + }) + + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.protocolVersion).toEqual('v2') + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { + associatedRecordId: aliceCredentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + '@id': expect.any(String), + comment: 'V2 W3C Offer', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }, + ], + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~service': undefined, + '~attach': undefined, + '~please_ack': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + credential_preview: expect.any(Object), + replacement_id: undefined, + }) + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (!aliceCredentialRecord.connectionId) { + throw new Error('Missing Connection Id') + } + + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: undefined, + }, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + 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') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: 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: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + const w3cCredential = credentialMessage.credentialAttachments[0].getDataAsJson() + + expect(w3cCredential).toMatchObject({ + context: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'BbsBlsSignature2020', + created: expect.any(String), + verificationMethod: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + proofPurpose: 'assertionMethod', + proofValue: expect.any(String), + }, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 W3C Offer', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) + }) +}) diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index f7c39f6e29..8fbca0e12e 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -21,6 +21,7 @@ import type { } from './CredentialsApiOptions' import type { CredentialFormat } from './formats' import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' +import type { JsonLdCredentialFormat } from './formats/jsonld/JsonLdCredentialFormat' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' import type { CredentialService } from './services/CredentialService' @@ -92,7 +93,7 @@ export interface CredentialsApi[] = [V1CredentialService, V2CredentialService] > implements CredentialsApi { diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index a54a20b1a7..8e9926ff87 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -7,6 +7,7 @@ import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { IndyCredentialFormatService } from './formats/indy' +import { JsonLdCredentialFormatService } from './formats/jsonld/JsonLdCredentialFormatService' import { RevocationNotificationService } from './protocol/revocation-notification/services' import { V1CredentialService } from './protocol/v1' import { V2CredentialService } from './protocol/v2' @@ -60,5 +61,6 @@ export class CredentialsModule implements Module { // Credential Formats dependencyManager.registerSingleton(IndyCredentialFormatService) + dependencyManager.registerSingleton(JsonLdCredentialFormatService) } } diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts index cb76cc2840..9aec292944 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts @@ -4,6 +4,7 @@ import { CredentialsApi } from '../CredentialsApi' import { CredentialsModule } from '../CredentialsModule' import { CredentialsModuleConfig } from '../CredentialsModuleConfig' import { IndyCredentialFormatService } from '../formats' +import { JsonLdCredentialFormatService } from '../formats/jsonld/JsonLdCredentialFormatService' import { V1CredentialService, V2CredentialService } from '../protocol' import { RevocationNotificationService } from '../protocol/revocation-notification/services' import { CredentialRepository } from '../repository' @@ -29,11 +30,13 @@ describe('CredentialsModule', () => { expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(CredentialsModuleConfig, credentialsModule.config) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1CredentialService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2CredentialService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(RevocationNotificationService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(CredentialRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyCredentialFormatService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(JsonLdCredentialFormatService) }) }) diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index 68a8d5ab8d..f16edfb147 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -15,6 +15,7 @@ import type { FormatAutoRespondOfferOptions, FormatAutoRespondProposalOptions, FormatAutoRespondRequestOptions, + FormatProcessCredentialOptions, } from './CredentialFormatServiceOptions' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' @@ -58,7 +59,7 @@ export abstract class CredentialFormatService // credential methods - abstract processCredential(agentContext: AgentContext, options: FormatProcessOptions): Promise + abstract processCredential(agentContext: AgentContext, options: FormatProcessCredentialOptions): Promise // auto accept methods abstract shouldAutoRespondToProposal(agentContext: AgentContext, options: FormatAutoRespondProposalOptions): boolean diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 1a6cc4db11..0c4d6044af 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -40,6 +40,10 @@ export interface FormatProcessOptions { credentialRecord: CredentialExchangeRecord } +export interface FormatProcessCredentialOptions extends FormatProcessOptions { + requestAttachment: Attachment +} + export interface FormatCreateProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createProposal'> @@ -89,6 +93,12 @@ export interface FormatAcceptRequestOptions { offerAttachment?: Attachment } +export interface FormatAcceptCredentialOptions { + credentialRecord: CredentialExchangeRecord + attachId?: string + requestAttachment: Attachment + offerAttachment?: Attachment +} // Auto accept method interfaces export interface FormatAutoRespondProposalOptions { credentialRecord: CredentialExchangeRecord diff --git a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts new file mode 100644 index 0000000000..fee7df817f --- /dev/null +++ b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts @@ -0,0 +1,437 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentConfig } from '../../../../agent/AgentConfig' +import type { ParseRevocationRegistryDefinitionTemplate } from '../../../ledger/services/IndyLedgerService' +import type { CredentialFormatService } from '../../formats' +import type { IndyCredentialFormat } from '../../formats/indy/IndyCredentialFormat' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' +import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' +import type { RevocRegDef } from 'indy-sdk' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' +import { JsonEncoder } from '../../../../utils/JsonEncoder' +import { ConnectionService } from '../../../connections/services/ConnectionService' +import { DidResolverService } from '../../../dids/services/DidResolverService' +import { IndyHolderService } from '../../../indy/services/IndyHolderService' +import { IndyIssuerService } from '../../../indy/services/IndyIssuerService' +import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' +import { credDef, credReq, schema } from '../../__tests__/fixtures' +import { IndyCredentialFormatService } from '../../formats' +import { IndyCredentialUtils } from '../../formats/indy/IndyCredentialUtils' +import { CredentialState } from '../../models' +import { + INDY_CREDENTIAL_ATTACHMENT_ID, + INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, +} from '../../protocol/v1/messages' +import { V2CredentialPreview } from '../../protocol/v2/messages' +import { V2OfferCredentialMessage } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import { CredentialMetadataKeys } from '../../repository' +import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' + +jest.mock('../../../../modules/ledger/services/IndyLedgerService') +jest.mock('../../../indy/services/IndyHolderService') +jest.mock('../../../indy/services/IndyIssuerService') +jest.mock('../../../dids/services/DidResolverService') +jest.mock('../../../connections/services/ConnectionService') + +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +const IndyHolderServiceMock = IndyHolderService as jest.Mock +const IndyIssuerServiceMock = IndyIssuerService as jest.Mock +const ConnectionServiceMock = ConnectionService as jest.Mock +const DidResolverServiceMock = DidResolverService as jest.Mock + +const values = { + x: { + raw: 'x', + encoded: 'y', + }, +} +const cred = { + schema_id: 'xsxs', + cred_def_id: 'xdxd', + rev_reg_id: 'x', + values: values, + signature: undefined, + signature_correctness_proof: undefined, +} + +const revDef: RevocRegDef = { + id: 'x', + revocDefType: 'CL_ACCUM', + tag: 'x', + credDefId: 'x', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 33, + tailsHash: 'd', + tailsLocation: 'x', + publicKeys: ['x'], + }, + ver: 't', +} + +const revocationTemplate: ParseRevocationRegistryDefinitionTemplate = { + revocationRegistryDefinition: revDef, + revocationRegistryDefinitionTxnTime: 42, +} + +const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const offerAttachment = new Attachment({ + id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +const requestAttachment = new Attachment({ + id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(credReq), + }), +}) + +const credentialAttachment = new Attachment({ + id: INDY_CREDENTIAL_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64({ + values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), + }), + }), +}) + +// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` +// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. +const mockCredentialRecord = ({ + state, + metadata, + threadId, + connectionId, + tags, + id, + credentialAttributes, +}: { + state?: CredentialState + metadata?: { indyRequest: Record } + tags?: CustomCredentialTags + threadId?: string + connectionId?: string + id?: string + credentialAttributes?: CredentialPreviewAttribute[] +} = {}) => { + const offerOptions: V2OfferCredentialMessageOptions = { + id: '', + formats: [ + { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + format: 'hlindy/cred-abstract@v2.0', + }, + ], + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + replacementId: undefined, + } + const offerMessage = new V2OfferCredentialMessage(offerOptions) + + const credentialRecord = new CredentialExchangeRecord({ + id, + credentialAttributes: credentialAttributes || credentialPreview.attributes, + state: state || CredentialState.OfferSent, + threadId: threadId ?? offerMessage.id, + connectionId: connectionId ?? '123', + tags, + protocolVersion: 'v2', + }) + + if (metadata?.indyRequest) { + credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) + } + + return credentialRecord +} +let indyFormatService: CredentialFormatService +let indyLedgerService: IndyLedgerService +let indyIssuerService: IndyIssuerService +let indyHolderService: IndyHolderService +let didResolverService: DidResolverService +let connectionService: ConnectionService +let agentConfig: AgentConfig +let credentialRecord: CredentialExchangeRecord + +describe('Indy CredentialFormatService', () => { + let agentContext: AgentContext + beforeEach(async () => { + agentContext = getAgentContext() + agentConfig = getAgentConfig('CredentialServiceTest') + + indyIssuerService = new IndyIssuerServiceMock() + indyHolderService = new IndyHolderServiceMock() + indyLedgerService = new IndyLedgerServiceMock() + didResolverService = new DidResolverServiceMock() + connectionService = new ConnectionServiceMock() + + indyFormatService = new IndyCredentialFormatService( + indyIssuerService, + indyLedgerService, + indyHolderService, + connectionService, + didResolverService, + agentConfig.logger + ) + + mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) + }) + + describe('Create Credential Proposal / Offer', () => { + test(`Creates Credential Proposal`, async () => { + // when + const { attachment, previewAttributes, format } = await indyFormatService.createProposal(agentContext, { + credentialRecord: mockCredentialRecord(), + credentialFormats: { + indy: { + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + attributes: credentialPreview.attributes, + }, + }, + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAiLCJzY2hlbWFfaWQiOiJxN0FUd1RZYlFEZ2lpZ1ZpalVBZWo6Mjp0ZXN0OjEuMCIsInNjaGVtYV9uYW1lIjoiYWhveSIsInNjaGVtYV92ZXJzaW9uIjoiMS4wIiwiY3JlZF9kZWZfaWQiOiJUaDdNcFRhUlpWUlluUGlhYmRzODFZOjM6Q0w6MTc6VEFHIiwiaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAifQ==', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(previewAttributes).toMatchObject([ + { + mimeType: 'text/plain', + name: 'name', + value: 'John', + }, + { + mimeType: 'text/plain', + name: 'age', + value: '99', + }, + ]) + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred-filter@v2.0', + }) + }) + + test(`Creates Credential Offer`, async () => { + // when + const { attachment, previewAttributes, format } = await indyFormatService.createOffer(agentContext, { + credentialRecord: mockCredentialRecord(), + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + }) + + // then + expect(indyIssuerService.createCredentialOffer).toHaveBeenCalledTimes(1) + + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(previewAttributes).toMatchObject([ + { + mimeType: 'text/plain', + name: 'name', + value: 'John', + }, + { + mimeType: 'text/plain', + name: 'age', + value: '99', + }, + ]) + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred-abstract@v2.0', + }) + }) + }) + describe('Process Credential Offer', () => { + test(`processes credential offer - returns modified credential record (adds metadata)`, async () => { + // given + const credentialRecord = mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + // when + await indyFormatService.processOffer(agentContext, { attachment: offerAttachment, credentialRecord }) + }) + }) + + describe('Create Credential Request', () => { + test('returns credential request message base on existing credential offer message', async () => { + // given + const credentialRecord = mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + + // when + const { format, attachment } = await indyFormatService.acceptOffer(agentContext, { + credentialRecord, + credentialFormats: { + indy: { + holderDid: 'holderDid', + }, + }, + offerAttachment, + }) + + // then + expect(indyHolderService.createCredentialRequest).toHaveBeenCalledTimes(1) + + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJwcm92ZXJfZGlkIjoiaG9sZGVyRGlkIiwiY3JlZF9kZWZfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjM6Q0w6MTY6VEFHIiwiYmxpbmRlZF9tcyI6e30sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnt9LCJub25jZSI6Im5vbmNlIn0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred-req@v2.0', + }) + + const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyCredential) + + expect(credentialRequestMetadata?.schemaId).toBe('aaa') + expect(credentialRequestMetadata?.credentialDefinitionId).toBe('Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG') + }) + }) + + describe('Accept request', () => { + test('Creates a credentials', async () => { + // given + const credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + mockFunction(indyIssuerService.createCredential).mockReturnValue(Promise.resolve([cred, 'x'])) + + // when + const { format, attachment } = await indyFormatService.acceptRequest(agentContext, { + credentialRecord, + requestAttachment, + offerAttachment, + attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + }) + + expect(attachment).toMatchObject({ + id: 'libindy-cred-0', + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaWQiOiJ4c3hzIiwiY3JlZF9kZWZfaWQiOiJ4ZHhkIiwicmV2X3JlZ19pZCI6IngiLCJ2YWx1ZXMiOnsieCI6eyJyYXciOiJ4IiwiZW5jb2RlZCI6InkifX19', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred@v2.0', + }) + }) + }) + + describe('Process Credential', () => { + test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { + // given + credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestSent, + metadata: { indyRequest: { cred_req: 'meta-data' } }, + }) + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + mockFunction(indyLedgerService.getRevocationRegistryDefinition).mockReturnValue( + Promise.resolve(revocationTemplate) + ) + mockFunction(indyHolderService.storeCredential).mockReturnValue(Promise.resolve('100')) + + // when + await indyFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachment, + credentialRecord, + }) + + // then + expect(indyHolderService.storeCredential).toHaveBeenCalledTimes(1) + expect(credentialRecord.credentials.length).toBe(1) + expect(credentialRecord.credentials[0].credentialRecordType).toBe('indy') + expect(credentialRecord.credentials[0].credentialRecordId).toBe('100') + }) + }) +}) diff --git a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts new file mode 100644 index 0000000000..a66b8abe65 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts @@ -0,0 +1,567 @@ +import type { AgentContext } from '../../../../agent' +import type { CredentialFormatService } from '../../formats' +import type { + JsonLdAcceptRequestOptions, + JsonLdCredentialFormat, + SignCredentialOptionsRFC0593, +} from '../../formats/jsonld/JsonLdCredentialFormat' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' +import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' + +import { getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' +import { JsonTransformer } from '../../../../utils' +import { JsonEncoder } from '../../../../utils/JsonEncoder' +import { DidResolverService } from '../../../dids/services/DidResolverService' +import { W3cCredentialRecord, W3cCredentialService } from '../../../vc' +import { Ed25519Signature2018Fixtures } from '../../../vc/__tests__/fixtures' +import { CREDENTIALS_CONTEXT_V1_URL } from '../../../vc/constants' +import { W3cVerifiableCredential } from '../../../vc/models' +import { W3cCredential } from '../../../vc/models/credential/W3cCredential' +import { JsonLdCredentialFormatService } from '../../formats/jsonld/JsonLdCredentialFormatService' +import { CredentialState } from '../../models' +import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID } from '../../protocol/v1/messages' +import { V2CredentialPreview } from '../../protocol/v2/messages' +import { V2OfferCredentialMessage } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' + +jest.mock('../../../vc/W3cCredentialService') +jest.mock('../../../dids/services/DidResolverService') + +const W3cCredentialServiceMock = W3cCredentialService as jest.Mock +const DidResolverServiceMock = DidResolverService as jest.Mock + +const didDocument = { + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + verificationMethod: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx', + }, + ], + authentication: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + assertionMethod: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + keyAgreement: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf', + }, + ], +} + +const vcJson = { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + credentialSubject: { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED.credentialSubject, + alumniOf: 'oops', + }, +} + +const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) + +const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const offerAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +const credentialAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(vc), + }), +}) + +// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` +// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. +const mockCredentialRecord = ({ + state, + threadId, + connectionId, + tags, + id, + credentialAttributes, +}: { + state?: CredentialState + tags?: CustomCredentialTags + threadId?: string + connectionId?: string + id?: string + credentialAttributes?: CredentialPreviewAttribute[] +} = {}) => { + const offerOptions: V2OfferCredentialMessageOptions = { + id: '', + formats: [ + { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + format: 'hlindy/cred-abstract@v2.0', + }, + ], + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + replacementId: undefined, + } + const offerMessage = new V2OfferCredentialMessage(offerOptions) + + const credentialRecord = new CredentialExchangeRecord({ + id, + credentialAttributes: credentialAttributes || credentialPreview.attributes, + state: state || CredentialState.OfferSent, + threadId: threadId ?? offerMessage.id, + connectionId: connectionId ?? '123', + tags, + protocolVersion: 'v2', + }) + + return credentialRecord +} +const inputDoc = { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + alumniOf: 'oops', + }, +} +const verificationMethod = `8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K#8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K` +const credential = JsonTransformer.fromJSON(inputDoc, W3cCredential) + +const signCredentialOptions: SignCredentialOptionsRFC0593 = { + credential, + options: { + proofPurpose: 'assertionMethod', + proofType: 'Ed25519Signature2018', + }, +} + +const requestAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptions), + }), +}) +let jsonldFormatService: CredentialFormatService +let w3cCredentialService: W3cCredentialService +let didResolver: DidResolverService + +describe('JsonLd CredentialFormatService', () => { + let agentContext: AgentContext + beforeEach(async () => { + agentContext = getAgentContext() + w3cCredentialService = new W3cCredentialServiceMock() + didResolver = new DidResolverServiceMock() + jsonldFormatService = new JsonLdCredentialFormatService(w3cCredentialService, didResolver) + }) + + describe('Create JsonLd Credential Proposal / Offer', () => { + test(`Creates JsonLd Credential Proposal`, async () => { + // when + const { attachment, format } = await jsonldFormatService.createProposal(agentContext, { + credentialRecord: mockCredentialRecord(), + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJjcmVkZW50aWFsIjp7ImNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ2czNDJZY3B1azI2M1I5ZDhBcTZNVWF4UG4xRERlSHlHbzM4RWVmWG1nREwiLCJpc3N1YW5jZURhdGUiOiIyMDE3LTEwLTIyVDEyOjIzOjQ4WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjaGVsb3Igb2YgU2NpZW5jZSBhbmQgQXJ0cyJ9LCJhbHVtbmlPZiI6Im9vcHMifX0sIm9wdGlvbnMiOnsicHJvb2ZQdXJwb3NlIjoiYXNzZXJ0aW9uTWV0aG9kIiwicHJvb2ZUeXBlIjoiRWQyNTUxOVNpZ25hdHVyZTIwMTgifX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }) + }) + + test(`Creates JsonLd Credential Offer`, async () => { + // when + const { attachment, previewAttributes, format } = await jsonldFormatService.createOffer(agentContext, { + credentialFormats: { + jsonld: signCredentialOptions, + }, + credentialRecord: mockCredentialRecord(), + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJjcmVkZW50aWFsIjp7ImNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ2czNDJZY3B1azI2M1I5ZDhBcTZNVWF4UG4xRERlSHlHbzM4RWVmWG1nREwiLCJpc3N1YW5jZURhdGUiOiIyMDE3LTEwLTIyVDEyOjIzOjQ4WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjaGVsb3Igb2YgU2NpZW5jZSBhbmQgQXJ0cyJ9LCJhbHVtbmlPZiI6Im9vcHMifX0sIm9wdGlvbnMiOnsicHJvb2ZQdXJwb3NlIjoiYXNzZXJ0aW9uTWV0aG9kIiwicHJvb2ZUeXBlIjoiRWQyNTUxOVNpZ25hdHVyZTIwMTgifX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(previewAttributes).toBeUndefined() + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }) + }) + }) + + describe('Accept Credential Offer', () => { + test('returns credential request message base on existing credential offer message', async () => { + // when + const { attachment, format } = await jsonldFormatService.acceptOffer(agentContext, { + credentialFormats: { + jsonld: undefined, + }, + offerAttachment, + credentialRecord: mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }), + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }) + }) + }) + + describe('Accept Request', () => { + const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' + + test('Derive Verification Method', async () => { + mockFunction(didResolver.resolveDidDocument).mockReturnValue(Promise.resolve(didDocument as any)) + mockFunction(w3cCredentialService.getVerificationMethodTypesByProofType).mockReturnValue([ + 'Ed25519VerificationKey2018', + ]) + + const service = jsonldFormatService as JsonLdCredentialFormatService + const credentialRequest = requestAttachment.getDataAsJson() + + // calls private method in the format service + const verificationMethod = await service['deriveVerificationMethod']( + agentContext, + signCredentialOptions.credential, + credentialRequest + ) + expect(verificationMethod).toBe( + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL' + ) + }) + + test('Creates a credential', async () => { + // given + mockFunction(w3cCredentialService.signCredential).mockReturnValue(Promise.resolve(vc)) + + const credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestReceived, + threadId, + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + const acceptRequestOptions: JsonLdAcceptRequestOptions = { + ...signCredentialOptions, + verificationMethod, + } + + const { format, attachment } = await jsonldFormatService.acceptRequest(agentContext, { + credentialRecord, + credentialFormats: { + jsonld: acceptRequestOptions, + }, + requestAttachment, + offerAttachment, + }) + //then + expect(w3cCredentialService.signCredential).toHaveBeenCalledTimes(1) + + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: expect.any(String), + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }) + }) + }) + + describe('Process Credential', () => { + const credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestSent, + }) + let w3c: W3cCredentialRecord + let signCredentialOptionsWithProperty: SignCredentialOptionsRFC0593 + beforeEach(async () => { + signCredentialOptionsWithProperty = signCredentialOptions + signCredentialOptionsWithProperty.options = { + proofPurpose: 'assertionMethod', + proofType: 'Ed25519Signature2018', + } + + w3c = new W3cCredentialRecord({ + id: 'foo', + createdAt: new Date(), + credential: vc, + tags: { + expandedTypes: [ + 'https://www.w3.org/2018/credentials#VerifiableCredential', + 'https://example.org/examples#UniversityDegreeCredential', + ], + }, + }) + }) + test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when + await jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachment, + credentialRecord, + }) + + // then + expect(w3cCredentialService.storeCredential).toHaveBeenCalledTimes(1) + expect(credentialRecord.credentials.length).toBe(1) + expect(credentialRecord.credentials[0].credentialRecordType).toBe('w3c') + expect(credentialRecord.credentials[0].credentialRecordId).toBe('foo') + }) + + test('throws error if credential subject not equal to request subject', async () => { + const vcJson = { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + credentialSubject: { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED.credentialSubject, + // missing alumni + }, + } + + const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) + const credentialAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(vc), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachment, + credentialRecord, + }) + ).rejects.toThrow('Received credential subject does not match subject from credential request') + }) + + test('throws error if credential domain not equal to request domain', async () => { + signCredentialOptionsWithProperty.options.domain = 'https://test.com' + const requestAttachmentWithDomain = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithDomain, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof domain does not match domain from credential request') + }) + + test('throws error if credential challenge not equal to request challenge', async () => { + signCredentialOptionsWithProperty.options.challenge = '7bf32d0b-39d4-41f3-96b6-45de52988e4c' + const requestAttachmentWithChallenge = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithChallenge, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof challenge does not match challenge from credential request') + }) + + test('throws error if credential proof type not equal to request proof type', async () => { + signCredentialOptionsWithProperty.options.proofType = 'Ed25519Signature2016' + const requestAttachmentWithProofType = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithProofType, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof type does not match proof type from credential request') + }) + + test('throws error if credential proof purpose not equal to request proof purpose', async () => { + signCredentialOptionsWithProperty.options.proofPurpose = 'authentication' + const requestAttachmentWithProofPurpose = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithProofPurpose, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof purpose does not match proof purpose from credential request') + }) + + test('are credentials equal', async () => { + const message1 = new Attachment({ + id: 'cdb0669b-7cd6-46bc-b1c7-7034f86083ac', + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(inputDoc), + }), + }) + + const message2 = new Attachment({ + id: '9a8ff4fb-ac86-478f-b7f9-fbf3f9cc60e6', + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(inputDoc), + }), + }) + + // indirectly test areCredentialsEqual as black box rather than expose that method in the API + let areCredentialsEqual = jsonldFormatService.shouldAutoRespondToProposal(agentContext, { + credentialRecord, + proposalAttachment: message1, + offerAttachment: message2, + }) + expect(areCredentialsEqual).toBe(true) + + const inputDoc2 = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + } + message2.data = new AttachmentData({ + base64: JsonEncoder.toBase64(inputDoc2), + }) + + areCredentialsEqual = jsonldFormatService.shouldAutoRespondToProposal(agentContext, { + credentialRecord, + proposalAttachment: message1, + offerAttachment: message2, + }) + expect(areCredentialsEqual).toBe(false) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/formats/index.ts b/packages/core/src/modules/credentials/formats/index.ts index fd4da3ca50..614b3559a3 100644 --- a/packages/core/src/modules/credentials/formats/index.ts +++ b/packages/core/src/modules/credentials/formats/index.ts @@ -2,3 +2,4 @@ export * from './CredentialFormatService' export * from './CredentialFormatServiceOptions' export * from './CredentialFormat' export * from './indy' +export * from './jsonld' diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index 4847cf9fd7..c97dc188cb 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -94,7 +94,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { + private w3cCredentialService: W3cCredentialService + private didResolver: DidResolverService + + public constructor(w3cCredentialService: W3cCredentialService, didResolver: DidResolverService) { + super() + this.w3cCredentialService = w3cCredentialService + this.didResolver = didResolver + } + + public readonly formatKey = 'jsonld' as const + public readonly credentialRecordType = 'w3c' as const + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the proposed credential + * @returns object containing associated attachment, formats and filtersAttach elements + * + */ + public async createProposal( + agentContext: AgentContext, + { credentialFormats }: FormatCreateProposalOptions + ): Promise { + const format = new CredentialFormatSpec({ + format: JSONLD_VC_DETAIL, + }) + + const jsonLdFormat = credentialFormats.jsonld + if (!jsonLdFormat) { + throw new AriesFrameworkError('Missing jsonld payload in createProposal') + } + + const jsonLdCredential = new JsonLdCredentialDetail(jsonLdFormat) + MessageValidator.validateSync(jsonLdCredential) + + // jsonLdFormat is now of type SignCredentialOptionsRFC0593 + const attachment = this.getFormatData(jsonLdFormat, format.attachId) + return { format, attachment } + } + + /** + * Method called on reception of a propose credential message + * @param options the options needed to accept the proposal + */ + public async processProposal(agentContext: AgentContext, { attachment }: FormatProcessOptions): Promise { + const credProposalJson = attachment.getDataAsJson() + + if (!credProposalJson) { + throw new AriesFrameworkError('Missing jsonld credential proposal data payload') + } + + const messageToValidate = new JsonLdCredentialDetail(credProposalJson) + MessageValidator.validateSync(messageToValidate) + } + + public async acceptProposal( + agentContext: AgentContext, + { attachId, credentialFormats, proposalAttachment }: FormatAcceptProposalOptions + ): Promise { + // if the offer has an attachment Id use that, otherwise the generated id of the formats object + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC_DETAIL, + }) + + const jsonLdFormat = credentialFormats?.jsonld + if (jsonLdFormat) { + // if there is an offer, validate + const jsonLdCredentialOffer = new JsonLdCredentialDetail(jsonLdFormat) + MessageValidator.validateSync(jsonLdCredentialOffer) + } + + const credentialProposal = proposalAttachment.getDataAsJson() + const jsonLdCredentialProposal = new JsonLdCredentialDetail(credentialProposal) + MessageValidator.validateSync(jsonLdCredentialProposal) + + const offerData = jsonLdFormat ?? credentialProposal + + const attachment = this.getFormatData(offerData, format.attachId) + + return { format, attachment } + } + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the credential offer + * @returns object containing associated attachment, formats and offersAttach elements + * + */ + public async createOffer( + agentContext: AgentContext, + { credentialFormats, attachId }: FormatCreateOfferOptions + ): Promise { + // if the offer has an attachment Id use that, otherwise the generated id of the formats object + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC_DETAIL, + }) + + const jsonLdFormat = credentialFormats?.jsonld + if (!jsonLdFormat) { + throw new AriesFrameworkError('Missing jsonld payload in createOffer') + } + + const jsonLdCredential = new JsonLdCredentialDetail(jsonLdFormat) + + MessageValidator.validateSync(jsonLdCredential) + const attachment = this.getFormatData(jsonLdFormat, format.attachId) + + return { format, attachment } + } + + public async processOffer(agentContext: AgentContext, { attachment }: FormatProcessOptions) { + const credentialOfferJson = attachment.getDataAsJson() + + if (!credentialOfferJson) { + throw new AriesFrameworkError('Missing jsonld credential offer data payload') + } + + const jsonLdCredential = new JsonLdCredentialDetail(credentialOfferJson) + MessageValidator.validateSync(jsonLdCredential) + } + + public async acceptOffer( + agentContext: AgentContext, + { credentialFormats, attachId, offerAttachment }: FormatAcceptOfferOptions + ): Promise { + const jsonLdFormat = credentialFormats?.jsonld + + const credentialOffer = offerAttachment.getDataAsJson() + const requestData = jsonLdFormat ?? credentialOffer + + const jsonLdCredential = new JsonLdCredentialDetail(requestData) + MessageValidator.validateSync(jsonLdCredential) + + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC_DETAIL, + }) + + const attachment = this.getFormatData(requestData, format.attachId) + return { format, attachment } + } + + /** + * Create a credential attachment format for a credential request. + * + * @param options The object containing all the options for the credential request is derived + * @returns object containing associated attachment, formats and requestAttach elements + * + */ + public async createRequest( + agentContext: AgentContext, + { credentialFormats }: FormatCreateRequestOptions + ): Promise { + const jsonLdFormat = credentialFormats?.jsonld + + const format = new CredentialFormatSpec({ + format: JSONLD_VC_DETAIL, + }) + + if (!jsonLdFormat) { + throw new AriesFrameworkError('Missing jsonld payload in createRequest') + } + + const jsonLdCredential = new JsonLdCredentialDetail(jsonLdFormat) + MessageValidator.validateSync(jsonLdCredential) + + const attachment = this.getFormatData(jsonLdFormat, format.attachId) + + return { format, attachment } + } + + public async processRequest(agentContext: AgentContext, { attachment }: FormatProcessOptions): Promise { + const requestJson = attachment.getDataAsJson() + + if (!requestJson) { + throw new AriesFrameworkError('Missing jsonld credential request data payload') + } + + const jsonLdCredential = new JsonLdCredentialDetail(requestJson) + MessageValidator.validateSync(jsonLdCredential) + } + + public async acceptRequest( + agentContext: AgentContext, + { credentialFormats, attachId, requestAttachment }: FormatAcceptRequestOptions + ): Promise { + const jsonLdFormat = credentialFormats?.jsonld + + // sign credential here. credential to be signed is received as the request attachment + // (attachment in the request message from holder to issuer) + const credentialRequest = requestAttachment.getDataAsJson() + + const credentialData = jsonLdFormat ?? credentialRequest + const jsonLdCredential = new JsonLdCredentialDetail(credentialData) + MessageValidator.validateSync(jsonLdCredential) + + const verificationMethod = + credentialFormats?.jsonld?.verificationMethod ?? + (await this.deriveVerificationMethod(agentContext, credentialData.credential, credentialRequest)) + + if (!verificationMethod) { + throw new AriesFrameworkError('Missing verification method in credential data') + } + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC, + }) + + const options = credentialData.options + + if (options.challenge || options.domain || options.credentialStatus) { + throw new AriesFrameworkError( + 'The fields challenge, domain and credentialStatus not currently supported in credential options ' + ) + } + + const verifiableCredential = await this.w3cCredentialService.signCredential(agentContext, { + credential: JsonTransformer.fromJSON(credentialData.credential, W3cCredential), + proofType: credentialData.options.proofType, + verificationMethod: verificationMethod, + }) + + const attachment = this.getFormatData(verifiableCredential, format.attachId) + return { format, attachment } + } + + /** + * Derive a verification method using the issuer from the given verifiable credential + * @param credential the verifiable credential we want to sign + * @return the verification method derived from this credential and its associated issuer did, keys etc. + */ + private async deriveVerificationMethod( + agentContext: AgentContext, + credential: W3cCredential, + credentialRequest: SignCredentialOptionsRFC0593 + ): Promise { + // extract issuer from vc (can be string or Issuer) + let issuerDid = credential.issuer + + if (typeof issuerDid !== 'string') { + issuerDid = issuerDid.id + } + // this will throw an error if the issuer did is invalid + const issuerDidDocument = await this.didResolver.resolveDidDocument(agentContext, issuerDid) + + // find first key which matches proof type + const proofType = credentialRequest.options.proofType + + // actually gets the key type(s) + const keyType = this.w3cCredentialService.getVerificationMethodTypesByProofType(proofType) + + if (!keyType || keyType.length === 0) { + throw new AriesFrameworkError(`No Key Type found for proofType ${proofType}`) + } + + const verificationMethod = await findVerificationMethodByKeyType(keyType[0], issuerDidDocument) + if (!verificationMethod) { + throw new AriesFrameworkError(`Missing verification method for key type ${keyType}`) + } + + return verificationMethod.id + } + /** + * Processes an incoming credential - retrieve metadata, retrieve payload and store it in the Indy wallet + * @param options the issue credential message wrapped inside this object + * @param credentialRecord the credential exchange record for this credential + */ + public async processCredential( + agentContext: AgentContext, + { credentialRecord, attachment, requestAttachment }: FormatProcessCredentialOptions + ): Promise { + const credentialAsJson = attachment.getDataAsJson() + const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) + MessageValidator.validateSync(credential) + + // compare stuff in the proof object of the credential and request...based on aca-py + + const requestAsJson = requestAttachment.getDataAsJson() + const request = JsonTransformer.fromJSON(requestAsJson, JsonLdCredentialDetail) + + if (Array.isArray(credential.proof)) { + // question: what do we compare here, each element of the proof array with the request??? + throw new AriesFrameworkError('Credential arrays are not supported') + } else { + // do checks here + this.compareCredentialSubject(credential, request.credential) + this.compareProofs(credential.proof, request.options) + } + + const verifiableCredential = await this.w3cCredentialService.storeCredential(agentContext, { + credential: credential, + }) + + credentialRecord.credentials.push({ + credentialRecordType: this.credentialRecordType, + credentialRecordId: verifiableCredential.id, + }) + } + + private compareCredentialSubject(credential: W3cVerifiableCredential, request: W3cCredential): void { + if (!deepEqual(credential.credentialSubject, request.credentialSubject)) { + throw new AriesFrameworkError('Received credential subject does not match subject from credential request') + } + } + + private compareProofs(credentialProof: LinkedDataProof, requestProof: JsonLdOptionsRFC0593): void { + if (credentialProof.domain !== requestProof.domain) { + throw new AriesFrameworkError('Received credential proof domain does not match domain from credential request') + } + + if (credentialProof.challenge !== requestProof.challenge) { + throw new AriesFrameworkError( + 'Received credential proof challenge does not match challenge from credential request' + ) + } + + if (credentialProof.type !== requestProof.proofType) { + throw new AriesFrameworkError('Received credential proof type does not match proof type from credential request') + } + + if (credentialProof.proofPurpose !== requestProof.proofPurpose) { + throw new AriesFrameworkError( + 'Received credential proof purpose does not match proof purpose from credential request' + ) + } + } + + public supportsFormat(format: string): boolean { + const supportedFormats = [JSONLD_VC_DETAIL, JSONLD_VC] + + return supportedFormats.includes(format) + } + + public async deleteCredentialById(): Promise { + throw new Error('Not implemented.') + } + + public areCredentialsEqual = (message1: Attachment, message2: Attachment): boolean => { + // FIXME: this implementation doesn't make sense. We can't loop over stringified objects... + const obj1 = message1.getDataAsJson() + const obj2 = message2.getDataAsJson() + + return deepEqual(obj1, obj2) + } + + public shouldAutoRespondToProposal( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: FormatAutoRespondProposalOptions + ) { + return this.areCredentialsEqual(proposalAttachment, offerAttachment) + } + + public shouldAutoRespondToOffer( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: FormatAutoRespondOfferOptions + ) { + return this.areCredentialsEqual(proposalAttachment, offerAttachment) + } + + public shouldAutoRespondToRequest( + agentContext: AgentContext, + { offerAttachment, requestAttachment }: FormatAutoRespondRequestOptions + ) { + return this.areCredentialsEqual(offerAttachment, requestAttachment) + } + + public shouldAutoRespondToCredential( + agentContext: AgentContext, + { credentialAttachment, requestAttachment }: FormatAutoRespondCredentialOptions + ) { + const credentialAsJson = credentialAttachment.getDataAsJson() + const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) + + if (Array.isArray(credential.proof)) { + throw new AriesFrameworkError('Credential arrays are not supported') + } else { + // do checks here + try { + const requestAsJson = requestAttachment.getDataAsJson() + const request = JsonTransformer.fromJSON(requestAsJson, JsonLdCredentialDetail) + this.compareCredentialSubject(credential, request.credential) + this.compareProofs(credential.proof, request.options) + return true + } catch (error) { + return false + } + } + } +} diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts new file mode 100644 index 0000000000..410a190902 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts @@ -0,0 +1,30 @@ +import { Expose, Type } from 'class-transformer' + +import { W3cCredential } from '../../../vc/models/credential/W3cCredential' + +import { JsonLdOptionsRFC0593 } from './JsonLdOptionsRFC0593' + +export interface JsonLdCredentialDetailOptions { + credential: W3cCredential + options: JsonLdOptionsRFC0593 +} + +/** + * Class providing validation for the V2 json ld credential as per RFC0593 (used to sign credentials) + * + */ +export class JsonLdCredentialDetail { + public constructor(options: JsonLdCredentialDetailOptions) { + if (options) { + this.credential = options.credential + this.options = options.options + } + } + + @Type(() => W3cCredential) + public credential!: W3cCredential + + @Expose({ name: 'options' }) + @Type(() => JsonLdOptionsRFC0593) + public options!: JsonLdOptionsRFC0593 +} diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts new file mode 100644 index 0000000000..77eff33a6c --- /dev/null +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts @@ -0,0 +1,59 @@ +import { IsObject, IsOptional, IsString } from 'class-validator' + +export interface JsonLdOptionsCredentialStatusOptions { + type: string +} + +export class JsonLdOptionsCredentialStatus { + public constructor(options: JsonLdOptionsCredentialStatusOptions) { + if (options) { + this.type = options.type + } + } + @IsString() + public type!: string +} + +export interface JsonLdOptionsRFC0593Options { + proofPurpose: string + created?: string + domain?: string + challenge?: string + credentialStatus?: JsonLdOptionsCredentialStatus + proofType: string +} + +export class JsonLdOptionsRFC0593 { + public constructor(options: JsonLdOptionsRFC0593Options) { + if (options) { + this.proofPurpose = options.proofPurpose + this.created = options.created + this.domain = options.domain + this.challenge = options.challenge + this.credentialStatus = options.credentialStatus + this.proofType = options.proofType + } + } + + @IsString() + public proofPurpose!: string + + @IsString() + @IsOptional() + public created?: string + + @IsString() + @IsOptional() + public domain?: string + + @IsString() + @IsOptional() + public challenge?: string + + @IsString() + public proofType!: string + + @IsOptional() + @IsObject() + public credentialStatus?: JsonLdOptionsCredentialStatus +} diff --git a/packages/core/src/modules/credentials/formats/jsonld/index.ts b/packages/core/src/modules/credentials/formats/jsonld/index.ts new file mode 100644 index 0000000000..bce1b302d7 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/jsonld/index.ts @@ -0,0 +1,4 @@ +export * from './JsonLdCredentialFormatService' +export * from './JsonLdCredentialFormat' +export * from './JsonLdCredentialOptions' +export * from './JsonLdOptionsRFC0593' diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts index fa84e9d439..6c627e0fb6 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts @@ -29,7 +29,9 @@ import { credDef, credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' import { IndyCredentialUtils } from '../../../formats/indy/IndyCredentialUtils' -import { CredentialState, AutoAcceptCredential, CredentialFormatSpec } from '../../../models' +import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' +import { CredentialFormatSpec } from '../../../models/CredentialFormatSpec' +import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../repository/CredentialRepository' diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index 5201f2464b..42da4ed4da 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -7,7 +7,6 @@ import { setupCredentialTests, waitForCredentialRecord } from '../../../../../.. import testLogger from '../../../../../../tests/logger' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { sleep } from '../../../../../utils/sleep' import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -341,60 +340,6 @@ describe('credentials', () => { } }) - test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { - testLogger.test('Alice sends credential proposal to Faber') - const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, - }, - }, - comment: 'v1 propose credential test', - }) - - testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialExchangeRecord.threadId, - state: CredentialState.ProposalReceived, - }) - - await faberAgent.credentials.negotiateProposal({ - credentialRecordId: faberCredentialExchangeRecord.id, - credentialFormats: { - indy: { - credentialDefinitionId: credDefId, - attributes: newCredentialPreview.attributes, - }, - }, - }) - - testLogger.test('Alice waits for credential offer from Faber') - - const record = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(record.id).not.toBeNull() - expect(record.getTags()).toEqual({ - threadId: record.threadId, - state: record.state, - connectionId: aliceConnection.id, - credentialIds: [], - }) - - // Check if the state of the credential records did not change - faberCredentialExchangeRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) - faberCredentialExchangeRecord.assertState(CredentialState.OfferSent) - - const aliceRecord = await aliceAgent.credentials.getById(record.id) - aliceRecord.assertState(CredentialState.OfferReceived) - }) - test('Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ @@ -442,8 +387,6 @@ describe('credentials', () => { state: CredentialState.ProposalReceived, }) - await sleep(5000) - // Check if the state of fabers credential record did not change const faberRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) faberRecord.assertState(CredentialState.ProposalReceived) @@ -451,5 +394,59 @@ describe('credentials', () => { aliceCredentialExchangeRecord = await aliceAgent.credentials.getById(aliceCredentialExchangeRecord.id) aliceCredentialExchangeRecord.assertState(CredentialState.ProposalSent) }) + + test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v1', + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + comment: 'v1 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + await faberAgent.credentials.negotiateProposal({ + credentialRecordId: faberCredentialExchangeRecord.id, + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: newCredentialPreview.attributes, + }, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + + const record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(record.id).not.toBeNull() + expect(record.getTags()).toEqual({ + threadId: record.threadId, + state: record.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + + // Check if the state of the credential records did not change + faberCredentialExchangeRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) + faberCredentialExchangeRecord.assertState(CredentialState.OfferSent) + + const aliceRecord = await aliceAgent.credentials.getById(record.id) + aliceRecord.assertState(CredentialState.OfferReceived) + }) }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index 0ee7ea021c..1a6777bd63 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -503,18 +503,26 @@ export class CredentialFormatCoordinator { { credentialRecord, message, + requestMessage, formatServices, }: { credentialRecord: CredentialExchangeRecord message: V2IssueCredentialMessage + requestMessage: V2RequestCredentialMessage formatServices: CredentialFormatService[] } ) { for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.credentialAttachments) + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) await formatService.processCredential(agentContext, { attachment, + requestAttachment, credentialRecord, }) } diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts index 3b2ac2bdf1..dd0b511ac1 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -24,7 +24,7 @@ import type { CredentialFormatService, CredentialFormatServiceMap, } from '../../formats' -import type { CredentialFormatSpec } from '../../models' +import type { CredentialFormatSpec } from '../../models/CredentialFormatSpec' import { Dispatcher } from '../../../../agent/Dispatcher' import { EventEmitter } from '../../../../agent/EventEmitter' @@ -40,6 +40,7 @@ import { RoutingService } from '../../../routing/services/RoutingService' import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' import { CredentialProblemReportReason } from '../../errors' import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' +import { JsonLdCredentialFormatService } from '../../formats/jsonld/JsonLdCredentialFormatService' import { AutoAcceptCredential, CredentialState } from '../../models' import { CredentialExchangeRecord, CredentialRepository } from '../../repository' import { CredentialService } from '../../services/CredentialService' @@ -49,12 +50,12 @@ import { arePreviewAttributesEqual } from '../../util/previewAttributes' import { CredentialFormatCoordinator } from './CredentialFormatCoordinator' import { V2CredentialAckHandler, - V2CredentialProblemReportHandler, V2IssueCredentialHandler, V2OfferCredentialHandler, V2ProposeCredentialHandler, V2RequestCredentialHandler, } from './handlers' +import { V2CredentialProblemReportHandler } from './handlers/V2CredentialProblemReportHandler' import { V2CredentialAckMessage, V2CredentialProblemReportMessage, @@ -80,6 +81,7 @@ export class V2CredentialService ({ ...formatServiceMap, [formatService.formatKey]: formatService, @@ -567,7 +569,7 @@ export class V2CredentialService const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock +const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock @@ -59,6 +62,7 @@ const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() const indyCredentialFormatService = new IndyCredentialFormatServiceMock() +const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() @@ -66,6 +70,10 @@ const connectionService = new ConnectionServiceMock() // @ts-ignore indyCredentialFormatService.formatKey = 'indy' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +jsonLdCredentialFormatService.formatKey = 'jsonld' + const agentConfig = getAgentConfig('V2CredentialServiceCredTest') const agentContext = getAgentContext() @@ -261,6 +269,7 @@ describe('CredentialService', () => { eventEmitter, credentialRepository, indyCredentialFormatService, + jsonLdCredentialFormatService, agentConfig.logger, new CredentialsModuleConfig() ) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts index 45c987a818..a580005723 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts @@ -19,6 +19,7 @@ import { CredentialEventTypes } from '../../../CredentialEvents' import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { credDef, schema } from '../../../__tests__/fixtures' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' +import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' import { CredentialFormatSpec } from '../../../models' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -31,6 +32,7 @@ import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' jest.mock('../../../repository/CredentialRepository') jest.mock('../../../../ledger/services/IndyLedgerService') jest.mock('../../../formats/indy/IndyCredentialFormatService') +jest.mock('../../../formats/jsonld/JsonLdCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') @@ -40,6 +42,7 @@ jest.mock('../../../../../agent/Dispatcher') const CredentialRepositoryMock = CredentialRepository as jest.Mock const IndyLedgerServiceMock = IndyLedgerService as jest.Mock const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock +const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock @@ -50,6 +53,7 @@ const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() const indyLedgerService = new IndyLedgerServiceMock() const indyCredentialFormatService = new IndyCredentialFormatServiceMock() +const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() @@ -57,6 +61,9 @@ const connectionService = new ConnectionServiceMock() // @ts-ignore indyCredentialFormatService.formatKey = 'indy' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +jsonLdCredentialFormatService.formatKey = 'jsonld' const agentConfig = getAgentConfig('V2CredentialServiceOfferTest') const agentContext = getAgentContext() @@ -104,6 +111,7 @@ describe('V2CredentialServiceOffer', () => { eventEmitter, credentialRepository, indyCredentialFormatService, + jsonLdCredentialFormatService, agentConfig.logger, new CredentialsModuleConfig() ) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 19bcf6b6f0..0bd25c345e 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -168,9 +168,6 @@ describe('v2 credentials', () => { await aliceAgent.wallet.delete() }) - // ============================== - // TESTS v2 BEGIN - // ========================== test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { testLogger.test('Alice sends credential proposal to Faber') const schemaId = schema.id diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts new file mode 100644 index 0000000000..5d9d85f596 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -0,0 +1,167 @@ +import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { Wallet } from '../../../../../wallet' +import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { CreateOfferOptions } from '../../../CredentialsApiOptions' +import type { SignCredentialOptionsRFC0593 } from '../../../formats/jsonld/JsonLdCredentialFormat' + +import { ReplaySubject, Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' +import { JsonTransformer } from '../../../../../../src/utils' +import { getAgentOptions, prepareForIssuance, waitForCredentialRecordSubject } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { Agent } from '../../../../../agent/Agent' +import { InjectionSymbols } from '../../../../../constants' +import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures' +import { W3cCredential } from '../../../../vc/models/' +import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialState } from '../../../models' +import { CredentialExchangeRecord } from '../../../repository' + +const faberAgentOptions = getAgentOptions('Faber LD connection-less Credentials V2', { + endpoints: ['rxjs:faber'], +}) + +const aliceAgentOptions = getAgentOptions('Alice LD connection-less Credentials V2', { + endpoints: ['rxjs:alice'], +}) + +let wallet +let credential: W3cCredential +let signCredentialOptions: SignCredentialOptionsRFC0593 + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberReplay: ReplaySubject + let aliceReplay: ReplaySubject + const seed = 'testseed000000000000000000000001' + + beforeEach(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + faberAgent = new Agent(faberAgentOptions) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + + await prepareForIssuance(faberAgent, ['name', 'age']) + + faberReplay = new ReplaySubject() + aliceReplay = new ReplaySubject() + + faberAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(faberReplay) + aliceAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(aliceReplay) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + + credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) + + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Faber starts with V2 W3C connection-less credential offer to Alice', async () => { + const offerOptions: CreateOfferOptions = { + comment: 'V2 Out of Band offer (W3C)', + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + } + testLogger.test('Faber sends credential offer to Alice') + + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + + const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberCredentialRecord.id, + message, + domain: 'https://a-domain.com', + }) + await aliceAgent.receiveMessage(offerMessage.toJSON()) + + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + testLogger.test('Alice sends credential request to Faber') + + const credentialRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + testLogger.test('Faber waits for credential request from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: credentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + faberCredentialRecord = await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + aliceCredentialRecord = await aliceAgent.credentials.acceptCredential({ + credentialRecordId: aliceCredentialRecord.id, + }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + threadId: expect.any(String), + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + threadId: expect.any(String), + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts new file mode 100644 index 0000000000..d91a5afb01 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -0,0 +1,382 @@ +import type { ProposeCredentialOptions } from '../../..' +import type { Agent } from '../../../../../agent/Agent' +import type { Wallet } from '../../../../../wallet' +import type { ConnectionRecord } from '../../../../connections' +import type { + JsonLdCredentialFormat, + SignCredentialOptionsRFC0593, +} from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { V2CredentialService } from '../V2CredentialService' + +import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { InjectionSymbols } from '../../../../../constants' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures' +import { W3cCredential } from '../../../../../modules/vc/models' +import { JsonTransformer } from '../../../../../utils' +import { AutoAcceptCredential, CredentialState } from '../../../models' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + let aliceCredentialRecord: CredentialExchangeRecord + let credential: W3cCredential + let signCredentialOptions: SignCredentialOptionsRFC0593 + let wallet + const seed = 'testseed000000000000000000000001' + + describe('Auto accept on `always`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: always v2 jsonld', + 'alice agent: always v2 jsonld', + AutoAcceptCredential.Always + )) + + credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + + const options: ProposeCredentialOptions< + [JsonLdCredentialFormat], + [V2CredentialService<[JsonLdCredentialFormat]>] + > = { + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + } + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + aliceCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Faber sends V2 credential offer to Alice as start of protocol process') + + const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + }) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + testLogger.test('Faber waits for credential ack from Alice') + const faberCredentialRecord: CredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.CredentialReceived, + }) + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + }) + }) + + describe('Auto accept on `contentApproved`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: content-approved v2 jsonld', + 'alice agent: content-approved v2 jsonld', + AutoAcceptCredential.ContentApproved + )) + credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + testLogger.test('Faber sends credential offer to Alice') + const faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 JsonLd Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.CredentialReceived, + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Faber sends credential offer to Alice') + + let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + if (!aliceCredentialRecord.connectionId) { + throw new AriesFrameworkError('missing alice connection id') + } + + // we do not need to specify connection id in this object + // it is either connectionless or included in the offer message + testLogger.test('Alice sends credential request to faber') + faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + + const faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.CredentialReceived, + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Faber sends credential offer to Alice') + + const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + }) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + testLogger.test('Alice sends credential request to Faber') + + const aliceExchangeCredentialRecord = await aliceAgent.credentials.negotiateOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + const faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceExchangeCredentialRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + // Check if the state of faber credential record did not change + const faberRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) + faberRecord.assertState(CredentialState.ProposalReceived) + + aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) + aliceCredentialRecord.assertState(CredentialState.ProposalSent) + }) + test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + await faberAgent.credentials.negotiateProposal({ + credentialRecordId: faberCredentialRecord.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + + const record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(record.id).not.toBeNull() + expect(record.getTags()).toEqual({ + threadId: record.threadId, + state: record.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(record.type).toBe(CredentialExchangeRecord.type) + + // Check if the state of the credential records did not change + faberCredentialRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) + faberCredentialRecord.assertState(CredentialState.OfferSent) + + const aliceRecord = await aliceAgent.credentials.getById(record.id) + aliceRecord.assertState(CredentialState.OfferReceived) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts new file mode 100644 index 0000000000..f712bfa43e --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -0,0 +1,551 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { Wallet } from '../../../../../wallet' +import type { ConnectionRecord } from '../../../../connections' +import type { SignCredentialOptionsRFC0593 } from '../../../formats/jsonld/JsonLdCredentialFormat' + +import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { InjectionSymbols } from '../../../../../constants' +import { DidCommMessageRepository } from '../../../../../storage' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { W3cCredential } from '../../../../vc/models/credential/W3cCredential' +import { CredentialState } from '../../../models' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V2CredentialPreview } from '../messages' +import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' +import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let aliceCredentialRecord: CredentialExchangeRecord + let faberCredentialRecord: CredentialExchangeRecord + + let didCommMessageRepository: DidCommMessageRepository + + const inputDoc = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + } + + const credential = JsonTransformer.fromJSON(inputDoc, W3cCredential) + + let signCredentialOptions: SignCredentialOptionsRFC0593 + + let wallet + const seed = 'testseed000000000000000000000001' + let credDefId: string + + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, aliceConnection } = await setupCredentialTests( + 'Faber Agent Credentials LD', + 'Alice Agent Credentials LD' + )) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 (ld format, Ed25519 signature) credential proposal to Faber', async () => { + testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') + + const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test for W3C Credentials', + }) + + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.protocolVersion).toEqual('v2') + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { + associatedRecordId: aliceCredentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + '@id': expect.any(String), + comment: 'V2 W3C Offer', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }, + ], + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~service': undefined, + '~attach': undefined, + '~please_ack': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + credential_preview: expect.any(Object), + replacement_id: undefined, + }) + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (aliceCredentialRecord.connectionId) { + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: undefined, + }, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + 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') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: 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: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) + } + }) + + test('Multiple Formats: Alice starts with V2 (both ld and indy formats) credential proposal to Faber', async () => { + testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') + // set the propose options - using both indy and ld credential formats here + const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + const testAttributes = { + attributes: credentialPreview.attributes, + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', + } + + testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') + + const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + indy: testAttributes, + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.protocolVersion).toEqual('v2') + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + testLogger.test('Faber sends credential offer to Alice') + + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C & INDY Proposals', + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: credentialPreview.attributes, + }, + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + // didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() + + expect(credOfferJson).toMatchObject({ + credential: { + context: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + // type: [Array], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + }) + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + '@id': expect.any(String), + comment: 'V2 W3C & INDY Proposals', + formats: [ + { + attach_id: expect.any(String), + format: 'hlindy/cred-abstract@v2.0', + }, + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }, + ], + credential_preview: { + '@type': 'https://didcomm.org/issue-credential/2.0/credential-preview', + attributes: expect.any(Array), + }, + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~service': undefined, + '~attach': undefined, + '~please_ack': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + replacement_id: undefined, + }) + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (aliceCredentialRecord.connectionId) { + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + 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') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: 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: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + const w3cCredential = credentialMessage.credentialAttachments[1].getDataAsJson() + + expect(w3cCredential).toMatchObject({ + context: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'Ed25519Signature2018', + created: expect.any(String), + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + proofPurpose: 'assertionMethod', + }, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'hlindy/cred@v2.0', + }, + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) + } + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts index ededafbd35..22edc8ad72 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -53,7 +53,6 @@ export class V2IssueCredentialHandler implements Handler { const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { credentialRecord, }) - if (messageContext.connection) { return createOutboundDIDCommV1Message(messageContext.connection, message) } else if (requestMessage?.service && messageContext.message.service) { diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index f15ecc8831..45ff363a8d 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -38,7 +38,6 @@ export class V2OfferCredentialHandler implements Handler { credentialRecord, offerMessage: messageContext.message, }) - if (shouldAutoRespond) { return await this.acceptOffer(credentialRecord, messageContext) } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts index c485feef59..bc22c9952b 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -76,6 +76,6 @@ export class V2RequestCredentialHandler implements Handler { }) } - this.logger.error(`Could not automatically create credential request`) + this.logger.error(`Could not automatically issue credential`) } } diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts index 4f3a066835..5316933952 100644 --- a/packages/core/src/modules/dids/domain/DidDocument.ts +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -11,7 +11,7 @@ import { getKeyDidMappingByVerificationMethod } from './key-type' import { IndyAgentService, ServiceTransformer, DidCommV1Service } from './service' import { VerificationMethodTransformer, VerificationMethod, IsStringOrVerificationMethod } from './verificationMethod' -type DidPurpose = +export type DidPurpose = | 'authentication' | 'keyAgreement' | 'assertionMethod' @@ -234,7 +234,6 @@ export async function findVerificationMethodByKeyType( 'capabilityInvocation', 'capabilityDelegation', ] - for await (const purpose of didVerificationMethods) { const key: VerificationMethod[] | (string | VerificationMethod)[] | undefined = didDocument[purpose] if (key instanceof Array) { diff --git a/packages/core/src/modules/proofs/formats/ProofFormatService.ts b/packages/core/src/modules/proofs/formats/ProofFormatService.ts index 92e00838ff..1ce367cf33 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatService.ts @@ -13,7 +13,7 @@ import type { GetRequestedCredentialsFormat } from './indy/IndyProofFormatsServi import type { ProofAttachmentFormat } from './models/ProofAttachmentFormat' import type { CreatePresentationFormatsOptions, - CreateProposalOptions, + FormatCreateProofProposalOptions, CreateRequestOptions, FormatCreatePresentationOptions, ProcessPresentationOptions, @@ -40,7 +40,7 @@ export abstract class ProofFormatService { this.agentConfig = agentConfig } - abstract createProposal(options: CreateProposalOptions): Promise + abstract createProposal(options: FormatCreateProofProposalOptions): Promise abstract processProposal(options: ProcessProposalOptions): Promise diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts index 1d51478e76..12641a4fb4 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts @@ -11,7 +11,7 @@ import type { ProofAttachmentFormat } from '../models/ProofAttachmentFormat' import type { CreatePresentationFormatsOptions, CreateProofAttachmentOptions, - CreateProposalOptions, + FormatCreateProofProposalOptions, CreateRequestAttachmentOptions, CreateRequestOptions, FormatCreatePresentationOptions, @@ -133,7 +133,7 @@ export class IndyProofFormatService extends ProofFormatService { return { format, attachment } } - public async createProposal(options: CreateProposalOptions): Promise { + public async createProposal(options: FormatCreateProofProposalOptions): Promise { if (!options.formats.indy) { throw Error('Missing indy format to create proposal attachment format') } diff --git a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts index 1a377f4af2..2538aaf1b4 100644 --- a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts @@ -15,7 +15,7 @@ export interface CreateProofAttachmentOptions { proofProposalOptions: ProofRequestOptions } -export interface CreateProposalOptions { +export interface FormatCreateProofProposalOptions { id?: string formats: ProposeProofFormats } diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts index 66d12e5d64..c15302a7a8 100644 --- a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -12,11 +12,6 @@ export interface SignCredentialOptions { verificationMethod: string proofPurpose?: ProofPurpose created?: string - domain?: string - challenge?: string - credentialStatus?: { - type: string - } } export interface VerifyCredentialOptions { diff --git a/packages/core/src/utils/objEqual.ts b/packages/core/src/utils/objEqual.ts new file mode 100644 index 0000000000..2909b5c450 --- /dev/null +++ b/packages/core/src/utils/objEqual.ts @@ -0,0 +1,23 @@ +export function deepEqual(a: any, b: any): boolean { + if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) { + const count = [0, 0] + for (const key in a) count[0]++ + for (const key in b) count[1]++ + if (count[0] - count[1] != 0) { + return false + } + for (const key in a) { + if (!(key in b) || !deepEqual(a[key], b[key])) { + return false + } + } + for (const key in b) { + if (!(key in a) || !deepEqual(b[key], a[key])) { + return false + } + } + return true + } else { + return a === b + } +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 39b79cd84e..a8b89bfaec 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -25,6 +25,7 @@ import { catchError, filter, map, timeout } from 'rxjs/operators' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { BbsModule } from '../../bbs-signatures/src/BbsModule' import { agentDependencies, WalletScheme } from '../../node/src' import { Agent, @@ -100,7 +101,6 @@ export function getAgentOptions( logger: new TestLogger(LogLevel.off, name), ...extraConfig, } - return { config, modules, dependencies: agentDependencies } as const } @@ -223,7 +223,7 @@ export function waitForCredentialRecordSubject( threadId, state, previousState, - timeoutMs = 10000, + timeoutMs = 15000, // sign and store credential in W3c credential service take several seconds }: { threadId?: string state?: CredentialState @@ -666,15 +666,28 @@ export async function setupCredentialTests( 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - const faberAgentOptions = getAgentOptions(faberName, { - endpoints: ['rxjs:faber'], - autoAcceptCredentials, - }) - const aliceAgentOptions = getAgentOptions(aliceName, { - endpoints: ['rxjs:alice'], - autoAcceptCredentials, - }) + // TODO remove the dependency on BbsModule + const modules = { + bbs: new BbsModule(), + } + const faberAgentOptions = getAgentOptions( + faberName, + { + endpoints: ['rxjs:faber'], + autoAcceptCredentials, + }, + modules + ) + + const aliceAgentOptions = getAgentOptions( + aliceName, + { + endpoints: ['rxjs:alice'], + autoAcceptCredentials, + }, + modules + ) const faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) diff --git a/yarn.lock b/yarn.lock index 12c63cbfe5..2dc9456804 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,25 +42,25 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" - integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" + integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" - integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" + integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.1" "@babel/helper-module-transforms" "^7.19.0" "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" + "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" @@ -92,14 +92,14 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" - integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" + integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== dependencies: - "@babel/compat-data" "^7.19.0" + "@babel/compat-data" "^7.19.1" "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.20.2" + browserslist "^4.21.3" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": @@ -123,7 +123,7 @@ "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": +"@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== @@ -203,15 +203,15 @@ integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6" - integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-member-expression-to-functions" "^7.18.9" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" "@babel/helper-simple-access@^7.18.6": version "7.18.6" @@ -240,9 +240,9 @@ integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== "@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== "@babel/helper-validator-option@^7.18.6": version "7.18.6" @@ -267,10 +267,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" - integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" + integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": version "7.18.6" @@ -619,15 +619,15 @@ regenerator-transform "^0.15.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" - integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz#a3df2d7312eea624c7889a2dcd37fd1dfd25b2c6" + integrity sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA== dependencies: "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.9" - babel-plugin-polyfill-corejs2 "^0.3.2" - babel-plugin-polyfill-corejs3 "^0.5.3" - babel-plugin-polyfill-regenerator "^0.4.0" + "@babel/helper-plugin-utils" "^7.19.0" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" semver "^6.3.0" "@babel/plugin-transform-shorthand-properties@^7.0.0": @@ -660,9 +660,9 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.18.6", "@babel/plugin-transform-typescript@^7.5.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.0.tgz#50c3a68ec8efd5e040bde2cd764e8e16bc0cbeaf" - integrity sha512-DOOIywxPpkQHXijXv+s9MDAyZcLp12oYRl3CMWZ6u7TjSoCBq/KqHR/nNFR3+i2xqheZxoF0H2XyL7B6xeSRuA== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz#adcf180a041dcbd29257ad31b0c65d4de531ce8d" + integrity sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ== dependencies: "@babel/helper-create-class-features-plugin" "^7.19.0" "@babel/helper-plugin-utils" "^7.19.0" @@ -721,10 +721,10 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" - integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.2": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" + integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== dependencies: "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" @@ -732,7 +732,7 @@ "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/types" "^7.19.0" debug "^4.1.0" globals "^11.1.0" @@ -2369,9 +2369,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.18.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.1.tgz#ce5e2c8c272b99b7a9fd69fa39f0b4cd85028bd9" - integrity sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA== + version "7.18.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" + integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== dependencies: "@babel/types" "^7.3.0" @@ -2421,18 +2421,18 @@ integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== "@types/express-serve-static-core@^4.17.18": - version "4.17.30" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" - integrity sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ== + version "4.17.31" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" + integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/express@^4.17.13": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" - integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.18" @@ -2440,9 +2440,9 @@ "@types/serve-static" "*" "@types/ffi-napi@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.5.tgz#0b2dc2d549361947a117d55156ff34fd9632c3df" - integrity sha512-WDPpCcHaPhHmP1FIw3ds/+OLt8bYQ/h3SO7o+8kH771PL21kHVzTwii7+WyMBXMQrBsR6xVU2y7w+h+9ggpaQw== + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.6.tgz#cd1c65cc9e701de664e640ccb17a2e823a674d44" + integrity sha512-yrBtqeVD1aeVo271jXVEo3iAtbzSGVGRssJv9W9JlUfg5Z5FgHJx2MV88GRwVATu/XWg6zyenW/cb1MNAuOtaQ== dependencies: "@types/node" "*" "@types/ref-napi" "*" @@ -2560,9 +2560,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.0.tgz#ea03e9f0376a4446f44797ca19d9c46c36e352dc" - integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + version "2.7.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" + integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== "@types/prop-types@*": version "15.7.5" @@ -2587,25 +2587,25 @@ "@types/react" "^17" "@types/react@^17": - version "17.0.49" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.49.tgz#df87ba4ca8b7942209c3dc655846724539dc1049" - integrity sha512-CCBPMZaPhcKkYUTqFs/hOWqKjPxhTEmnZWjlHHgIMop67DsXywf9B5Os9Hz8KSacjNOgIdnZVJamwl232uxoPg== + version "17.0.50" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.50.tgz#39abb4f7098f546cfcd6b51207c90c4295ee81fc" + integrity sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" csstype "^3.0.2" "@types/ref-napi@*", "@types/ref-napi@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.4.tgz#d7edc063b244c85767867ce1167ec2d7051728a1" - integrity sha512-ng8SCmdZbz1GHaW3qgGoX9IaHoIvgMqgBHLe3sv18NbAkHVgnjRW8fJq51VTUm4lnJyLu60q9/002o7qjOg13g== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.5.tgz#8db441d381737af5c353d7dd89c7593b5f2080c8" + integrity sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw== dependencies: "@types/node" "*" "@types/ref-struct-di@*": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.6.tgz#9775753b24ba5bf248dd66d79d4fdb7cebef6e95" - integrity sha512-+Sa2H3ynDYo2ungR3d5kmNetlkAYNqQVjJvs1k7i6zvo7Zu/qb+OsrXU54RuiOYJCwY9piN+hOd4YRRaiEOqgw== + version "1.1.7" + resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz#85e0149858a81a14f12f15ff31a6dffa42bab2d3" + integrity sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g== dependencies: "@types/ref-napi" "*" @@ -2640,9 +2640,9 @@ integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/validator@^13.1.3": - version "13.7.6" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.6.tgz#631f1acd15cbac9cb0a114da7e87575f1c95b46a" - integrity sha512-uBsnWETsUagQ0n6G2wcXNIufpTNJir0zqzG4p62fhnwzs48d/iuOWEEo0d3iUxN7D+9R/8CSvWGKS+KmaD0mWA== + version "13.7.7" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.7.tgz#e87cf34dd08522d21acf30130fd8941f433b81b5" + integrity sha512-jiEw2kTUJ8Jsh4A1K4b5Pkjj9Xz6FktLLOQ36ZVLRkmxFbpTvAV2VRoKMojz8UlZxNg/2dZqzpigH4JYn1bkQg== "@types/varint@^6.0.0": version "6.0.0" @@ -3274,7 +3274,7 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.3.2: +babel-plugin-polyfill-corejs2@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== @@ -3283,15 +3283,15 @@ babel-plugin-polyfill-corejs2@^0.3.2: "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" - integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.2" - core-js-compat "^3.21.0" + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" -babel-plugin-polyfill-regenerator@^0.4.0: +babel-plugin-polyfill-regenerator@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== @@ -3512,15 +3512,15 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.20.2, browserslist@^4.21.3: - version "4.21.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== +browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" node-releases "^2.0.6" - update-browserslist-db "^1.0.5" + update-browserslist-db "^1.0.9" bs-logger@0.x: version "0.2.6" @@ -3671,10 +3671,10 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001370: - version "1.0.30001399" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001399.tgz#1bf994ca375d7f33f8d01ce03b7d5139e8587873" - integrity sha512-4vQ90tMKS+FkvuVWS5/QY1+d805ODxZiKFzsU8o/RsVJz49ZSRR8EjykLJbqhzdPgadbX6wB538wOzle3JniRA== +caniuse-lite@^1.0.30001400: + version "1.0.30001412" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz#30f67d55a865da43e0aeec003f073ea8764d5d7c" + integrity sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA== canonicalize@^1.0.1: version "1.0.8" @@ -4185,12 +4185,12 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== -core-js-compat@^3.21.0: - version "3.25.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.1.tgz#6f13a90de52f89bbe6267e5620a412c7f7ff7e42" - integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== +core-js-compat@^3.25.1: + version "3.25.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.3.tgz#d6a442a03f4eade4555d4e640e6a06151dd95d38" + integrity sha512-xVtYpJQ5grszDHEUU9O7XbjjcZ0ccX3LgQsyqSvTnjX97ZqEgn9F5srmrwwwMtbKzDllyFPL+O+2OFMl1lU4TQ== dependencies: - browserslist "^4.21.3" + browserslist "^4.21.4" core-util-is@1.0.2: version "1.0.2" @@ -4286,9 +4286,9 @@ cssstyle@^2.3.0: cssom "~0.3.6" csstype@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" - integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== dargs@^7.0.0: version "7.0.0" @@ -4366,9 +4366,9 @@ decamelize@^1.1.0, decamelize@^1.2.0: integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decimal.js@^10.2.1: - version "10.4.0" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.0.tgz#97a7448873b01e92e5ff9117d89a7bca8e63e0fe" - integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== + version "10.4.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" + integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== decode-uri-component@^0.2.0: version "0.2.0" @@ -4605,10 +4605,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.202: - version "1.4.248" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.248.tgz#dd2dab68277e91e8452536ee9265f484066f94ad" - integrity sha512-qShjzEYpa57NnhbW2K+g+Fl+eNoDvQ7I+2MRwWnU6Z6F0HhXekzsECCLv+y2OJUsRodjqoSfwHkIX42VUFtUzg== +electron-to-chromium@^1.4.251: + version "1.4.262" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.262.tgz#25715dfbae4c2e0640517cba184715241ecd8e63" + integrity sha512-Ckn5haqmGh/xS8IbcgK3dnwAVnhDyo/WQnklWn6yaMucYTq7NNxwlGE8ElzEOnonzRLzUCo2Ot3vUb2GYUF2Hw== emittery@^0.8.1: version "0.8.1" @@ -4684,21 +4684,21 @@ errorhandler@^1.5.0: escape-html "~1.0.3" es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.2.tgz#8495a07bc56d342a3b8ea3ab01bd986700c2ccb3" - integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== + version "1.20.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.3.tgz#90b143ff7aedc8b3d189bcfac7f1e3e3f81e9da1" + integrity sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.2" + get-intrinsic "^1.1.3" get-symbol-description "^1.0.0" has "^1.0.3" has-property-descriptors "^1.0.0" has-symbols "^1.0.3" internal-slot "^1.0.3" - is-callable "^1.2.4" + is-callable "^1.2.6" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" @@ -4708,6 +4708,7 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 object-keys "^1.1.1" object.assign "^4.1.4" regexp.prototype.flags "^1.4.3" + safe-regex-test "^1.0.0" string.prototype.trimend "^1.0.5" string.prototype.trimstart "^1.0.5" unbox-primitive "^1.0.2" @@ -5186,9 +5187,9 @@ fastq@^1.6.0: reusify "^1.0.4" fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" @@ -5341,9 +5342,9 @@ flatted@^3.1.0: integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== flow-parser@0.*: - version "0.186.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.186.0.tgz#ef6f4c7a3d8eb29fdd96e1d1f651b7ccb210f8e9" - integrity sha512-QaPJczRxNc/yvp3pawws439VZ/vHGq+i1/mZ3bEdSaRy8scPgZgiWklSB6jN7y5NR9sfgL4GGIiBcMXTj3Opqg== + version "0.187.1" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.187.1.tgz#52b2c7ebd7544b75bda0676380138bc5b3de3177" + integrity sha512-ZvlTeakTTMmYGukt4EIQtLEp4ie45W+jK325uukGgiqFg2Rl7TdpOJQbOLUN2xMeGS+WvXaK0uIJ3coPGDXFGQ== flow-parser@^0.121.0: version "0.121.0" @@ -5525,7 +5526,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== @@ -6130,10 +6131,10 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.5.tgz#6123e0b1fef5d7591514b371bb018204892f1a2b" - integrity sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw== +is-callable@^1.1.4, is-callable@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-ci@^2.0.0: version "2.0.0" @@ -6960,9 +6961,9 @@ jetifier@^1.6.2: integrity sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw== joi@^17.2.1: - version "17.6.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" - integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== + version "17.6.1" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.1.tgz#e77422f277091711599634ac39a409e599d7bdaa" + integrity sha512-Hl7/iBklIX345OCM1TiFSCZRVaAOLDGlWCp0Df2vWYgBgjkezaR7Kvm3joBciBHQjZj5sxXs859r6eqsRSlG8w== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -9205,9 +9206,9 @@ rc@^1.2.8: strip-json-comments "~2.0.1" react-devtools-core@^4.6.0: - version "4.25.0" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.25.0.tgz#78b11a2c9f81dd9ebff3745ab4ee2147cc96c12a" - integrity sha512-iewRrnu0ZnmfL+jJayKphXj04CFh6i3ezVnpCtcnZbTPSQgN09XqHAzXbKbqNDl7aTg9QLNkQRP6M3DvdrinWA== + version "4.26.0" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.0.tgz#d3d0f59d62ccf1ac03017a7e92f0fe71455019cc" + integrity sha512-OO0Q+vXtHYCXvRQ6elLiOUph3MjsCpuYktGTLnBpizYm46f8tAPuJKihGkwsceitHSJNpzNIjJaYHgX96CyTUQ== dependencies: shell-quote "^1.6.1" ws "^7" @@ -9483,10 +9484,10 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" - integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== dependencies: regenerate "^1.4.2" @@ -9530,26 +9531,26 @@ regexpp@^3.1.0: integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" - integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + version "5.2.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" + integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" + regenerate-unicode-properties "^10.1.0" + regjsgen "^0.7.1" + regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" - integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== +regjsgen@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" + integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" - integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== dependencies: jsesc "~0.5.0" @@ -9742,9 +9743,9 @@ rxjs@^6.6.0: tslib "^1.9.0" rxjs@^7.2.0: - version "7.5.6" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.6.tgz#0446577557862afd6903517ce7cae79ecb9662bc" - integrity sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw== + version "7.5.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" + integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== dependencies: tslib "^2.1.0" @@ -9758,6 +9759,15 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, s resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -10825,9 +10835,9 @@ uglify-es@^3.1.9: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.17.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" - integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== + version "3.17.2" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.2.tgz#f55f668b9a64b213977ae688703b6bbb7ca861c6" + integrity sha512-bbxglRjsGQMchfvXZNusUcYgiB9Hx2K4AHYXQy2DITZ9Rd+JzhX7+hoocE5Winr7z2oHvPsekkBwXtigvxevXg== uid-number@0.0.6: version "0.0.6" @@ -10873,9 +10883,9 @@ unicode-match-property-value-ecmascript@^2.0.0: integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" - integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== union-value@^1.0.0: version "1.0.1" @@ -10939,10 +10949,10 @@ upath@^2.0.1: resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -update-browserslist-db@^1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.8.tgz#2f0b711327668eee01bbecddcf4a7c7954a7f8e2" - integrity sha512-GHg7C4M7oJSJYW/ED/5QOJ7nL/E0lwTOBGsOorA7jqHr8ExUhPfwAotIAmdSw/LWv3SMLSNpzTAgeLG9zaZKTA== +update-browserslist-db@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" + integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== dependencies: escalade "^3.1.1" picocolors "^1.0.0"