Skip to content
Snippets Groups Projects
Commit 4468661d 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 b7ba92e5
No related branches found
No related tags found
No related merge requests found
This commit is part of merge request !13. Comments created here will be created in the context of that merge request.
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