Skip to content
Snippets Groups Projects
Commit a9bc2b46 authored by Berend Sliedrecht's avatar Berend Sliedrecht
Browse files

feat(ssi): block connection by id or did


Signed-off-by: default avatarBerend Sliedrecht <berend@animo.id>
parent fdcce1c0
No related branches found
No related tags found
1 merge request!11feat(ssi): resolve dids
Showing
with 328 additions and 22 deletions
......@@ -5,6 +5,7 @@ import {
} from '@aries-framework/core';
import {
EventDidcommConnectionsBlock,
EventDidcommConnectionsCreateWithSelf,
EventDidcommConnectionsGetAll,
EventDidcommConnectionsGetById,
......@@ -49,4 +50,21 @@ describe('Connection Events', () => {
state: DidExchangeState.Completed,
});
});
it('should create a new connections block event', () => {
const event = new EventDidcommConnectionsBlock(
new ConnectionRecord({
role: DidExchangeRole.Requester,
state: DidExchangeState.Completed,
}),
);
expect(typeof event.id).toStrictEqual('string');
expect(event.type).toStrictEqual('EventDidcommConnectionsBlock');
expect(event.timestamp).toBeInstanceOf(Date);
expect(event.instance).toMatchObject({
role: DidExchangeRole.Requester,
state: DidExchangeState.Completed,
});
});
});
import {
ConnectionRecord,
DidDocument,
JsonTransformer,
} from '@aries-framework/core';
import { ConnectionRecord, JsonTransformer } from '@aries-framework/core';
import { BaseEvent } from './baseEvents.js';
export class EventInfoPublicDid extends BaseEvent<DidDocument> {
public static token = 'didcomm.info.publicDid';
public get instance() {
return JsonTransformer.fromJSON(this.data, DidDocument);
}
public static fromEvent(e: EventInfoPublicDid) {
return new EventInfoPublicDid(e.data, e.id, e.type, e.timestamp);
}
}
export class EventDidcommConnectionsGetAll extends BaseEvent<
Array<ConnectionRecord>
> {
......@@ -55,9 +39,7 @@ export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionR
public static token = 'didcomm.connections.createWithSelf';
public get instance() {
return JsonTransformer.fromJSON(this.data, ConnectionRecord, {
validate: true,
});
return JsonTransformer.fromJSON(this.data, ConnectionRecord);
}
public static fromEvent(e: EventDidcommConnectionsCreateWithSelf) {
......@@ -69,3 +51,17 @@ export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionR
);
}
}
export class EventDidcommConnectionsBlock extends BaseEvent<ConnectionRecord | null> {
public static token = 'didcomm.connections.block';
public get instance() {
return this.data
? JsonTransformer.fromJSON(this.data, ConnectionRecord)
: null;
}
public static fromEvent(e: EventDidcommConnectionsBlock) {
return new EventDidcommConnectionsBlock(e.data, e.id, e.type, e.timestamp);
}
}
import { DidDocument, JsonTransformer } from '@aries-framework/core';
import { BaseEvent } from './baseEvents.js';
/**
*
* @todo: this should be removed as it is a weird event that should not be needed
*
*/
export class EventInfoPublicDid extends BaseEvent<DidDocument> {
public static token = 'dids.publicDid';
public get instance() {
return JsonTransformer.fromJSON(this.data, DidDocument);
}
public static fromEvent(e: EventInfoPublicDid) {
return new EventInfoPublicDid(e.data, e.id, e.type, e.timestamp);
}
}
export class EventDidsResolve extends BaseEvent<DidDocument> {
public static token = 'dids.resolve';
public get instance() {
return JsonTransformer.fromJSON(this.data, DidDocument);
}
public static fromEvent(e: EventDidsResolve) {
return new EventDidsResolve(e.data, e.id, e.type, e.timestamp);
}
}
......@@ -5,3 +5,4 @@ export * from './logging/logger.js';
export * from './logging/logAxiosError.js';
export * from './events/connectionEvents.js';
export * from './events/didEvents.js';
......@@ -12,6 +12,8 @@ import {
CredentialsModule,
DidsModule,
HttpOutboundTransport,
JwkDidRegistrar,
JwkDidResolver,
KeyDidRegistrar,
KeyDidResolver,
KeyType,
......@@ -19,6 +21,7 @@ import {
PeerDidRegistrar,
PeerDidResolver,
TypedArrayEncoder,
WebDidResolver,
} from '@aries-framework/core';
import {
IndyVdrAnonCredsRegistry,
......@@ -107,8 +110,10 @@ export class AgentService implements OnApplicationShutdown {
new IndyVdrSovDidResolver(),
new PeerDidResolver(),
new KeyDidResolver(),
new JwkDidResolver(),
new WebDidResolver(),
],
registrars: [new PeerDidRegistrar(), new KeyDidRegistrar()],
registrars: [new PeerDidRegistrar(), new KeyDidRegistrar(), new JwkDidRegistrar()],
}),
askar: new AskarModule({ ariesAskar }),
......@@ -203,6 +208,11 @@ export class AgentService implements OnApplicationShutdown {
public async onApplicationShutdown() {
if (!this.agent.isInitialized) return;
await this.agent.shutdown();
// If we cannot shutdown the wallet on application shutdown, no error will occur
// This is done because the Askar shutdown procedure is a bit buggy
try {
await this.agent.shutdown();
// eslint-disable-next-line no-empty
} catch {}
}
}
......@@ -4,6 +4,7 @@ import {
EventDidcommConnectionsGetById,
EventDidcommConnectionsGetAll,
EventDidcommConnectionsCreateWithSelf,
EventDidcommConnectionsBlock,
} from '@ocm/shared';
import { ConnectionsService } from './connections.service.js';
......@@ -36,4 +37,15 @@ export class ConnectionsController {
await this.connectionsService.createConnectionWithSelf(),
);
}
@MessagePattern(EventDidcommConnectionsBlock.token)
public async blockConnection({
idOrDid,
}: {
idOrDid: string;
}): Promise<EventDidcommConnectionsBlock> {
return new EventDidcommConnectionsBlock(
await this.connectionsService.blockByIdOrDid(idOrDid),
);
}
}
......@@ -9,6 +9,7 @@ import {
ConnectionRepository,
DidExchangeState,
} from '@aries-framework/core';
import { isDid } from '@aries-framework/core/build/utils/did.js';
import { Injectable } from '@nestjs/common';
import { MetadataTokens } from '../../common/constants.js';
......@@ -30,6 +31,37 @@ export class ConnectionsService {
return await this.agent.connections.findById(id);
}
public async blockByIdOrDid(
idOrDid: string,
): Promise<ConnectionRecord | null> {
if (isDid(idOrDid)) {
const records = await this.agent.connections.findAllByQuery({
theirDid: idOrDid,
});
if (records.length === 0) {
return null;
}
if (records.length > 1) {
throw new Error(
'Found multiple records with the same DID. This should not be possible',
);
}
await this.agent.connections.deleteById(records[0].id);
return records[0];
}
const record = await this.agent.connections.findById(idOrDid);
if (!record) return null;
await this.agent.connections.deleteById(record.id);
return record;
}
public async createConnectionWithSelf(): Promise<ConnectionRecord> {
const outOfBandRecord = await this.agent.oob.createInvitation();
const invitation = outOfBandRecord.outOfBandInvitation;
......
import { DidDocument } from '@aries-framework/core';
import { Test } from '@nestjs/testing';
import { mockConfigModule } from '../../../config/__tests__/mockConfig.js';
import { AgentModule } from '../../agent.module.js';
import { DidsController } from '../dids.controller.js';
import { DidsService } from '../dids.service.js';
describe('DidsController', () => {
let didsController: DidsController;
let didsService: DidsService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [mockConfigModule(), AgentModule],
controllers: [DidsController],
providers: [DidsService],
}).compile();
didsService = moduleRef.get(DidsService);
didsController = moduleRef.get(DidsController);
});
describe('resolve', () => {
it('should resolve a basic did', async () => {
const result = new DidDocument({ id: 'did:key:foo' });
jest.spyOn(didsService, 'resolve').mockResolvedValue(result);
const event = await didsController.resolve({
did: 'did:key:foo',
});
expect(event.data).toStrictEqual(result);
});
});
});
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { EventDidsResolve } from '@ocm/shared';
import { DidsService } from './dids.service.js';
@Controller('dids')
export class DidsController {
public constructor(private didsService: DidsService) {}
@MessagePattern('dids.resolve')
public async resolve({ did }: { did: string }) {
return new EventDidsResolve(await this.didsService.resolve(did));
}
}
import { Module } from '@nestjs/common';
import { AgentModule } from '../agent.module.js';
import { DidsController } from './dids.controller.js';
import { DidsService } from './dids.service.js';
@Module({
imports: [AgentModule],
providers: [DidsService],
controllers: [DidsController],
})
export class DidsModule {}
import type { AppAgent } from '../agent.service.js';
import { Injectable } from '@nestjs/common';
import { AgentService } from '../agent.service.js';
@Injectable()
export class DidsService {
public agent: AppAgent;
public constructor(agentService: AgentService) {
this.agent = agentService.agent;
}
public async resolve(did: string) {
const {
didDocument,
didResolutionMetadata: { message, error },
} = await this.agent.dids.resolve(did);
if (!didDocument) {
throw new Error(
`Could not resolve did: '${did}'. Error: ${error ?? 'None'} Message: ${
message ?? 'None'
}`,
);
}
return didDocument;
}
}
......@@ -8,6 +8,7 @@ import {
EventDidcommConnectionsGetById,
EventDidcommConnectionsGetAll,
EventDidcommConnectionsCreateWithSelf,
EventDidcommConnectionsBlock,
} from '@ocm/shared';
import { firstValueFrom } from 'rxjs';
......@@ -83,4 +84,16 @@ describe('Connections', () => {
);
expect(metadata).toMatchObject({ trusted: true });
});
it(EventDidcommConnectionsBlock.token, async () => {
const response$: Observable<EventDidcommConnectionsBlock> = client.send(
EventDidcommConnectionsBlock.token,
{ idOrDid: 'some-id' },
);
const response = await firstValueFrom(response$);
const eventInstance = EventDidcommConnectionsBlock.fromEvent(response);
expect(eventInstance.instance).toBeNull();
});
});
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type { Observable } from 'rxjs';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import {
EventDidsResolve,
} from '@ocm/shared';
import { firstValueFrom } from 'rxjs';
import { AgentModule } from '../src/agent/agent.module.js';
import { DidsModule } from '../src/agent/dids/dids.module.js';
import { mockConfigModule } from '../src/config/__tests__/mockConfig.js';
describe('Dids', () => {
const TOKEN = 'DIDS_CLIENT_SERVICE';
let app: INestApplication;
let client: ClientProxy;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
mockConfigModule(3005),
AgentModule,
DidsModule,
ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]),
],
}).compile();
app = moduleRef.createNestApplication();
app.connectMicroservice({ transport: Transport.NATS });
await app.startAllMicroservices();
await app.init();
client = app.get(TOKEN);
await client.connect();
});
afterAll(async () => {
await app.close();
client.close();
});
it(EventDidsResolve.token, async () => {
const response$: Observable<EventDidsResolve> = client.send(
EventDidsResolve.token,
{
did: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
},
);
const response = await firstValueFrom(response$);
const eventInstance = EventDidsResolve.fromEvent(response);
expect(eventInstance.instance.toJSON()).toStrictEqual({
'@context': [
'https://w3id.org/did/v1',
'https://w3id.org/security/suites/ed25519-2018/v1',
'https://w3id.org/security/suites/x25519-2019/v1',
],
id: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
verificationMethod: [
{
id: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
type: 'Ed25519VerificationKey2018',
controller:
'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
publicKeyBase58: 'B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u',
},
],
authentication: [
'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
],
assertionMethod: [
'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
],
keyAgreement: [
{
id: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc',
type: 'X25519KeyAgreementKey2019',
controller:
'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
publicKeyBase58: 'JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr',
},
],
capabilityInvocation: [
'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
],
capabilityDelegation: [
'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
],
});
});
});
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