Skip to content
Snippets Groups Projects
Commit 68b355b5 authored by Steffen Schulze's avatar Steffen Schulze
Browse files

Merge branch 'ssi-credential-accept' into 'main'

feat: accept credential offer

See merge request !34
parents ff969761 57f31a4c
No related branches found
No related tags found
1 merge request!34feat: accept credential offer
Pipeline #39745 failed
...@@ -50,6 +50,27 @@ export class EventAnonCredsCredentialsGetById extends BaseEvent<CredentialExchan ...@@ -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<{ export type EventDidcommAnonCredsCredentialsOfferInput = BaseEventInput<{
connectionId: string; connectionId: string;
credentialDefinitionId: string; credentialDefinitionId: string;
......
...@@ -15,6 +15,8 @@ import { ...@@ -15,6 +15,8 @@ import {
EventAnonCredsCredentialsGetAllInput, EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetById, EventAnonCredsCredentialsGetById,
EventAnonCredsCredentialsGetByIdInput, EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput,
EventDidcommAnonCredsCredentialsOffer, EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferInput, EventDidcommAnonCredsCredentialsOfferInput,
EventDidcommAnonCredsCredentialsOfferToSelf, EventDidcommAnonCredsCredentialsOfferToSelf,
...@@ -97,6 +99,16 @@ export class AnonCredsCredentialsController { ...@@ -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) @MessagePattern(EventDidcommAnonCredsCredentialsOffer.token)
public async offer( public async offer(
options: EventDidcommAnonCredsCredentialsOfferInput, options: EventDidcommAnonCredsCredentialsOfferInput,
......
import type { TenantAgent } from '../agent.service.js'; import type { TenantAgent } from '../agent.service.js';
import type { CredentialExchangeRecord } from '@credo-ts/core'; import type {
CredentialExchangeRecord,
CredentialStateChangedEvent,
} from '@credo-ts/core';
import type { import type {
EventAnonCredsCredentialOfferGetAll, EventAnonCredsCredentialOfferGetAll,
EventAnonCredsCredentialOfferGetAllInput, EventAnonCredsCredentialOfferGetAllInput,
...@@ -13,13 +16,19 @@ import type { ...@@ -13,13 +16,19 @@ import type {
EventAnonCredsCredentialsDeleteByIdInput, EventAnonCredsCredentialsDeleteByIdInput,
EventAnonCredsCredentialsGetAllInput, EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetByIdInput, EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput,
EventDidcommAnonCredsCredentialsOffer, EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferInput, EventDidcommAnonCredsCredentialsOfferInput,
EventDidcommAnonCredsCredentialsOfferToSelf, EventDidcommAnonCredsCredentialsOfferToSelf,
EventDidcommAnonCredsCredentialsOfferToSelfInput, EventDidcommAnonCredsCredentialsOfferToSelfInput,
} from '@ocm/shared'; } 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 { GenericRecord } from '@credo-ts/core/build/modules/generic-records/repository/GenericRecord.js';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { logger } from '@ocm/shared'; import { logger } from '@ocm/shared';
...@@ -138,6 +147,19 @@ export class AnonCredsCredentialsService { ...@@ -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({ public async offer({
tenantId, tenantId,
connectionId, connectionId,
...@@ -193,7 +215,36 @@ export class AnonCredsCredentialsService { ...@@ -193,7 +215,36 @@ export class AnonCredsCredentialsService {
const revocationRegistryIndex = await this.getNextRevocationIdx(t); 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', protocolVersion: 'v2',
autoAcceptCredential: AutoAcceptCredential.Always, autoAcceptCredential: AutoAcceptCredential.Always,
connectionId: connection.id, connectionId: connection.id,
...@@ -206,6 +257,8 @@ export class AnonCredsCredentialsService { ...@@ -206,6 +257,8 @@ export class AnonCredsCredentialsService {
}, },
}, },
}); });
return acceptOfferListener;
}); });
} }
......
...@@ -145,7 +145,16 @@ export class ConnectionsService { ...@@ -145,7 +145,16 @@ export class ConnectionsService {
const outOfBandRecord = await t.oob.createInvitation(); const outOfBandRecord = await t.oob.createInvitation();
const invitation = outOfBandRecord.outOfBandInvitation; 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) => return new Promise((resolve) =>
this.agent.events.on<ConnectionStateChangedEvent>( this.agent.events.on<ConnectionStateChangedEvent>(
......
import type { INestApplication } from '@nestjs/common'; import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices'; import type { ClientProxy } from '@nestjs/microservices';
import type { import type {
EventAnonCredsCredentialRequestGetAllInput,
EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsOfferToSelfInput,
EventAnonCredsCredentialOfferGetAllInput, EventAnonCredsCredentialOfferGetAllInput,
EventAnonCredsCredentialOfferGetByIdInput, EventAnonCredsCredentialOfferGetByIdInput,
EventAnonCredsCredentialRequestGetAllInput,
EventAnonCredsCredentialRequestGetByIdInput, EventAnonCredsCredentialRequestGetByIdInput,
EventAnonCredsCredentialsDeleteByIdInput, EventAnonCredsCredentialsDeleteByIdInput,
EventAnonCredsCredentialsGetAllInput,
EventAnonCredsCredentialsGetByIdInput,
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput,
EventDidcommAnonCredsCredentialsOfferInput,
EventDidcommAnonCredsCredentialsOfferToSelfInput,
} from '@ocm/shared'; } 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 { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { import {
EventAnonCredsCredentialsDeleteById,
EventAnonCredsCredentialOfferGetAll, EventAnonCredsCredentialOfferGetAll,
EventAnonCredsCredentialOfferGetById, EventAnonCredsCredentialOfferGetById,
EventAnonCredsCredentialRequestGetAll, EventAnonCredsCredentialRequestGetAll,
EventAnonCredsCredentialRequestGetById, EventAnonCredsCredentialRequestGetById,
EventAnonCredsCredentialsDeleteById,
EventAnonCredsCredentialsGetAll, EventAnonCredsCredentialsGetAll,
EventAnonCredsCredentialsGetById, EventAnonCredsCredentialsGetById,
EventAnonCredsProofsDeleteById, EventAnonCredsProofsDeleteById,
EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferToSelf, EventDidcommAnonCredsCredentialsOfferToSelf,
} from '@ocm/shared'; } from '@ocm/shared';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
...@@ -50,6 +58,7 @@ describe('Credentials', () => { ...@@ -50,6 +58,7 @@ describe('Credentials', () => {
let issuerDid: string; let issuerDid: string;
let credentialDefinitionId: string; let credentialDefinitionId: string;
let connectionId: string;
beforeAll(async () => { beforeAll(async () => {
const moduleRef = await Test.createTestingModule({ const moduleRef = await Test.createTestingModule({
...@@ -111,6 +120,18 @@ describe('Credentials', () => { ...@@ -111,6 +120,18 @@ describe('Credentials', () => {
}); });
credentialDefinitionId = cdi; 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 () => { afterAll(async () => {
...@@ -197,6 +218,45 @@ describe('Credentials', () => { ...@@ -197,6 +218,45 @@ describe('Credentials', () => {
expect(eventInstance.instance).toEqual(null); 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 () => { it(EventDidcommAnonCredsCredentialsOfferToSelf.token, async () => {
const attributes = [ const attributes = [
{ name: 'Name', value: 'Berend' }, { 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