From 2b464a281aad14ea0e0f7248cbccf405b16cb5e5 Mon Sep 17 00:00:00 2001 From: Al Rosenthal Date: Fri, 29 Nov 2024 11:01:36 -0800 Subject: [PATCH] feat: Missing Attribute Proof request (#4) * formatting updated Signed-off-by: al-rosenthal * feat: added new tests for proof flow Signed-off-by: al-rosenthal * feat: added test for sending a credential Signed-off-by: al-rosenthal * feat: added writeQRCode method Signed-off-by: al-rosenthal --------- Signed-off-by: al-rosenthal --- invitations | 0 src/AgentCredo.ts | 76 +-- src/AgentManual.ts | 122 ++-- src/AgentTraction.ts | 1520 ++++++++++++++++++++++++------------------ src/basic.test.ts | 711 +++++++++++++------- src/credo.test.ts | 21 +- src/lib.ts | 7 + 7 files changed, 1471 insertions(+), 986 deletions(-) create mode 100644 invitations diff --git a/invitations b/invitations new file mode 100644 index 0000000..e69de29 diff --git a/src/AgentCredo.ts b/src/AgentCredo.ts index 8d5ad88..f78cce6 100644 --- a/src/AgentCredo.ts +++ b/src/AgentCredo.ts @@ -179,7 +179,7 @@ export const createAgent = async ( agent.registerOutboundTransport(wsTransport); agent.registerOutboundTransport(httpTransport); - + await agent.initialize(); console.log("AFJ Agent initialized"); @@ -187,9 +187,9 @@ export const createAgent = async ( if (config.mediatorInvitationUrl) { await agent.mediationRecipient.initiateMessagePickup(undefined, MediatorPickupStrategy.Implicit) } - + createLinkSecretIfRequired(agent); - + const indyVdrPoolService = agent.dependencyManager.resolve(IndyVdrPoolService); await Promise.all( @@ -197,7 +197,7 @@ export const createAgent = async ( (pool as unknown as any).pool.refresh() ) ); - + return agent; }; @@ -219,19 +219,19 @@ export class AgentCredo implements AriesAgent { ( | V1CredentialProtocol | V2CredentialProtocol< - ( - | LegacyIndyCredentialFormatService - | AnonCredsCredentialFormatService - )[] - > + ( + | LegacyIndyCredentialFormatService + | AnonCredsCredentialFormatService + )[] + > )[] >; proofs: ProofsModule< ( | V1ProofProtocol | V2ProofProtocol< - (LegacyIndyProofFormatService | AnonCredsProofFormatService)[] - > + (LegacyIndyProofFormatService | AnonCredsProofFormatService)[] + > )[] >; }>; @@ -257,28 +257,28 @@ export class AgentCredo implements AriesAgent { this.basicMessageReceivedSubscription?.unsubscribe() this.basicMessageReceivedSubscription = undefined } - + private filterByType = (recordClass: RecordClass) => { return pipe( map((event: BaseEvent) => (event.payload as Record).record), filter((record: R) => record.type === recordClass.type), ) } - + private recordsAddedByType = (recordClass: RecordClass) => { if (!this.agent) { throw new Error('Agent is required to check record type') } - + if (!recordClass) { throw new Error("The recordClass can't be undefined") } - + return this.agent?.events.observable>(RepositoryEventTypes.RecordSaved).pipe(this.filterByType(recordClass)) } sendBasicMessage(connection_id: string, content: string): Promise { - return this.agent.basicMessages.sendMessage(connection_id, content) + return this.agent.basicMessages.sendMessage(connection_id, content); } sendOOBConnectionlessProofRequest( @@ -302,28 +302,27 @@ export class AgentCredo implements AriesAgent { } async acceptProof(proof: AcceptProofArgs): Promise { - - while (true) { - const proofs = await this.agent.proofs.getAll(); - //console.log(`Proofs ${proofs.length}`) - for (let index = 0; index < proofs.length; index++) { - const p = proofs[index]; - //console.dir(p.toJSON()); - if ("id" in proof) { - //console.log(`[${index + 1}/${proofs.length}] - id:${p.id}, threadId:${p.threadId}, arg:${proof.id}`); - if (p.threadId === proof.id) { - await this.agent.proofs.acceptRequest({ proofRecordId: p.id }); - return; - } - } else if ("connection_id" in proof) { - if (p.connectionId === proof.connection_id){ - await this.agent.proofs.acceptRequest({ proofRecordId: p.id }); - return; - } + while (true) { + const proofs = await this.agent.proofs.getAll(); + //console.log(`Proofs ${proofs.length}`) + for (let index = 0; index < proofs.length; index++) { + const p = proofs[index]; + //console.dir(p.toJSON()); + if ("id" in proof) { + //console.log(`[${index + 1}/${proofs.length}] - id:${p.id}, threadId:${p.threadId}, arg:${proof.id}`); + if (p.threadId === proof.id) { + await this.agent.proofs.acceptRequest({ proofRecordId: p.id }); + return; + } + } else if ("connection_id" in proof) { + if (p.connectionId === proof.connection_id) { + await this.agent.proofs.acceptRequest({ proofRecordId: p.id }); + return; } } - waitFor(1000); } + waitFor(1000); + } } async findCredentialOffer(connectionId: string): Promise { @@ -345,8 +344,9 @@ export class AgentCredo implements AriesAgent { createSchema(_builder: SchemaBuilder): Promise { throw new Error("Method not implemented."); } - - async createInvitationToConnect(_invitationType: T): Promise> { + async createInvitationToConnect( + _invitationType: T + ): Promise> { throw new Error("Method not implemented."); } @@ -406,6 +406,6 @@ export class AgentCredo implements AriesAgent { await this.agent?.mediationRecipient?.stopMessagePickup(); await this.agent?.shutdown(); - this.basicMessageReceivedSubscription?.unsubscribe() + this.basicMessageReceivedSubscription?.unsubscribe() } } diff --git a/src/AgentManual.ts b/src/AgentManual.ts index 0a36b8a..cdcec06 100644 --- a/src/AgentManual.ts +++ b/src/AgentManual.ts @@ -1,66 +1,88 @@ -import { AcceptProofArgs, AriesAgent, CreateInvitationResponse, CredentialOfferRef, INVITATION_TYPE, ReceiveInvitationResponse, ResponseCreateInvitation } from "./Agent"; +import { + AcceptProofArgs, + AriesAgent, + CreateInvitationResponse, + CredentialOfferRef, + INVITATION_TYPE, + ReceiveInvitationResponse, + ResponseCreateInvitation, +} from "./Agent"; import { Logger } from "@credo-ts/core"; -import { CredentialDefinitionBuilder, ProofRequestBuilder, SchemaBuilder } from "./lib"; -import QRCode from 'qrcode' -import fs from 'node:fs'; -import path from 'node:path'; -import { log } from "console" -import chalk from "chalk" +import { + CredentialDefinitionBuilder, + ProofRequestBuilder, + SchemaBuilder, +} from "./lib"; +import QRCode from "qrcode"; +import fs from "node:fs"; +import path from "node:path"; +import { log } from "console"; +import chalk from "chalk"; export class AgentManual implements AriesAgent { - config: any; - public readonly logger: Logger; - public constructor(config:any,logger: Logger){ - this.config = config - this.logger = logger - } + config: any; + public readonly logger: Logger; + public constructor(config: any, logger: Logger) { + this.config = config; + this.logger = logger; + } sendBasicMessage(_connection_id: string, content: string): Promise { - log(chalk.yellowBright(`> Send a message with '${content}' as content`)) - return Promise.resolve() + log(chalk.yellowBright(`> Send a message with '${content}' as content`)); + return Promise.resolve(); } - sendOOBConnectionlessProofRequest(_builder: ProofRequestBuilder): Promise { + sendOOBConnectionlessProofRequest( + _builder: ProofRequestBuilder + ): Promise { throw new Error("Method not implemented."); } acceptProof(_proof: AcceptProofArgs): Promise { //throw new Error("Method not implemented."); - return Promise.resolve() + return Promise.resolve(); } waitForPresentation(_presentation_exchange_id: string): Promise { throw new Error("Method not implemented."); } - sendConnectionlessProofRequest(_builder: ProofRequestBuilder): Promise { + sendConnectionlessProofRequest( + _builder: ProofRequestBuilder + ): Promise { + throw new Error("Method not implemented."); + } + async acceptCredentialOffer(_offer: CredentialOfferRef): Promise { + console.warn("Accept Credential"); + } + async findCredentialOffer(connectionId: string): Promise { + return { id: "undefined", connection_id: connectionId }; + } + createSchemaCredDefinition( + _credDefBuilder: CredentialDefinitionBuilder + ): Promise { + throw new Error("Method not implemented."); + } + createSchema(_builder: SchemaBuilder): Promise { throw new Error("Method not implemented."); } - async acceptCredentialOffer(_offer: CredentialOfferRef): Promise { - console.warn('Accept Credential') - } - async findCredentialOffer(connectionId: string): Promise { - return {id: 'undefined', connection_id: connectionId} - } - createSchemaCredDefinition(_credDefBuilder: CredentialDefinitionBuilder): Promise { - throw new Error("Method not implemented."); - } - createSchema(_builder: SchemaBuilder): Promise { - throw new Error("Method not implemented."); - } - async createInvitationToConnect(_invitationType: T): Promise> { - throw new Error("Method not implemented."); - } - async receiveInvitation(ref: ResponseCreateInvitation, appName:string = 'BC Wallet App'): Promise { - const relativePath = './tmp/__qrcode.png' - const QRCodePath = path.resolve(process.cwd() as string, relativePath) - fs.mkdirSync(path.dirname(QRCodePath), { recursive: true }) - //fs.writeFileSync(path.join(process.cwd(), 'invitation_url.txt'), ref.payload.invitation_url) - //fs.writeFileSync(path.join(process.cwd(), 'invitation.json'), JSON.stringify((ref.payload.invitation as any))) - await QRCode.toFile( - QRCodePath, - ref.payload.invitation_url, - {margin: 10} + async createInvitationToConnect( + _invitationType: T + ): Promise> { + throw new Error("Method not implemented."); + } + async receiveInvitation( + ref: ResponseCreateInvitation, + appName: string = "BC Wallet App" + ): Promise { + const relativePath = "./tmp/__qrcode.png"; + const QRCodePath = path.resolve(process.cwd() as string, relativePath); + fs.mkdirSync(path.dirname(QRCodePath), { recursive: true }); + //fs.writeFileSync(path.join(process.cwd(), 'invitation_url.txt'), ref.payload.invitation_url) + //fs.writeFileSync(path.join(process.cwd(), 'invitation.json'), JSON.stringify((ref.payload.invitation as any))) + await QRCode.toFile(QRCodePath, ref.payload.invitation_url, { margin: 10 }); + log( + chalk.yellowBright( + `> Scan QR Code image using "${appName}" from ${relativePath}` ) - log(chalk.yellowBright(`> Scan QR Code image using "${appName}" from ${relativePath}`)) - return {} - } - public async startup(){ - } - public async shutdown() {} -} \ No newline at end of file + ); + return {}; + } + public async startup() {} + public async shutdown() {} +} diff --git a/src/AgentTraction.ts b/src/AgentTraction.ts index 6aaecce..5ff92ba 100644 --- a/src/AgentTraction.ts +++ b/src/AgentTraction.ts @@ -1,592 +1,757 @@ import _axios, { AxiosInstance } from "axios"; -import { AcceptProofArgs, AriesAgent, ConnectionRef, CreateInvitationResponse, CredentialOfferRef, INVITATION_TYPE, ReceiveInvitationResponse, ResponseCreateInvitation, ResponseCreateInvitationV1, ResponseCreateInvitationV2 } from "./Agent"; -import { CredentialDefinitionBuilder, extractResponseData, IssueCredentialPreviewV1, printResponse, ProofRequestBuilder, SchemaBuilder } from "./lib"; +import { + AcceptProofArgs, + AriesAgent, + ConnectionRef, + CreateInvitationResponse, + CredentialOfferRef, + INVITATION_TYPE, + ReceiveInvitationResponse, + ResponseCreateInvitation, + ResponseCreateInvitationV1, + ResponseCreateInvitationV2, +} from "./Agent"; +import { + CredentialDefinitionBuilder, + extractResponseData, + IssueCredentialPreviewV1, + printResponse, + ProofRequestBuilder, + SchemaBuilder, +} from "./lib"; import { Logger } from "@credo-ts/core"; export class AgentTraction implements AriesAgent { public axios: AxiosInstance; - private config: any + private config: any; public readonly logger: Logger; - public constructor(config:any, logger:Logger){ - this.config = config - this.axios = _axios.create({baseURL: config.base_url}) - this.logger = logger + public constructor(config: any, logger: Logger) { + this.config = config; + this.axios = _axios.create({ baseURL: config.base_url }); + this.logger = logger; /* - this.axios.interceptors.request.use(function (config) { - this.logger.info(`Requesting ${config.url}`) - return config; - }, function (error) { - // Do something with request error - return Promise.reject(error); - }); - */ + this.axios.interceptors.request.use(function (config) { + this.logger.info(`Requesting ${config.url}`) + return config; + }, function (error) { + // Do something with request error + return Promise.reject(error); + }); + */ } shutdown(): Promise { //throw new Error("Method not implemented."); - return Promise.resolve() + return Promise.resolve(); } acceptProof(_proof: AcceptProofArgs): Promise { throw new Error("Method not implemented."); } - async waitForLedgerTransactionAcked ( txn_id:string, counter: number) { - await this.axios.get(`/transactions/${txn_id}`, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then((value)=>{ - this.logger.info(`transaction ${txn_id} state: ${value.data.state}`) - if (value.data.state !== 'transaction_acked') { - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this.waitForLedgerTransactionAcked(txn_id, counter + 1)) - }, 2000); - }) - } - }) - } - async _waitForOOBConnectionRecord (invitation_msg_id:string, counter: number): Promise { - return this.axios.get(`/connections`, { - params: { - invitation_msg_id: invitation_msg_id - }, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then((value)=>{ - const results = value.data.results as any[] - if (results.length == 0) { - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this._waitForOOBConnectionRecord(invitation_msg_id, counter + 1)) - }, 2000); - }) - } - return results[0] as ConnectionRef - }) - } - async _waitForConnectionReadyByInvitationKey (invitation_key:string, counter: number): Promise { - return await this.axios.get(`/connections`, { - params: {invitation_key}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then((value)=>{ - if (value.data.results.length > 0 ) { - const conn = value.data.results[0] - this.logger.info(`connection state: ${conn.state}`) - if (conn.state === 'active') { - return conn + async waitForLedgerTransactionAcked(txn_id: string, counter: number) { + await this.axios + .get(`/transactions/${txn_id}`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then((value) => { + this.logger.info(`transaction ${txn_id} state: ${value.data.state}`); + if (value.data.state !== "transaction_acked") { + return new Promise((resolve) => { + setTimeout(() => { + resolve(this.waitForLedgerTransactionAcked(txn_id, counter + 1)); + }, 2000); + }); } - - } - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this._waitForConnectionReadyByInvitationKey(invitation_key, counter + 1)) - }, 2000); + }); + } + async _waitForOOBConnectionRecord( + invitation_msg_id: string, + counter: number + ): Promise { + return this.axios + .get(`/connections`, { + params: { + invitation_msg_id: invitation_msg_id, + }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, }) - }) - } - async _waitForConnectionReady (connection_id:string, counter: number) { - await this.axios.get(`/connections/${connection_id}`, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then((value)=>{ - this.logger.info(`connection state: ${value.data.state}`) - if (value.data.state !== 'active') { - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this._waitForConnectionReady(connection_id, counter + 1)) - }, 2000); - }) - } - }) + .then(printResponse) + .then((value) => { + const results = value.data.results as any[]; + if (results.length == 0) { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + this._waitForOOBConnectionRecord(invitation_msg_id, counter + 1) + ); + }, 2000); + }); + } + return results[0] as ConnectionRef; + }); + } + async _waitForConnectionReady(connection_id: string, counter: number) { + await this.axios + .get(`/connections/${connection_id}`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then((value) => { + this.logger.info(`connection state: ${value.data.state}`); + if (value.data.state !== "active") { + return new Promise((resolve) => { + setTimeout(() => { + resolve(this._waitForConnectionReady(connection_id, counter + 1)); + }, 2000); + }); + } + }); } - - async _waitForProofRequestV1 (presentation_exchange_id: string, config: any, http: AxiosInstance, counter: number) { + + async _waitForProofRequestV1( + presentation_exchange_id: string, + config: any, + http: AxiosInstance, + counter: number + ) { //this.logger.info(`/present-proof/records/${config.presentation_exchange_id}`) - await http.get(`/present-proof/records/${presentation_exchange_id}`, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - //.then(printResponse) - .then((value)=>{ - this.logger.info(`proof request state: ${value.data.state} #${counter}`) - if (!(value.data.state === 'verified' || value.data.state === 'abandoned')) { - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this._waitForProofRequestV1(presentation_exchange_id, config, http, counter + 1)) - }, 2000); - }) - } - }) - } - async _waitForProofRequestV2 (presentation_exchange_id: string, config: any, http: AxiosInstance, counter: number) { + await http + .get(`/present-proof/records/${presentation_exchange_id}`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + //.then(printResponse) + .then((value) => { + this.logger.info( + `proof request state: ${value.data.state} #${counter}` + ); + if ( + !(value.data.state === "verified" || value.data.state === "abandoned") + ) { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + this._waitForProofRequestV1( + presentation_exchange_id, + config, + http, + counter + 1 + ) + ); + }, 2000); + }); + } + }); + } + async _waitForProofRequestV2( + presentation_exchange_id: string, + config: any, + http: AxiosInstance, + counter: number + ) { //this.logger.info(`/present-proof/records/${config.presentation_exchange_id}`) - await http.get(`/present-proof-2.0/records/${presentation_exchange_id}`, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - //.then(printResponse) - .then((value)=>{ - this.logger.info(`proof request state: ${value.data.state} #${counter}`) - if (!(value.data.state === 'done' || value.data.state === 'abandoned')) { - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this._waitForProofRequestV2(presentation_exchange_id, config, http, counter + 1)) - }, 2000); - }) - } - }) + await http + .get(`/present-proof-2.0/records/${presentation_exchange_id}`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + //.then(printResponse) + .then((value) => { + this.logger.info( + `proof request state: ${value.data.state} #${counter}` + ); + if ( + !(value.data.state === "done" || value.data.state === "abandoned") + ) { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + this._waitForProofRequestV2( + presentation_exchange_id, + config, + http, + counter + 1 + ) + ); + }, 2000); + }); + } + }); } async clearAllRecords() { - let records: any[] | undefined = undefined + let records: any[] | undefined = undefined; - this.logger.info(`Clearing Presentantion Exchage Records `) - while (records==undefined || records?.length > 0) { - records = await this.axios.get(`/present-proof/records`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then(extractResponseData) - .then((data:any) =>{return data.results}) + this.logger.info(`Clearing Presentantion Exchage Records `); + while (records == undefined || records?.length > 0) { + records = await this.axios + .get(`/present-proof/records`, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(printResponse) + .then(extractResponseData) + .then((data: any) => { + return data.results; + }); if (records !== undefined && records.length > 0) { for (const record of records) { - await this.axios.delete(`/present-proof/records/${record.presentation_exchange_id}`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }).then(printResponse) + await this.axios + .delete( + `/present-proof/records/${record.presentation_exchange_id}`, + { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + } + ) + .then(printResponse); } } } - this.logger.info(`Clearing Credential Issuance Records `) - records=undefined - while (records==undefined || records?.length > 0) { - records = await this.axios.get(`/issue-credential/records`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(extractResponseData) - .then((data:any) =>{return data.results}) + this.logger.info(`Clearing Credential Issuance Records `); + records = undefined; + while (records == undefined || records?.length > 0) { + records = await this.axios + .get(`/issue-credential/records`, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(extractResponseData) + .then((data: any) => { + return data.results; + }); if (records !== undefined && records?.length > 0) { for (const record of records) { - await this.axios.delete(`/issue-credential/records/${record.credential_exchange_id}`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }).then(printResponse) + await this.axios + .delete( + `/issue-credential/records/${record.credential_exchange_id}`, + { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + } + ) + .then(printResponse); } } } - this.logger.info(`Clearing Connections`) - records=undefined - while (records==undefined || records?.length > 0) { - records = await this.axios.get(`/connections`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then(extractResponseData) - .then((data:any) =>{return data.results}) - records= records?.filter((item)=>!item?.alias?.endsWith('-endorser')) + this.logger.info(`Clearing Connections`); + records = undefined; + while (records == undefined || records?.length > 0) { + records = await this.axios + .get(`/connections`, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(printResponse) + .then(extractResponseData) + .then((data: any) => { + return data.results; + }); + records = records?.filter((item) => !item?.alias?.endsWith("-endorser")); if (records !== undefined && records.length > 0) { for (const record of records) { - await this.axios.delete(`/connections/${record.connection_id}`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }).then(printResponse) + await this.axios + .delete(`/connections/${record.connection_id}`, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(printResponse); } } } } waitForPresentation(presentation_exchange_id: string): Promise { - this.logger.info(`Waiting for Presentation ...`) - return this._waitForProofRequestV1(presentation_exchange_id, this.config, this.axios, 0) - + this.logger.info(`Waiting for Presentation ...`); + return this._waitForProofRequestV1( + presentation_exchange_id, + this.config, + this.axios, + 0 + ); } async waitForPresentationV2(presentation_exchange_id: string): Promise { - this.logger.info(`Waiting for Presentation ...`) - return this._waitForProofRequestV2(presentation_exchange_id, this.config, this.axios, 0) - + this.logger.info(`Waiting for Presentation ...`); + return this._waitForProofRequestV2( + presentation_exchange_id, + this.config, + this.axios, + 0 + ); } async createPresentProofV1(builder: ProofRequestBuilder): Promise { - const proofRequest = builder.build() - return await this.axios.post(`/present-proof/create-request`,{ - "auto_remove": false, - "auto_verify": true, - "comment": "string", - "trace": false, - proof_request: proofRequest - }, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then(extractResponseData) - } - async createPresentProofV2(builder: ProofRequestBuilder): Promise { - const proofRequest = builder.buildv2() - return await this.axios.post(`/present-proof-2.0/create-request`,{ - "auto_remove": false, - "auto_verify": true, - "comment": "string", - "trace": false, - presentation_request: proofRequest - }, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then(extractResponseData) + const proofRequest = builder.build(); + return await this.axios + .post( + `/present-proof/create-request`, + { + auto_remove: false, + auto_verify: true, + comment: "string", + trace: false, + proof_request: proofRequest, + }, + { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + } + ) + .then(printResponse) + .then(extractResponseData); + } + async createPresentProofV2( + builder: ProofRequestBuilder + ): Promise { + const proofRequest = builder.buildv2(); + return await this.axios + .post( + `/present-proof-2.0/create-request`, + { + auto_remove: false, + auto_verify: true, + comment: "string", + trace: false, + presentation_request: proofRequest, + }, + { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + } + ) + .then(printResponse) + .then(extractResponseData); } - async sendOOBConnectionlessProofRequest(builder: ProofRequestBuilder): Promise { - const proof = await this.createPresentProofV1(builder) + async sendOOBConnectionlessProofRequest( + builder: ProofRequestBuilder + ): Promise { + const proof = await this.createPresentProofV1(builder); const create_invitation_payload = { - "attachments": [ + attachments: [ { - "id": proof["presentation_exchange_id"], - "type": "present-proof", - "data": {"json": proof}, - } + id: proof["presentation_exchange_id"], + type: "present-proof", + data: { json: proof }, + }, ], - "label": "vc-authn-oidc", + label: "vc-authn-oidc", //"goal": "request-proof", //"goal_code": "request-proof", - "use_public_did": false, + use_public_did: false, //handshake_protocols:['https://didcomm.org/connections/1.0'], //handshake_protocols:['https://didcomm.org/connections/1.0', 'https://didcomm.org/didexchange/1.0'], - } - this.logger.info('create_invitation_payload', create_invitation_payload) - const invitation: any = (await this.axios.post(`/out-of-band/create-invitation`, create_invitation_payload, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }).then(extractResponseData)) - this.logger.info('OOB_invitation', invitation) - delete invitation.invitation.handshake_protocols - invitation.invitation_url = 'bcwallet://launch?oob='+encodeURIComponent(Buffer.from(JSON.stringify(invitation.invitation)).toString('base64')) - - return { + }; + this.logger.info("create_invitation_payload", create_invitation_payload); + const invitation: any = await this.axios + .post(`/out-of-band/create-invitation`, create_invitation_payload, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(extractResponseData); + this.logger.info("OOB_invitation", invitation); + delete invitation.invitation.handshake_protocols; + invitation.invitation_url = + "bcwallet://launch?oob=" + + encodeURIComponent( + Buffer.from(JSON.stringify(invitation.invitation)).toString("base64") + ); + + return { type: INVITATION_TYPE.OOB_DIDX_1_1, payload: { invitation: invitation.invitation, invitation_url: invitation.invitation_url, - presentation_exchange_id:proof.presentation_exchange_id, - invi_msg_id: invitation.invi_msg_id - } - } + presentation_exchange_id: proof.presentation_exchange_id, + invi_msg_id: invitation.invi_msg_id, + }, + }; } - async sendOOBConnectionlessProofRequestV2(builder: ProofRequestBuilder): Promise { - const proof = await this.createPresentProofV2(builder) + async sendOOBConnectionlessProofRequestV2( + builder: ProofRequestBuilder + ): Promise { + const proof = await this.createPresentProofV2(builder); const create_invitation_payload = { - "attachments": [ + attachments: [ { - "id": proof["pres_ex_id"], - "type": "present-proof", - "data": {"json": proof}, - } + id: proof["pres_ex_id"], + type: "present-proof", + data: { json: proof }, + }, ], - "label": "vc-authn-oidc", + label: "vc-authn-oidc", //"goal": "request-proof", //"goal_code": "request-proof", - "use_public_did": false, + use_public_did: false, //handshake_protocols:['https://didcomm.org/connections/1.0'], //handshake_protocols:['https://didcomm.org/connections/1.0', 'https://didcomm.org/didexchange/1.0'], - } - this.logger.info('create_invitation_payload', create_invitation_payload) - const invitation: any = (await this.axios.post(`/out-of-band/create-invitation`, create_invitation_payload, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }).then(extractResponseData)) - this.logger.info('OOB_invitation', invitation) - delete invitation.invitation.handshake_protocols - invitation.invitation_url = 'bcwallet://launch?oob='+encodeURIComponent(Buffer.from(JSON.stringify(invitation.invitation)).toString('base64')) + }; + this.logger.info("create_invitation_payload", create_invitation_payload); + const invitation: any = await this.axios + .post(`/out-of-band/create-invitation`, create_invitation_payload, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(extractResponseData); + this.logger.info("OOB_invitation", invitation); + delete invitation.invitation.handshake_protocols; + invitation.invitation_url = + "bcwallet://launch?oob=" + + encodeURIComponent( + Buffer.from(JSON.stringify(invitation.invitation)).toString("base64") + ); //return {...invitation, presentation_exchange_id:proof["pres_ex_id"]} - return { + return { type: INVITATION_TYPE.OOB_DIDX_1_1, payload: { invitation: invitation.invitation, invitation_url: invitation.invitation_url, presentation_exchange_id: proof.pres_ex_id, - invi_msg_id: invitation.invi_msg_id - } - } + invi_msg_id: invitation.invi_msg_id, + }, + }; } - async sendConnectionlessProofRequestV2(builder: ProofRequestBuilder): Promise { - const wallet: any = (await this.axios.get(`/wallet/did/public`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }).then(extractResponseData)) + async sendConnectionlessProofRequestV2( + builder: ProofRequestBuilder + ): Promise { + const wallet: any = await this.axios + .get(`/wallet/did/public`, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(extractResponseData); //console.dir(['wallet', wallet]) - const proof = await this.createPresentProofV2(builder) - const invitation = JSON.parse(JSON.stringify(proof.pres_request)) - invitation['comment']= null - invitation['~service']= { - "recipientKeys": [wallet.result.verkey], - "routingKeys": null, - "serviceEndpoint": this.config.serviceEndpoint - } - invitation['@type'] ='https://didcomm.org/present-proof/2.0/request-presentation' - return Promise.resolve(invitation).then(value => { - const baseUrl = 'bcwallet://launch' - const invitation_url = baseUrl+'?c_i='+encodeURIComponent(Buffer.from(JSON.stringify(value, undefined, 2)).toString('base64')) + const proof = await this.createPresentProofV2(builder); + const invitation = JSON.parse(JSON.stringify(proof.pres_request)); + invitation["comment"] = null; + invitation["~service"] = { + recipientKeys: [wallet.result.verkey], + routingKeys: null, + serviceEndpoint: this.config.serviceEndpoint, + }; + invitation["@type"] = + "https://didcomm.org/present-proof/2.0/request-presentation"; + return Promise.resolve(invitation).then((value) => { + const baseUrl = "bcwallet://launch"; + const invitation_url = + baseUrl + + "?c_i=" + + encodeURIComponent( + Buffer.from(JSON.stringify(value, undefined, 2)).toString("base64") + ); return { type: INVITATION_TYPE.CONN_1_0, payload: { invitation: value, presentation_exchange_id: proof.pres_ex_id, - invitation_url - } - } - }) - } - async sendConnectionlessProofRequest(builder: ProofRequestBuilder): Promise { - const wallet: any = (await this.axios.get(`/wallet/did/public`, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }).then(extractResponseData)) + invitation_url, + }, + }; + }); + } + async sendConnectionlessProofRequest( + builder: ProofRequestBuilder + ): Promise { + const wallet: any = await this.axios + .get(`/wallet/did/public`, { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }) + .then(extractResponseData); //console.dir(['wallet', wallet]) - const proof = await this.createPresentProofV1(builder) - const invitation = JSON.parse(JSON.stringify(proof.presentation_request_dict)) - invitation['comment']= null - invitation['~service']= { - "recipientKeys": [wallet.result.verkey], - "routingKeys": null, - "serviceEndpoint": this.config.serviceEndpoint - } - invitation['@type'] ='did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/present-proof/1.0/request-presentation' - return Promise.resolve(invitation).then(value => { - const baseUrl = 'bcwallet://launch' - const invitation_url = baseUrl+'?oob='+encodeURIComponent(Buffer.from(JSON.stringify(value, undefined, 2)).toString('base64')) + const proof = await this.createPresentProofV1(builder); + const invitation = JSON.parse( + JSON.stringify(proof.presentation_request_dict) + ); + invitation["comment"] = null; + invitation["~service"] = { + recipientKeys: [wallet.result.verkey], + routingKeys: null, + serviceEndpoint: this.config.serviceEndpoint, + }; + invitation["@type"] = + "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/present-proof/1.0/request-presentation"; + return Promise.resolve(invitation).then((value) => { + const baseUrl = "bcwallet://launch"; + const invitation_url = + baseUrl + + "?oob=" + + encodeURIComponent( + Buffer.from(JSON.stringify(value, undefined, 2)).toString("base64") + ); return { type: INVITATION_TYPE.CONN_1_0, payload: { invitation: value, presentation_exchange_id: proof.presentation_exchange_id, - invitation_url + invitation_url, + }, + }; + }); + } + async sendProofRequestV1( + connection_id: string, + proofRequestbuilder: ProofRequestBuilder + ): Promise { + const proofRequest = proofRequestbuilder.build(); + return await this.axios + .post( + `/present-proof/send-request`, + { + auto_remove: false, + auto_verify: true, + comment: "string", + trace: false, + connection_id: connection_id, + proof_request: proofRequest, + }, + { + params: {}, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, } - } - }) - } - async sendProofRequestV1(connection_id: string, proofRequestbuilder: ProofRequestBuilder): Promise { - const proofRequest = proofRequestbuilder.build() - return await this.axios.post(`/present-proof/send-request`,{ - "auto_remove": false, - "auto_verify": true, - "comment": "string", - "trace": false, - "connection_id": connection_id, - proof_request: proofRequest - }, { - params: {}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then(extractResponseData) - } + ) + .then(printResponse) + .then(extractResponseData); + } findCredentialOffer(_connectionId: string): Promise { throw new Error("Method not implemented."); } acceptCredentialOffer(_offer: CredentialOfferRef): Promise { throw new Error("Method not implemented."); } - receiveInvitation(_invitation: ResponseCreateInvitation): Promise { + receiveInvitation( + _invitation: ResponseCreateInvitation + ): Promise { throw new Error("Method not implemented."); } - async createSchemaCredDefinition(credDefBuilder: CredentialDefinitionBuilder): Promise { - const http = this.axios - const config = this.config - const schemas = await http.get(`/credential-definitions/created`, { - params:{schema_name: credDefBuilder.getSchema()?.getName(), schema_version: credDefBuilder.getSchema()?.getVersion()}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then(printResponse) - const credential_definitions:any[] = [] + async createSchemaCredDefinition( + credDefBuilder: CredentialDefinitionBuilder + ): Promise { + const http = this.axios; + const config = this.config; + const schemas = await http + .get(`/credential-definitions/created`, { + params: { + schema_name: credDefBuilder.getSchema()?.getName(), + schema_version: credDefBuilder.getSchema()?.getVersion(), + }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + .then(printResponse); + const credential_definitions: any[] = []; if (schemas.data.credential_definition_ids.length > 0) { - const credential_definition_ids:string[] = schemas.data.credential_definition_ids + const credential_definition_ids: string[] = + schemas.data.credential_definition_ids; for (const credential_definition_id of credential_definition_ids) { - const credential_definition = await http.get(`/credential-definitions/${credential_definition_id}`, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` + const credential_definition = await http.get( + `/credential-definitions/${credential_definition_id}`, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, } - }) - const credDef = credential_definition.data + ); + const credDef = credential_definition.data; if (credDef.credential_definition.tag === credDefBuilder.getTag()) { - if (!credDefBuilder.getSupportRevocation() && credDef.credential_definition.value.revocation === undefined){ - credential_definitions.push(credDef) - } else if (credDefBuilder.getSupportRevocation() && credDef.credential_definition.value.revocation !== undefined){ - credential_definitions.push(credDef) + if ( + !credDefBuilder.getSupportRevocation() && + credDef.credential_definition.value.revocation === undefined + ) { + credential_definitions.push(credDef); + } else if ( + credDefBuilder.getSupportRevocation() && + credDef.credential_definition.value.revocation !== undefined + ) { + credential_definitions.push(credDef); } } } } - if (credential_definitions.length === 0){ - await http.get(`/transactions`, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then(printResponse) - .then(extractResponseData) - .then(data => { - const results:any[] = data.results - const transactions = results.map((value) => { - let _txn = value.messages_attach[0].data.json - if (typeof _txn === 'string') { - _txn= JSON.parse(_txn) - } - return {state: value.state, created_at:value.created_at, updated_at:value.updated_at, txn_type:_txn?.operation?.type??_txn.result?.txn?.type} + if (credential_definitions.length === 0) { + await http + .get(`/transactions`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, }) - this.logger.info('transactions', transactions) - }) - this.logger.info('Creating Credential Definition ...') - const credDef = credDefBuilder.build() - this.logger.info('credDef', credDef) - return await this.axios.post(`/credential-definitions`,credDef, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then(printResponse) - .then(async (value)=>{ - this.logger.info('Waiting for writing transaction to ledger' ) - await this.waitForLedgerTransactionAcked(value.data.txn.transaction_id, 0) - return value - }) - .then(async (value)=>{ - this.logger.info('Created CredDef', value.data) - const credential_definition_id = value.data.sent.credential_definition_id - //config.current_credential_definition_id=credential_definition_id - this.logger.info(`Credential Definition created '${credential_definition_id}'`) - credDefBuilder.setId(credential_definition_id) - return credential_definition_id as string - }) + .then(printResponse) + .then(extractResponseData) + .then((data) => { + const results: any[] = data.results; + const transactions = results.map((value) => { + let _txn = value.messages_attach[0].data.json; + if (typeof _txn === "string") { + _txn = JSON.parse(_txn); + } + return { + state: value.state, + created_at: value.created_at, + updated_at: value.updated_at, + txn_type: _txn?.operation?.type ?? _txn.result?.txn?.type, + }; + }); + this.logger.info("transactions", transactions); + }); + this.logger.info("Creating Credential Definition ..."); + const credDef = credDefBuilder.build(); + this.logger.info("credDef", credDef); + return await this.axios + .post(`/credential-definitions`, credDef, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + .then(printResponse) + .then(async (value) => { + this.logger.info("Waiting for writing transaction to ledger"); + await this.waitForLedgerTransactionAcked( + value.data.txn.transaction_id, + 0 + ); + return value; + }) + .then(async (value) => { + this.logger.info("Created CredDef", value.data); + const credential_definition_id = + value.data.sent.credential_definition_id; + //config.current_credential_definition_id=credential_definition_id + this.logger.info( + `Credential Definition created '${credential_definition_id}'` + ); + credDefBuilder.setId(credential_definition_id); + return credential_definition_id as string; + }); } else { - const credDef = credential_definitions[0].credential_definition - const credential_definition_id = credDef.id - credDefBuilder.setId(credDef.id) - credDefBuilder.setTag(credDef.tag) - this.logger.info(`Credential Definition found '${credential_definition_id}'`) - return credential_definition_id as string + const credDef = credential_definitions[0].credential_definition; + const credential_definition_id = credDef.id; + credDefBuilder.setId(credDef.id); + credDefBuilder.setTag(credDef.tag); + this.logger.info( + `Credential Definition found '${credential_definition_id}'` + ); + return credential_definition_id as string; } } async startup(): Promise { - const config = this.config + const config = this.config; if (config.tenant_id && config.api_key) { - await this.axios.post(`/multitenancy/tenant/${config.tenant_id}/token`,{"api_key":config.api_key}) - .then((value)=>{ - config.auth_token = value.data.token - }) - }else { - await this.axios.post(`/multitenancy/wallet/${config.wallet_id}/token`,{"wallet_key":config.wallet_key}) - .then((value)=>{ - config.auth_token = value.data.token - }) + await this.axios + .post(`/multitenancy/tenant/${config.tenant_id}/token`, { + api_key: config.api_key, + }) + .then((value) => { + config.auth_token = value.data.token; + }); + } else { + await this.axios + .post(`/multitenancy/wallet/${config.wallet_id}/token`, { + wallet_key: config.wallet_key, + }) + .then((value) => { + config.auth_token = value.data.token; + }); } } - async createOOBInvitationToConnect(invitationType: T): Promise { - const config = this.config - const http = this.axios + async createOOBInvitationToConnect( + invitationType: T + ): Promise { + const config = this.config; + const http = this.axios; //const handshake_protocols: string[] = [] //handshake_protocols.push(handshakeProtocol as string) - const payload = { - "alias":`Faber\`s 😇 - ${new Date().getTime()}`, - "my_label":`Faber\`s 😇 - ${new Date().getTime()}`, - "handshake_protocols": [invitationType.substring(6)], - } + const payload = { + alias: `Faber\`s 😇 - ${new Date().getTime()}`, + my_label: `Faber\`s 😇 - ${new Date().getTime()}`, + handshake_protocols: [invitationType.substring(6)], + }; const params = { - "auto_accept": true, - "multi_use": false, - "create_unique_did": false, - } - if (invitationType === INVITATION_TYPE.OOB_DIDX_1_1){ + auto_accept: true, + multi_use: false, + create_unique_did: false, + }; + if (invitationType === INVITATION_TYPE.OOB_DIDX_1_1) { Object.assign(payload, { - "protocol_version":"1.1", - "use_did_method": "did:peer:4", - }) + protocol_version: "1.1", + use_did_method: "did:peer:4", + }); } - return http.post(`/out-of-band/create-invitation`,payload, { - params: params, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then(printResponse) - .then((value)=>{ - this.logger.info('createInvitationToConnect', value.data) - this.logger.info(`invitation_url=${value.data.invitation_url}`) - switch(invitationType) { - case INVITATION_TYPE.OOB_CONN_1_0: - return { - type: INVITATION_TYPE.OOB_CONN_1_0, - payload: { - invitation_url: value.data.invitation_url, - invi_msg_id: value.data.invi_msg_id, - invitation: value.data.invitation, - } - } + return http + .post(`/out-of-band/create-invitation`, payload, { + params: params, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + .then(printResponse) + .then((value) => { + this.logger.info("createInvitationToConnect", value.data); + this.logger.info(`invitation_url=${value.data.invitation_url}`); + switch (invitationType) { + case INVITATION_TYPE.OOB_CONN_1_0: + return { + type: INVITATION_TYPE.OOB_CONN_1_0, + payload: { + invitation_url: value.data.invitation_url, + invi_msg_id: value.data.invi_msg_id, + invitation: value.data.invitation, + }, + }; case INVITATION_TYPE.OOB_DIDX_1_1: return { type: INVITATION_TYPE.OOB_DIDX_1_1, @@ -594,201 +759,256 @@ export class AgentTraction implements AriesAgent { invitation_url: value.data.invitation_url, invi_msg_id: value.data.invi_msg_id, invitation: value.data.invitation, - } - } - default: - throw new Error("Unsupported protocol"); - } - }) + }, + }; + default: + throw new Error("Unsupported protocol"); + } + }); } - async createInvitationToConnect(invitationType: T): Promise> { - switch(invitationType) { + async createInvitationToConnect( + invitationType: T + ): Promise> { + switch (invitationType) { case INVITATION_TYPE.CONN_1_0: - return this.__createInvitationToConnectConnV1() as Promise> + return this.__createInvitationToConnectConnV1() as Promise< + CreateInvitationResponse + >; case INVITATION_TYPE.OOB_CONN_1_0: - return this.createOOBInvitationToConnect(invitationType) as Promise> + return this.createOOBInvitationToConnect(invitationType) as Promise< + CreateInvitationResponse + >; case INVITATION_TYPE.OOB_DIDX_1_1: - return this.createOOBInvitationToConnect(invitationType) as Promise> + return this.createOOBInvitationToConnect(invitationType) as Promise< + CreateInvitationResponse + >; default: throw new Error("Invalid invitation type"); } } - async __createInvitationToConnectConnV1(): Promise> { - const config = this.config - const http = this.axios - return http.post(`/connections/create-invitation`,{ - "my_label": `Faber\`s 😇 - ${new Date().getTime()}`, - "image_url": "https://bc-wallet-demo-agent-admin.apps.silver.devops.gov.bc.ca/public/student/connection/best-bc-logo.png" - }, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then((value)=>{ - const response:ResponseCreateInvitationV1 = { - type: INVITATION_TYPE.CONN_1_0, - payload: { - invitation_url: value.data.invitation_url, - connection_id: value.data.connection_id, - invitation: value.data.invitation + async __createInvitationToConnectConnV1(): Promise< + CreateInvitationResponse + > { + const config = this.config; + const http = this.axios; + return http + .post( + `/connections/create-invitation`, + { + my_label: `Faber\`s 😇 - ${new Date().getTime()}`, + image_url: + "https://bc-wallet-demo-agent-admin.apps.silver.devops.gov.bc.ca/public/student/connection/best-bc-logo.png", + }, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, } - } - this.logger.info('createInvitationToConnect', value.data) - this.logger.info(`invitation_url=${value.data.invitation_url}`) - return response //{invitation_url: value.data.invitation_url, connection_id: value.data.connection_id} - }) - } - async createSchema(schemaBuilder: SchemaBuilder): Promise { - const config = this.config - const schema = schemaBuilder.build() + ) + .then((value) => { + const response: ResponseCreateInvitationV1 = { + type: INVITATION_TYPE.CONN_1_0, + payload: { + invitation_url: value.data.invitation_url, + connection_id: value.data.connection_id, + invitation: value.data.invitation, + }, + }; + this.logger.info("createInvitationToConnect", value.data); + this.logger.info(`invitation_url=${value.data.invitation_url}`); + return response; //{invitation_url: value.data.invitation_url, connection_id: value.data.connection_id} + }); + } + async createSchema( + schemaBuilder: SchemaBuilder + ): Promise { + const config = this.config; + const schema = schemaBuilder.build(); const schemas = await this.axios.get(`/schemas/created`, { - params:{schema_name: schema.schema_name, schema_version: schema.schema_version}, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - if (schemas.data.schema_ids.length === 0){ - this.logger.info('Creating Schema ...') - this.logger.info('schema', schema) - await this.axios.post(`/schemas`, schema, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then(async (value)=>{ - this.logger.info('Waiting for writing transaction to ledger' ) - await this.waitForLedgerTransactionAcked(value.data.txn.transaction_id, 0) - return value - }) - .then((value)=>{ - schemaBuilder.setSchemaId(value.data.sent.schema_id) - this.logger.info(`Schema created '${value.data.sent.schema_id}'`) - return value.data.sent.schema_id - }) + params: { + schema_name: schema.schema_name, + schema_version: schema.schema_version, + }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }); + if (schemas.data.schema_ids.length === 0) { + this.logger.info("Creating Schema ..."); + this.logger.info("schema", schema); + await this.axios + .post(`/schemas`, schema, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + .then(async (value) => { + this.logger.info("Waiting for writing transaction to ledger"); + await this.waitForLedgerTransactionAcked( + value.data.txn.transaction_id, + 0 + ); + return value; + }) + .then((value) => { + schemaBuilder.setSchemaId(value.data.sent.schema_id); + this.logger.info(`Schema created '${value.data.sent.schema_id}'`); + return value.data.sent.schema_id; + }); } else { - schemaBuilder.setSchemaId(schemas.data.schema_ids[0]) - this.logger.info(`Schema found '${schemas.data.schema_ids[0]}'`) - return schemas.data.schema_ids[0] + schemaBuilder.setSchemaId(schemas.data.schema_ids[0]); + this.logger.info(`Schema found '${schemas.data.schema_ids[0]}'`); + return schemas.data.schema_ids[0]; } } - async sendCredential(cred: IssueCredentialPreviewV1, credential_definition_id: string, connection_id: string): Promise { - const config = this.config - const http = this.axios - this.logger.info(`Preparing Credential Request`) + async sendCredential( + cred: IssueCredentialPreviewV1, + credential_definition_id: string, + connection_id: string + ): Promise { + const config = this.config; + const http = this.axios; + this.logger.info(`Preparing Credential Request`); const data = { - "auto_issue": true, - "auto_remove": false, - "connection_id": connection_id, - "cred_def_id": credential_definition_id, - "credential_preview": await cred.build(), - "trace": true, - } - this.logger.info('Credential Data', data) - return await http.post(`/issue-credential/send-offer`,data, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then((value)=>{ - const credential_exchange_id = value.data.credential_exchange_id - cred.setCredentialExchangeId(credential_exchange_id) - this.logger.info(`Credential offer sent! ${credential_exchange_id}`) - return credential_exchange_id - }) - } - async waitFoConnectionReady (invitation: CreateInvitationResponse): Promise<{ connection_id: any; }> { - if (invitation.type === INVITATION_TYPE.OOB_DIDX_1_1) { - //todo: something - } - return {connection_id:''} - } - async waitForOOBConnectionReady (invi_msg_id:string): Promise { - const {invitation_key} = await this._waitForOOBConnectionRecord(invi_msg_id, 0) - const conn = await this._waitForConnectionReadyByInvitationKey(invitation_key as string, 0) - return {connection_id: conn.connection_id} - } - async waitForConnectionReady (connection_id:string) { - this.logger.info(`Waiting for connection ${connection_id} to get stablished`) - await this._waitForConnectionReady(connection_id, 0) - } - async waitForOfferAccepted (credential_exchange_id: string) { - const config = this.config - const http = this.axios - await http.get(`/issue-credential/records/${credential_exchange_id}`, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.auth_token}` - } - }) - .then((value)=>{ - this.logger.info(`Credential Exchange state: ${value.data.state}`) - if (value.data.state !== 'credential_acked') { - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this.waitForOfferAccepted(credential_exchange_id)) - }, 2000); - }) - } - }) + auto_issue: true, + auto_remove: false, + connection_id: connection_id, + cred_def_id: credential_definition_id, + credential_preview: await cred.build(), + trace: true, + }; + this.logger.info("Credential Data", data); + return await http + .post(`/issue-credential/send-offer`, data, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + .then((value) => { + const credential_exchange_id = value.data.credential_exchange_id; + cred.setCredentialExchangeId(credential_exchange_id); + this.logger.info(`Credential offer sent! ${credential_exchange_id}`); + return credential_exchange_id; + }); + } + async waitForOOBConnectionReady(invi_msg_id: string): Promise { + const { connection_id } = await this._waitForOOBConnectionRecord( + invi_msg_id, + 0 + ); + await this.waitForConnectionReady(connection_id); + return { connection_id }; + } + async waitForConnectionReady(connection_id: string) { + this.logger.info( + `Waiting for connection ${connection_id} to get stablished` + ); + await this._waitForConnectionReady(connection_id, 0); + } + async waitForOfferAccepted(credential_exchange_id: string) { + const config = this.config; + const http = this.axios; + await http + .get(`/issue-credential/records/${credential_exchange_id}`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.auth_token}`, + }, + }) + .then((value) => { + this.logger.info(`Credential Exchange state: ${value.data.state}`); + if (value.data.state !== "credential_acked") { + return new Promise((resolve) => { + setTimeout(() => { + resolve(this.waitForOfferAccepted(credential_exchange_id)); + }, 2000); + }); + } + }); } async sendBasicMessage(connection_id: string, content: string): Promise { - - return await this.axios.post(`/connections/${connection_id}/send-message`,{ - content - }, { - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then(()=>{ - return this.axios.get(`/basicmessages`,{ - params: { - connection_id, - state: 'sent' + return await this.axios + .post( + `/connections/${connection_id}/send-message`, + { + content, }, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, } + ) + .then(printResponse) + .then(() => { + return this.axios.get(`/basicmessages`, { + params: { + connection_id, + state: "sent", + }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, + }); }) - }) - .then(extractResponseData) - .then(data => { - const results: any[] = data.results - return results.find(item => item.content === content) - }) - } - async waitForBasicMessage(connection_id: string, receivedAfter: number, content: string[]): Promise{ - return this._waitForBasicMessage(connection_id, receivedAfter, content, 0) - } - private async _waitForBasicMessage(connection_id: string, receivedAfter: number, content: string[], counter: number): Promise{ - return await this.axios.get(`/basicmessages`,{ - params: { - connection_id, - state: 'received' - }, - headers:{ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.auth_token}` - } - }) - .then(printResponse) - .then(extractResponseData) - .then(data => { - const results: any[] = data.results - const item = results.find(item => Date.parse(item.created_at) >= receivedAfter && content.includes(item.content.toLowerCase())) - if (item) return item - return new Promise ((resolve) => { - setTimeout(() => { - resolve(this._waitForBasicMessage(connection_id, receivedAfter, content, counter +1)) - }, 2000); + .then(extractResponseData) + .then((data) => { + const results: any[] = data.results; + return results.find((item) => item.content === content); + }); + } + async waitForBasicMessage( + connection_id: string, + receivedAfter: number, + content: string[] + ): Promise { + return this._waitForBasicMessage(connection_id, receivedAfter, content, 0); + } + private async _waitForBasicMessage( + connection_id: string, + receivedAfter: number, + content: string[], + counter: number + ): Promise { + return await this.axios + .get(`/basicmessages`, { + params: { + connection_id, + state: "received", + }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.config.auth_token}`, + }, }) - }) + .then(printResponse) + .then(extractResponseData) + .then((data) => { + const results: any[] = data.results; + const item = results.find( + (item) => + Date.parse(item.created_at) >= receivedAfter && + content.includes(item.content.toLowerCase()) + ); + if (item) return item; + return new Promise((resolve) => { + setTimeout(() => { + resolve( + this._waitForBasicMessage( + connection_id, + receivedAfter, + content, + counter + 1 + ) + ); + }, 2000); + }); + }); } -} \ No newline at end of file +} diff --git a/src/basic.test.ts b/src/basic.test.ts index bfbecdb..972ee63 100644 --- a/src/basic.test.ts +++ b/src/basic.test.ts @@ -3,72 +3,97 @@ import { AgentTraction } from "./AgentTraction"; import { AgentCredo } from "./AgentCredo"; import { LogLevel } from "@credo-ts/core"; import { PersonCredential1, PersonSchemaV1_1 } from "./mocks"; -import { CredentialDefinitionBuilder, issueCredential, PinoLogger, ProofRequestBuilder, RequestAttributeBuilder, seconds_since_epoch, verifyCredentialA1, verifyCredentialA2, verifyCredentialB1, verifyCredentialB2, withRedirectUrl } from "./lib"; +import { + CredentialDefinitionBuilder, + issueCredential, + PinoLogger, + ProofRequestBuilder, + RequestAttributeBuilder, + seconds_since_epoch, + verifyCredentialA1, + verifyCredentialA2, + verifyCredentialB1, + verifyCredentialB2, + withRedirectUrl, + writeQRCode, +} from "./lib"; import pino from "pino"; +import QRCode from "qrcode"; +import fs from "node:fs"; +import path from "node:path"; -const stepTimeout = 120_000 -const shortTimeout = 4_000_000 -import { dir as console_dir } from "console" -import { setGlobalDispatcher, Agent} from 'undici'; +const stepTimeout = 120_000; +const shortTimeout = 40_000; +import { dir as console_dir } from "console"; +import { setGlobalDispatcher, Agent } from "undici"; import { AriesAgent, INVITATION_TYPE } from "./Agent"; import { AgentManual } from "./AgentManual"; import { cache_requests } from "./axios-traction-serializer"; -setGlobalDispatcher(new Agent({connect: { timeout: 20_000 }})); +setGlobalDispatcher(new Agent({ connect: { timeout: 20_000 } })); export const loggerTransport = pino.transport({ targets: [ { - level: 'trace', - target: 'pino/file', + level: "trace", + target: "pino/file", options: { destination: `./logs/run-${Date.now()}.log.ndjson`, autoEnd: true, }, - } + }, ], -}) +}); describe("Mandatory", () => { - const _logger = pino({ level: 'trace', timestamp: pino.stdTimeFunctions.isoTime, }, loggerTransport); - const logger = new PinoLogger(_logger, LogLevel.trace) + const _logger = pino( + { level: "trace", timestamp: pino.stdTimeFunctions.isoTime }, + loggerTransport + ); + const logger = new PinoLogger(_logger, LogLevel.trace); // eslint-disable-next-line @typescript-eslint/no-var-requires - const config = require("../local.env.json") + const config = require("../local.env.json"); // eslint-disable-next-line @typescript-eslint/no-var-requires const ledgers = require("../ledgers.json"); const agentSchemaOwner = new AgentTraction(config.schema_owner, logger); const agentIssuer = new AgentTraction(config.issuer, logger); const agentVerifier = new AgentTraction(config.verifier, logger); //const agentB: AriesAgent = new AgentManual(config, new ConsoleLogger(LogLevel.trace)) - const agentB: AriesAgent = process.env.HOLDER_TYPE === 'manual'? new AgentManual(config.holder, logger): new AgentCredo(config.holder,ledgers, logger) + const agentB: AriesAgent = + process.env.HOLDER_TYPE === "manual" + ? new AgentManual(config.holder, logger) + : new AgentCredo(config.holder, ledgers, logger); //new PinoLogger(logger, LogLevel.trace)); const schema = new PersonSchemaV1_1(); const credDef = new CredentialDefinitionBuilder() .setSchema(schema) .setSupportRevocation(true) - .setTag('Revocable Credential') - const requests: unknown[] = [] + .setTag("Revocable Credential"); + const requests: unknown[] = []; beforeAll(async () => { - logger.info('1 - beforeAll') - await agentSchemaOwner.startup() - await agentIssuer.startup() - await agentB.startup() - await agentVerifier.startup() + logger.info("1 - beforeAll"); + await agentSchemaOwner.startup(); + await agentIssuer.startup(); + await agentB.startup(); + await agentVerifier.startup(); //await Promise.all([agentA.startup(), agentB.startup()]); - agentSchemaOwner.axios.interceptors.request.use(cache_requests(requests)) - agentIssuer.axios.interceptors.request.use(cache_requests(requests)) - agentVerifier.axios.interceptors.request.use(cache_requests(requests)) - agentIssuer.axios.interceptors.response.use( async (response) => { - return response - }, (error) => { - console_dir(error.response) - return Promise.reject(error); - }) + agentSchemaOwner.axios.interceptors.request.use(cache_requests(requests)); + agentIssuer.axios.interceptors.request.use(cache_requests(requests)); + agentVerifier.axios.interceptors.request.use(cache_requests(requests)); + agentIssuer.axios.interceptors.response.use( + async (response) => { + return response; + }, + (error) => { + console_dir(error.response); + return Promise.reject(error); + } + ); await agentSchemaOwner.createSchema(schema); await agentIssuer.createSchemaCredDefinition(credDef); }, stepTimeout); afterAll(async () => { - logger.info('1 - afterAll') + logger.info("1 - afterAll"); await agentB.shutdown(); _logger.flush(); //loggerTransport.end(); @@ -94,7 +119,7 @@ describe("Mandatory", () => { logger.info('Message Received:', msgRcvd) //expect(requests).toMatchSnapshot(); }, shortTimeout); - test.only("OOB/connected/messaging", async () => { + test("OOB/connected/messaging", async () => { const issuer = agentIssuer const holder = agentB logger.info(`Executing ${expect.getState().currentTestName}`) @@ -114,217 +139,427 @@ describe("Mandatory", () => { test("setup", async () => { logger.info(`Executing ${expect.getState().currentTestName}`) - try{ - await agentIssuer.clearAllRecords() - - const personCred = new PersonCredential1(credDef) + try { + await agentIssuer.clearAllRecords(); - await issueCredential(agentIssuer, agentB, personCred) - }catch (error){ - console.dir(error) - throw error - } - //expect(requests).toMatchSnapshot(); - }, stepTimeout) - test("OOB/connected/issue", async () => { - logger.info(`Executing ${expect.getState().currentTestName}`) - const issuer = agentIssuer - const holder = agentB - const cred = new PersonCredential1(credDef) - const remoteInvitation = await issuer.createOOBInvitationToConnect(INVITATION_TYPE.OOB_CONN_1_0) - logger.info('remoteInvitation', remoteInvitation) - logger.info(`waiting for holder to accept connection`) - const agentBConnectionRef1 = await holder.receiveInvitation(remoteInvitation) - logger.info(`waiting for issuer to accept connection`) - const {connection_id} = await issuer.waitForOOBConnectionReady(remoteInvitation.payload.invi_msg_id) - logger.info('agentBConnectionRef1', agentBConnectionRef1) - await issuer.sendBasicMessage(connection_id, 'Hello') - const credential_exchange_id = await issuer.sendCredential(cred, cred.getCredDef()?.getId() as string, connection_id) - const offer = await holder.findCredentialOffer(agentBConnectionRef1.connectionRecord?.connection_id as string) - await holder.acceptCredentialOffer(offer) - await issuer.waitForOfferAccepted(credential_exchange_id as string) - }, shortTimeout); - test("connected/present-proof-1.0/encoded-payload", async () => { - //const issuer = agentA - const verifier = agentVerifier - const holder = agentB - logger.info(`Executing ${expect.getState().currentTestName}`) - const remoteInvitation = await verifier.createInvitationToConnect(INVITATION_TYPE.CONN_1_0) - logger.info(`waiting for holder to accept connection`) - const agentBConnectionRef1 = await holder.receiveInvitation(remoteInvitation) - logger.info(`waiting for issuer to accept connection`) - await verifier.waitForConnectionReady(remoteInvitation.payload.connection_id as string) - logger.info(`${remoteInvitation.payload.connection_id} connected to ${agentBConnectionRef1.connectionRecord?.connection_id}`) - logger.info('agentBConnectionRef1', agentBConnectionRef1) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) - const proofRequestSent = await verifier.sendProofRequestV1(remoteInvitation.payload.connection_id as string, proofRequest) - holder.acceptProof({connection_id:agentBConnectionRef1.connectionRecord?.connection_id as string}) - await verifier.waitForPresentation(proofRequestSent.presentation_exchange_id) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); - test("connectionless/present-proof-1.0/encoded-payload", async () => { - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - .addRestriction({"cred_def_id": credDef.getId()}) - //.addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) - await verifyCredentialA1(agentVerifier, agentB, proofRequest) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); - test("connectionless/present-proof-1.0/url-redirect", async () => { - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) - await verifyCredentialA2(agentVerifier, agentB, proofRequest) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); - test("connectionless/present-proof-2.0/encoded-payload", async () => { - const verifier = agentVerifier - const holder = agentB - const { logger } = verifier - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) - const remoteInvitation2 = await verifier.sendConnectionlessProofRequestV2(proofRequest) - const agentBConnectionRef2 = await holder.receiveInvitation(remoteInvitation2) - //console.dir(['agentBConnectionRef', agentBConnectionRef2]) - if (agentBConnectionRef2.invitationRequestsThreadIds){ - for (const proofId of agentBConnectionRef2.invitationRequestsThreadIds) { - await holder.acceptProof({id: proofId}) + const personCred = new PersonCredential1(credDef); + + await issueCredential(agentIssuer, agentB, personCred); + } catch (error) { + console.dir(error); + throw error; } - } - logger.info('remoteInvitation2', remoteInvitation2) - await verifier.waitForPresentationV2(remoteInvitation2.payload.presentation_exchange_id as string) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); - test("connectionless/present-proof-2.0/url-redirect", async () => { - const verifier = agentVerifier - const holder = agentB - const { logger } = verifier - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) - const remoteInvitation2 = await withRedirectUrl(await verifier.sendConnectionlessProofRequestV2(proofRequest)) - const agentBConnectionRef2 = await holder.receiveInvitation(remoteInvitation2) - //console.dir(['agentBConnectionRef', agentBConnectionRef2]) - if (agentBConnectionRef2.invitationRequestsThreadIds){ - for (const proofId of agentBConnectionRef2.invitationRequestsThreadIds) { - await holder.acceptProof({id: proofId}) + //expect(requests).toMatchSnapshot(); + }, + stepTimeout + ); + test( + "OOB/connected/issue", + async () => { + logger.info(`Executing ${expect.getState().currentTestName}`); + const issuer = agentIssuer; + const holder = agentB; + const cred = new PersonCredential1(credDef); + const remoteInvitation = await issuer.createOOBInvitationToConnect( + INVITATION_TYPE.OOB_CONN_1_0 + ); + logger.info("remoteInvitation", remoteInvitation); + logger.info(`waiting for holder to accept connection`); + const agentBConnectionRef1 = await holder.receiveInvitation( + remoteInvitation + ); + logger.info(`waiting for issuer to accept connection`); + const { connection_id } = await issuer.waitForOOBConnectionReady( + remoteInvitation.payload.invi_msg_id + ); + logger.info("agentBConnectionRef1", agentBConnectionRef1); + await issuer.sendBasicMessage(connection_id, "Hello"); + const credential_exchange_id = await issuer.sendCredential( + cred, + cred.getCredDef()?.getId() as string, + connection_id + ); + const offer = await holder.findCredentialOffer( + agentBConnectionRef1.connectionRecord?.connection_id as string + ); + await holder.acceptCredentialOffer(offer); + await issuer.waitForOfferAccepted(credential_exchange_id as string); + }, + shortTimeout + ); + test( + "connected/present-proof-1.0/encoded-payload", + async () => { + //const issuer = agentA + const verifier = agentVerifier; + const holder = agentB; + logger.info(`Executing ${expect.getState().currentTestName}`); + const remoteInvitation = await verifier.createInvitationToConnect( + INVITATION_TYPE.CONN_1_0 + ); + logger.info(`waiting for holder to accept connection`); + const agentBConnectionRef1 = await holder.receiveInvitation( + remoteInvitation + ); + logger.info(`waiting for issuer to accept connection`); + await verifier.waitForConnectionReady( + remoteInvitation.payload.connection_id as string + ); + logger.info( + `${remoteInvitation.payload.connection_id} connected to ${agentBConnectionRef1.connectionRecord?.connection_id}` + ); + logger.info("agentBConnectionRef1", agentBConnectionRef1); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + const proofRequestSent = await verifier.sendProofRequestV1( + remoteInvitation.payload.connection_id as string, + proofRequest + ); + holder.acceptProof({ + connection_id: agentBConnectionRef1.connectionRecord + ?.connection_id as string, + }); + await verifier.waitForPresentation( + proofRequestSent.presentation_exchange_id + ); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + test( + "connectionless/present-proof-1.0/encoded-payload", + async () => { + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + .addRestriction({ cred_def_id: credDef.getId() }) + //.addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + await verifyCredentialA1(agentVerifier, agentB, proofRequest); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + test( + "connectionless/present-proof-1.0/url-redirect", + async () => { + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + await verifyCredentialA2(agentVerifier, agentB, proofRequest); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + test( + "connectionless/present-proof-2.0/encoded-payload", + async () => { + const verifier = agentVerifier; + const holder = agentB; + const { logger } = verifier; + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + const remoteInvitation2 = await verifier.sendConnectionlessProofRequestV2( + proofRequest + ); + const agentBConnectionRef2 = await holder.receiveInvitation( + remoteInvitation2 + ); + //console.dir(['agentBConnectionRef', agentBConnectionRef2]) + if (agentBConnectionRef2.invitationRequestsThreadIds) { + for (const proofId of agentBConnectionRef2.invitationRequestsThreadIds) { + await holder.acceptProof({ id: proofId }); + } } - } - await verifier.waitForPresentationV2(remoteInvitation2.payload.presentation_exchange_id as string) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); - test("OOB/connectionless/present-proof-1.0/encoded-payload", async () => { - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) - await verifyCredentialB1(agentVerifier, agentB, proofRequest) - }, shortTimeout); - test("OOB/connectionless/present-proof-1.0/url-redirect", async () => { - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) - await verifyCredentialB2(agentVerifier, agentB, proofRequest) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); - test("OOB/connectionless/present-proof-2.0/encoded-payload", async () => { - const verifier = agentVerifier - const holder = agentB - const { logger } = verifier - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) + logger.info("remoteInvitation2", remoteInvitation2); + await verifier.waitForPresentationV2( + remoteInvitation2.payload.presentation_exchange_id as string + ); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + test( + "connectionless/present-proof-2.0/url-redirect", + async () => { + const verifier = agentVerifier; + const holder = agentB; + const { logger } = verifier; + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + const remoteInvitation2 = await withRedirectUrl( + await verifier.sendConnectionlessProofRequestV2(proofRequest) + ); + const agentBConnectionRef2 = await holder.receiveInvitation( + remoteInvitation2 + ); + //console.dir(['agentBConnectionRef', agentBConnectionRef2]) + if (agentBConnectionRef2.invitationRequestsThreadIds) { + for (const proofId of agentBConnectionRef2.invitationRequestsThreadIds) { + await holder.acceptProof({ id: proofId }); + } + } + await verifier.waitForPresentationV2( + remoteInvitation2.payload.presentation_exchange_id as string + ); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + test( + "OOB/connectionless/present-proof-1.0/encoded-payload", + async () => { + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + await verifyCredentialB1(agentVerifier, agentB, proofRequest); + }, + shortTimeout + ); + test( + "OOB/connectionless/present-proof-1.0/url-redirect", + async () => { + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + await verifyCredentialB2(agentVerifier, agentB, proofRequest); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + test( + "OOB/connectionless/present-proof-2.0/encoded-payload", + async () => { + const verifier = agentVerifier; + const holder = agentB; + const { logger } = verifier; + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); - const remoteInvitation3 = await verifier.sendOOBConnectionlessProofRequestV2(proofRequest) - logger.info('remoteInvitation3', remoteInvitation3) - logger.info(`Holder is receiving invitation for ${remoteInvitation3.payload.presentation_exchange_id}`) - const agentBConnectionRef3 = await holder.receiveInvitation(remoteInvitation3) - logger.info('Holder is accepting proofs') - //await waitFor(10000) - if (agentBConnectionRef3.invitationRequestsThreadIds){ - for (const proofId of agentBConnectionRef3.invitationRequestsThreadIds) { - await holder.acceptProof({id: proofId}) + const remoteInvitation3 = + await verifier.sendOOBConnectionlessProofRequestV2(proofRequest); + logger.info("remoteInvitation3", remoteInvitation3); + logger.info( + `Holder is receiving invitation for ${remoteInvitation3.payload.presentation_exchange_id}` + ); + const agentBConnectionRef3 = await holder.receiveInvitation( + remoteInvitation3 + ); + logger.info("Holder is accepting proofs"); + //await waitFor(10000) + if (agentBConnectionRef3.invitationRequestsThreadIds) { + for (const proofId of agentBConnectionRef3.invitationRequestsThreadIds) { + await holder.acceptProof({ id: proofId }); + } } - } - logger.info(`Verifier is waiting for proofs: ${remoteInvitation3.payload.presentation_exchange_id}`) - await verifier.waitForPresentationV2(remoteInvitation3.payload.presentation_exchange_id as string) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); - test("OOB/connectionless/present-proof-2.0/url-redirect", async () => { - const verifier = agentVerifier - const holder = agentB - const { logger } = verifier - logger.info(`Executing ${expect.getState().currentTestName}`) - const proofRequest = new ProofRequestBuilder() - .addRequestedAttribute("studentInfo", - new RequestAttributeBuilder() - .setNames(["given_names", "family_name"]) - //.addRestriction({"cred_def_id": credDef.getId()}) - .addRestriction({"schema_name": schema.getName(),"schema_version": schema.getVersion(),"issuer_did": credDef.getId()?.split(':')[0]}) - .setNonRevoked(seconds_since_epoch(new Date())) - ) + logger.info( + `Verifier is waiting for proofs: ${remoteInvitation3.payload.presentation_exchange_id}` + ); + await verifier.waitForPresentationV2( + remoteInvitation3.payload.presentation_exchange_id as string + ); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + test( + "OOB/connectionless/present-proof-2.0/url-redirect", + async () => { + const verifier = agentVerifier; + const holder = agentB; + const { logger } = verifier; + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: credDef.getId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); - const remoteInvitation3 = await withRedirectUrl(await verifier.sendOOBConnectionlessProofRequestV2(proofRequest)) - logger.info('remoteInvitation3', remoteInvitation3) - logger.info(`Holder is receiving invitation for ${remoteInvitation3.payload.presentation_exchange_id}`) - const agentBConnectionRef3 = await holder.receiveInvitation(remoteInvitation3) - logger.info('Holder is accepting proofs') - //await waitFor(10000) - if (agentBConnectionRef3.invitationRequestsThreadIds){ - for (const proofId of agentBConnectionRef3.invitationRequestsThreadIds) { - await holder.acceptProof({id: proofId}) + const remoteInvitation3 = await withRedirectUrl( + await verifier.sendOOBConnectionlessProofRequestV2(proofRequest) + ); + logger.info("remoteInvitation3", remoteInvitation3); + logger.info( + `Holder is receiving invitation for ${remoteInvitation3.payload.presentation_exchange_id}` + ); + const agentBConnectionRef3 = await holder.receiveInvitation( + remoteInvitation3 + ); + logger.info("Holder is accepting proofs"); + //await waitFor(10000) + if (agentBConnectionRef3.invitationRequestsThreadIds) { + for (const proofId of agentBConnectionRef3.invitationRequestsThreadIds) { + await holder.acceptProof({ id: proofId }); + } } - } - logger.info(`Verifier is waiting for proofs: ${remoteInvitation3.payload.presentation_exchange_id}`) - await verifier.waitForPresentationV2(remoteInvitation3.payload.presentation_exchange_id as string) - //expect(requests).toMatchSnapshot(); - }, shortTimeout); + logger.info( + `Verifier is waiting for proofs: ${remoteInvitation3.payload.presentation_exchange_id}` + ); + await verifier.waitForPresentationV2( + remoteInvitation3.payload.presentation_exchange_id as string + ); + //expect(requests).toMatchSnapshot(); + }, + shortTimeout + ); + + describe("Proof Request", () => { + test("create credential person offer (manual)", async () => { + const issuer = agentIssuer; + // const holder = new AgentManual(config.holder, logger) + const cred = new PersonCredential1(credDef) + console.log(cred.getCredDef()?.getSchemaId()) + const remoteInvitation = await issuer.createOOBInvitationToConnect(INVITATION_TYPE.OOB_CONN_1_0) + logger.info('remoteInvitation', remoteInvitation) + logger.info(`waiting for holder to accept connection`) + + const relativePath = `./tmp/proof/__issue_credential_request.png`; + const QRCodePath = path.resolve(process.cwd() as string, relativePath); + fs.mkdirSync(path.dirname(QRCodePath), { recursive: true }); + await QRCode.toFile(QRCodePath, remoteInvitation.payload.invitation_url, { margin: 10 }); + + + logger.info(`waiting for issuer to accept connection`) + const { connection_id } = await issuer.waitForOOBConnectionReady(remoteInvitation.payload.invi_msg_id) + await issuer.sendBasicMessage(connection_id, 'You are connected to the test bench') + const credential_exchange_id = await issuer.sendCredential(cred, cred.getCredDef()?.getId() as string, connection_id) + + await issuer.waitForOfferAccepted(credential_exchange_id as string) + + + }, stepTimeout) + test( + "create valid proof request qr code (manual)", + async () => { + const verifier = agentVerifier; + const { logger } = verifier; + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "family_name"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: schema.getSchemaId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + + const remoteInvitation = await verifier.sendOOBConnectionlessProofRequestV2(proofRequest); + const relativePath = `./tmp/proof/__valid_proof_request.png`; + writeQRCode(relativePath, remoteInvitation.payload.invitation_url) + }, + stepTimeout + ); + test( + "create invalid proof request qr code (manual)", + async () => { + const verifier = agentVerifier; + const { logger } = verifier; + logger.info(`Executing ${expect.getState().currentTestName}`); + const proofRequest = new ProofRequestBuilder().addRequestedAttribute( + "studentInfo", + new RequestAttributeBuilder() + .setNames(["given_names", "no_good"]) + //.addRestriction({"cred_def_id": credDef.getId()}) + .addRestriction({ + schema_name: schema.getName(), + schema_version: schema.getVersion(), + issuer_did: schema.getSchemaId()?.split(":")[0], + }) + .setNonRevoked(seconds_since_epoch(new Date())) + ); + + const remoteInvitation = await verifier.sendOOBConnectionlessProofRequestV2(proofRequest); + const relativePath = `./tmp/proof/__invalid_proof_request.png`; + writeQRCode(relativePath, remoteInvitation.payload.invitation_url) + }, + stepTimeout + ); + }); }); diff --git a/src/credo.test.ts b/src/credo.test.ts index ad8a4de..0bdc3d7 100644 --- a/src/credo.test.ts +++ b/src/credo.test.ts @@ -1,8 +1,9 @@ import { describe, expect, test } from "@jest/globals"; -import { LogLevel, WsOutboundTransport } from "@credo-ts/core"; +import { createAgent } from "./AgentCredo"; +import { LogLevel } from "@credo-ts/core"; import { AgentTraction } from "./AgentTraction"; import { AgentCredo } from "./AgentCredo"; -import {PinoLogger } from "./lib"; +import { PinoLogger } from "./lib"; import pino from "pino"; import { INVITATION_TYPE } from "./Agent"; @@ -37,20 +38,20 @@ describe("credo", () => { beforeAll(async () => { console.log('beforeAll') - holderAgent = new AgentCredo(config.holderX, ledgers, logger); + holderAgent = new AgentCredo(config.holderX, ledgers, logger); issuerAgent = new AgentTraction(config.issuer, logger); await holderAgent.startup() await issuerAgent.startup() }, 50000); - + afterAll(async () => { console.log('afterAll') await holderAgent.shutdown() await issuerAgent.shutdown() }, 20000); - + test("something", async () => { expect(1 + 2).toBe(3); }, 20000); @@ -59,12 +60,12 @@ describe("credo", () => { const messageCount = 2 const remoteInvitation = await issuerAgent.createInvitationToConnect(INVITATION_TYPE.CONN_1_0) const issuerAgentConnectionRef = await holderAgent.receiveInvitation(remoteInvitation) - + logger.info(`waiting for issuer to issuerAgent connection`) await issuerAgent.waitForConnectionReady(remoteInvitation.payload.connection_id as string) logger.info(`${remoteInvitation.payload.connection_id} connected to ${issuerAgentConnectionRef.connectionRecord?.connection_id}`) - + // await holderAgent.shutdown() const messageReceivedPromise = new Promise((resolve) => { @@ -79,7 +80,7 @@ describe("credo", () => { }); await holderAgent.agent.mediationRecipient.stopMessagePickup(); - + logger.info(`waiting for 20 seconds`) await delay(20000); // Pause for 20 seconds @@ -91,12 +92,12 @@ describe("credo", () => { await issuerAgent.sendBasicMessage(remoteInvitation.payload.connection_id as string, 'Hello from issuer 1') await issuerAgent.sendBasicMessage(remoteInvitation.payload.connection_id as string, 'Hello from issuer 2') - await delay(5000); + await delay(5000); await holderAgent.agent.mediationRecipient.initiateMessagePickup(); // await holderAgent.agent.outboundTransports[0].start(holderAgent.agent) // await holderAgent.agent.mediationRecipient.initiateMessagePickup(); - expect(await messageReceivedPromise).toBe(messageCount); + expect(await messageReceivedPromise).toBe(messageCount); }, 35000); }); diff --git a/src/lib.ts b/src/lib.ts index e31c44b..c796973 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -10,6 +10,7 @@ import { log, dir} from "console" import chalk from 'chalk'; import { AgentCredo } from './AgentCredo'; import { AnonCredsApi } from '@credo-ts/anoncreds'; +import QRCode from "qrcode"; export enum OOB_CONNECTION_HANDSHAKE_PROTOCOL { @@ -940,4 +941,10 @@ export const verifyCredentialA2 = async (verifier:AriesAgent, holder: AriesAgent remoteInvitation3.payload.invitation_url = `${publicUrl}/${invitationFile}` return remoteInvitation3 + } + + export const writeQRCode = async (filePath: string, invitationUrl: string) => { + const QRCodePath = path.resolve(process.cwd() as string, filePath); + fs.mkdirSync(path.dirname(QRCodePath), { recursive: true }); + await QRCode.toFile(QRCodePath, invitationUrl, { margin: 10 }); } \ No newline at end of file