Skip to content
Snippets Groups Projects
Verified Commit 57f31a4c authored by Berend Sliedrecht's avatar Berend Sliedrecht Committed by Konstantin Tsabolov
Browse files

feat: accept credential offer


Signed-off-by: default avatarBerend Sliedrecht <berend@animo.id>
parent 53b98bc9
No related branches found
No related tags found
No related merge requests found
Pipeline #39606 failed
......@@ -50,6 +50,27 @@ export class EventAnonCredsCredentialsGetById extends BaseEvent<CredentialExchan
}
}
export type EventDidcommAnonCredsCredentialsAcceptOfferInput = BaseEventInput<{
credentialId: string;
}>;
export class EventDidcommAnonCredsCredentialsAcceptOffer extends BaseEvent<CredentialExchangeRecord> {
public static token = 'didcomm.anoncreds.credentials.acceptOffer';
public get instance() {
return JsonTransformer.fromJSON(this.data, CredentialExchangeRecord);
}
public static fromEvent(e: EventDidcommAnonCredsCredentialsOffer) {
return new EventDidcommAnonCredsCredentialsOffer(
e.data,
e.tenantId,
e.id,
e.type,
e.timestamp,
);
}
}
export type EventDidcommAnonCredsCredentialsOfferInput = BaseEventInput<{
connectionId: string;
credentialDefinitionId: string;
......
......@@ -15,6 +15,8 @@ import {
EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetById,
EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput,
EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferInput,
EventDidcommAnonCredsCredentialsOfferToSelf,
......@@ -97,6 +99,16 @@ export class AnonCredsCredentialsController {
);
}
@MessagePattern(EventDidcommAnonCredsCredentialsAcceptOffer.token)
public async acceptOffer(
options: EventDidcommAnonCredsCredentialsAcceptOfferInput,
): Promise<EventDidcommAnonCredsCredentialsAcceptOffer> {
return new EventDidcommAnonCredsCredentialsAcceptOffer(
await this.credentialsService.acceptOffer(options),
options.tenantId,
);
}
@MessagePattern(EventDidcommAnonCredsCredentialsOffer.token)
public async offer(
options: EventDidcommAnonCredsCredentialsOfferInput,
......
import type { TenantAgent } from '../agent.service.js';
import type { CredentialExchangeRecord } from '@credo-ts/core';
import type {
CredentialExchangeRecord,
CredentialStateChangedEvent,
} from '@credo-ts/core';
import type {
EventAnonCredsCredentialOfferGetAll,
EventAnonCredsCredentialOfferGetAllInput,
......@@ -13,13 +16,19 @@ import type {
EventAnonCredsCredentialsDeleteByIdInput,
EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput,
EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferInput,
EventDidcommAnonCredsCredentialsOfferToSelf,
EventDidcommAnonCredsCredentialsOfferToSelfInput,
} from '@ocm/shared';
import { AutoAcceptCredential, CredentialState } from '@credo-ts/core';
import {
AutoAcceptCredential,
CredentialEventTypes,
CredentialState,
} from '@credo-ts/core';
import { GenericRecord } from '@credo-ts/core/build/modules/generic-records/repository/GenericRecord.js';
import { Injectable } from '@nestjs/common';
import { logger } from '@ocm/shared';
......@@ -138,6 +147,19 @@ export class AnonCredsCredentialsService {
});
}
public async acceptOffer({
tenantId,
credentialId,
}: EventDidcommAnonCredsCredentialsAcceptOfferInput): Promise<
EventDidcommAnonCredsCredentialsAcceptOffer['data']
> {
return this.withTenantService.invoke(tenantId, (t) =>
t.credentials.acceptOffer({
credentialRecordId: credentialId,
}),
);
}
public async offer({
tenantId,
connectionId,
......@@ -193,7 +215,36 @@ export class AnonCredsCredentialsService {
const revocationRegistryIndex = await this.getNextRevocationIdx(t);
return t.credentials.offerCredential({
const acceptOfferListener: Promise<CredentialExchangeRecord> =
new Promise((resolve) =>
t.events.on<CredentialStateChangedEvent>(
CredentialEventTypes.CredentialStateChanged,
async ({ payload: { credentialRecord } }) => {
const connection = connections.find(
(c) => c.id === credentialRecord.connectionId,
);
const withSelf = connection?.metadata.get<{ withSelf: boolean }>(
MetadataTokens.CONNECTION_METADATA_KEY,
);
const isWithSelf = withSelf?.withSelf ?? false;
if (
credentialRecord.state === CredentialState.OfferReceived &&
isWithSelf
) {
resolve(
await t.credentials.acceptOffer({
credentialRecordId: credentialRecord.id,
}),
);
}
},
),
);
await t.credentials.offerCredential({
protocolVersion: 'v2',
autoAcceptCredential: AutoAcceptCredential.Always,
connectionId: connection.id,
......@@ -206,6 +257,8 @@ export class AnonCredsCredentialsService {
},
},
});
return acceptOfferListener;
});
}
......
......@@ -145,7 +145,16 @@ export class ConnectionsService {
const outOfBandRecord = await t.oob.createInvitation();
const invitation = outOfBandRecord.outOfBandInvitation;
void t.oob.receiveInvitation(invitation);
const { connectionRecord } = await t.oob.receiveInvitation(invitation);
if (connectionRecord) {
connectionRecord.metadata.set(MetadataTokens.CONNECTION_METADATA_KEY, {
trusted: true,
withSelf: true,
});
const connRepo = t.dependencyManager.resolve(ConnectionRepository);
await connRepo.update(t.context, connectionRecord);
}
return new Promise((resolve) =>
this.agent.events.on<ConnectionStateChangedEvent>(
......
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type {
EventAnonCredsCredentialRequestGetAllInput,
EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsOfferToSelfInput,
EventAnonCredsCredentialOfferGetAllInput,
EventAnonCredsCredentialOfferGetByIdInput,
EventAnonCredsCredentialRequestGetAllInput,
EventAnonCredsCredentialRequestGetByIdInput,
EventAnonCredsCredentialsDeleteByIdInput,
EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput,
EventDidcommAnonCredsCredentialsOfferInput,
EventDidcommAnonCredsCredentialsOfferToSelfInput,
} from '@ocm/shared';
import { AutoAcceptCredential, CredentialExchangeRecord } from '@credo-ts/core';
import {
AutoAcceptCredential,
CredentialExchangeRecord,
CredentialState,
} from '@credo-ts/core';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import {
EventAnonCredsCredentialsDeleteById,
EventAnonCredsCredentialOfferGetAll,
EventAnonCredsCredentialOfferGetById,
EventAnonCredsCredentialRequestGetAll,
EventAnonCredsCredentialRequestGetById,
EventAnonCredsCredentialsDeleteById,
EventAnonCredsCredentialsGetAll,
EventAnonCredsCredentialsGetById,
EventAnonCredsProofsDeleteById,
EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferToSelf,
} from '@ocm/shared';
import { randomBytes } from 'crypto';
......@@ -50,6 +58,7 @@ describe('Credentials', () => {
let issuerDid: string;
let credentialDefinitionId: string;
let connectionId: string;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
......@@ -111,6 +120,18 @@ describe('Credentials', () => {
});
credentialDefinitionId = cdi;
const connectionService = app.get(ConnectionsService);
const { invitationUrl } = await connectionService.createInvitation({
tenantId,
});
const { id: cId } = await connectionService.receiveInvitationFromUrl({
tenantId,
invitationUrl,
});
connectionId = cId;
});
afterAll(async () => {
......@@ -197,6 +218,45 @@ describe('Credentials', () => {
expect(eventInstance.instance).toEqual(null);
});
it(EventDidcommAnonCredsCredentialsOffer.token, async () => {
const attributes = [
{ name: 'Name', value: 'Berend' },
{ name: 'Age', value: '25' },
];
const response$ = client.send<
EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferInput
>(EventDidcommAnonCredsCredentialsOffer.token, {
tenantId,
connectionId,
attributes,
credentialDefinitionId,
});
const response = await firstValueFrom(response$);
const eventInstance =
EventDidcommAnonCredsCredentialsOffer.fromEvent(response);
await new Promise((r) => setTimeout(r, 2000));
const acceptResponse$ = client.send<
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput
>(EventDidcommAnonCredsCredentialsOffer.token, {
tenantId,
credentialId: eventInstance.instance.id,
});
const acceptResponse = await firstValueFrom(acceptResponse$);
const acceptEventInstance =
EventAnonCredsCredentialsGetById.fromEvent(acceptResponse);
expect(acceptEventInstance.instance).toMatchObject({
state: CredentialState.RequestSent,
});
});
it(EventDidcommAnonCredsCredentialsOfferToSelf.token, async () => {
const attributes = [
{ name: 'Name', value: 'Berend' },
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment