Skip to content

Commit

Permalink
feat: Optionally send problem report when decline a proof request
Browse files Browse the repository at this point in the history
Signed-off-by: Artemkaaas <[email protected]>
  • Loading branch information
Artemkaaas committed Mar 28, 2023
1 parent 50e877d commit 4b973ed
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 18 deletions.
62 changes: 48 additions & 14 deletions packages/core/src/modules/proofs/ProofsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,17 @@ export class ProofsApi<PPs extends ProofProtocol[]> implements ProofsApi<PPs> {
}
}

public async declineRequest(proofRecordId: string): Promise<ProofExchangeRecord> {
public async declineRequest(proofRecordId: string, sendProblemReport?: boolean): Promise<ProofExchangeRecord> {
const proofRecord = await this.getById(proofRecordId)
proofRecord.assertState(ProofState.RequestReceived)

const protocol = this.getProtocol(proofRecord.protocolVersion)
await protocol.updateState(this.agentContext, proofRecord, ProofState.Declined)

if (sendProblemReport) {
await this.sendProblemReport({ proofRecordId, description: 'Request declined' })
}

return proofRecord
}

Expand Down Expand Up @@ -555,29 +559,59 @@ export class ProofsApi<PPs extends ProofProtocol[]> implements ProofsApi<PPs> {
*/
public async sendProblemReport(options: SendProofProblemReportOptions): Promise<ProofExchangeRecord> {
const proofRecord = await this.getById(options.proofRecordId)
if (!proofRecord.connectionId) {
throw new AriesFrameworkError(`No connectionId found for proof record '${proofRecord.id}'.`)
}

const protocol = this.getProtocol(proofRecord.protocolVersion)
const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId)

// Assert
connectionRecord.assertReady()
const requestMessage = await protocol.findRequestMessage(this.agentContext, proofRecord.id)

const { message: problemReport } = await protocol.createProblemReport(this.agentContext, {
proofRecord,
description: options.description,
})

const outboundMessageContext = new OutboundMessageContext(problemReport, {
agentContext: this.agentContext,
connection: connectionRecord,
associatedRecord: proofRecord,
})
if (proofRecord.connectionId) {
const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId)

await this.messageSender.sendMessage(outboundMessageContext)
return proofRecord
// Assert
connectionRecord.assertReady()

const outboundMessageContext = new OutboundMessageContext(problemReport, {
agentContext: this.agentContext,
connection: connectionRecord,
associatedRecord: proofRecord,
})

await this.messageSender.sendMessage(outboundMessageContext)
return proofRecord
} else if (requestMessage?.service) {
// Create ~service decorator
const routing = await this.routingService.getRouting(this.agentContext)
const ourService = new ServiceDecorator({
serviceEndpoint: routing.endpoints[0],
recipientKeys: [routing.recipientKey.publicKeyBase58],
routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58),
})
const recipientService = requestMessage.service

await this.messageSender.sendMessageToService(
new OutboundMessageContext(problemReport, {
agentContext: this.agentContext,
serviceParams: {
service: recipientService.resolvedDidCommService,
senderKey: ourService.resolvedDidCommService.recipientKeys[0],
returnRoute: options.useReturnRoute ?? true, // defaults to true if missing
},
})
)

return proofRecord
}
// Cannot send message without connectionId or ~service decorator
else {
throw new AriesFrameworkError(
`Cannot send problem report without connectionId or ~service decorator on presentation request.`
)
}
}

public async getFormatData(proofRecordId: string): Promise<GetProofFormatDataReturn<ProofFormatsFromProtocols<PPs>>> {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/modules/proofs/ProofsApiOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,5 @@ export interface SelectCredentialsForProofRequestReturn<PPs extends ProofProtoco
export interface SendProofProblemReportOptions {
proofRecordId: string
description: string
useReturnRoute?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,14 @@ export abstract class BaseProofProtocol<PFs extends ProofFormatService[] = Proof
public async processProblemReport(
messageContext: InboundMessageContext<ProblemReportMessage>
): Promise<ProofExchangeRecord> {
const { message: proofProblemReportMessage, agentContext } = messageContext

const connection = messageContext.assertReadyConnection()
const { message: proofProblemReportMessage, agentContext, connection } = messageContext

agentContext.config.logger.debug(`Processing problem report with message id ${proofProblemReportMessage.id}`)

const proofRecord = await this.getByThreadAndConnectionId(
agentContext,
proofProblemReportMessage.threadId,
connection.id
connection?.id
)

// Update record
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
makeConnection,
testLogger,
setupEventReplaySubjects,
waitForProofExchangeRecord,
} from '../../../../../../tests'
import { Agent } from '../../../../../agent/Agent'
import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment'
Expand Down Expand Up @@ -532,4 +533,98 @@ describe('V2 Connectionless Proofs - Indy', () => {
threadId: requestMessage.threadId,
})
})

test('Faber starts with connection-less proof requests to Alice but gets Problem Reported', async () => {
testLogger.test('Faber sends presentation request to Alice')

const {
issuerAgent: faberAgent,
issuerReplay: faberReplay,
holderAgent: aliceAgent,
holderReplay: aliceReplay,
credentialDefinitionId,
issuerHolderConnectionId: faberConnectionId,
} = await setupAnonCredsTests({
issuerName: 'Faber connection-less Proofs v2 - Reject Request',
holderName: 'Alice connection-less Proofs v2 - Reject Request',
autoAcceptProofs: AutoAcceptProof.Never,
attributeNames: ['name', 'age'],
})

await issueLegacyAnonCredsCredential({
issuerAgent: faberAgent,
issuerReplay: faberReplay,
holderAgent: aliceAgent,
holderReplay: aliceReplay,
issuerHolderConnectionId: faberConnectionId,
offer: {
credentialDefinitionId,
attributes: [
{
name: 'name',
value: 'Alice',
},
{
name: 'age',
value: '99',
},
],
},
})

agents = [aliceAgent, faberAgent]

// eslint-disable-next-line prefer-const
// eslint-disable-next-line prefer-const
let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({
protocolVersion: 'v2',
proofFormats: {
indy: {
name: 'test-proof-request',
version: '1.0',
requested_attributes: {
name: {
name: 'name',
restrictions: [
{
cred_def_id: credentialDefinitionId,
},
],
},
},
requested_predicates: {},
},
},
autoAcceptProof: AutoAcceptProof.ContentApproved,
})

const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({
recordId: faberProofExchangeRecord.id,
message,
domain: 'rxjs:faber',
})

for (const transport of faberAgent.outboundTransports) {
await faberAgent.unregisterOutboundTransport(transport)
}

const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, {
state: ProofState.RequestReceived,
})

await aliceAgent.receiveMessage(requestMessage.toJSON())
const aliceProofExchangeRecord = await aliceProofExchangeRecordPromise

await aliceAgent.proofs.declineRequest(aliceProofExchangeRecord.id, true)

await waitForProofExchangeRecordSubject(aliceReplay, {
state: ProofState.Declined,
threadId: requestMessage.threadId,
})

await waitForProofExchangeRecordSubject(faberReplay, {
state: ProofState.Abandoned,
threadId: requestMessage.threadId,
})
})
})

0 comments on commit 4b973ed

Please sign in to comment.