From 06a96f2cd02ce148ac1bee4063c35c53edd2a2ea Mon Sep 17 00:00:00 2001
From: Berend Sliedrecht <berend@animo.id>
Date: Tue, 28 Nov 2023 15:36:37 +0100
Subject: [PATCH] feat(ssi): block connection by id or did

Signed-off-by: Berend Sliedrecht <berend@animo.id>
---
 .../events/__tests__/connectionEvents.spec.ts | 18 +++++++++++
 apps/shared/src/events/connectionEvents.ts    | 18 +++++++++--
 .../src/agent/agent.service.ts                |  7 +++-
 .../connections/connections.controller.ts     | 12 +++++++
 .../agent/connections/connections.service.ts  | 32 +++++++++++++++++++
 .../test/connections.e2e-spec.ts              | 13 ++++++++
 6 files changed, 96 insertions(+), 4 deletions(-)

diff --git a/apps/shared/src/events/__tests__/connectionEvents.spec.ts b/apps/shared/src/events/__tests__/connectionEvents.spec.ts
index 42ea6e6..70f97f0 100644
--- a/apps/shared/src/events/__tests__/connectionEvents.spec.ts
+++ b/apps/shared/src/events/__tests__/connectionEvents.spec.ts
@@ -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,
+    });
+  });
 });
diff --git a/apps/shared/src/events/connectionEvents.ts b/apps/shared/src/events/connectionEvents.ts
index 1831f5c..22177f1 100644
--- a/apps/shared/src/events/connectionEvents.ts
+++ b/apps/shared/src/events/connectionEvents.ts
@@ -55,9 +55,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 +67,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);
+  }
+}
diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts
index 0d68b5e..10bcad8 100644
--- a/apps/ssi-abstraction/src/agent/agent.service.ts
+++ b/apps/ssi-abstraction/src/agent/agent.service.ts
@@ -203,6 +203,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 {}
   }
 }
diff --git a/apps/ssi-abstraction/src/agent/connections/connections.controller.ts b/apps/ssi-abstraction/src/agent/connections/connections.controller.ts
index 322f337..0eb6bc7 100644
--- a/apps/ssi-abstraction/src/agent/connections/connections.controller.ts
+++ b/apps/ssi-abstraction/src/agent/connections/connections.controller.ts
@@ -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),
+    );
+  }
 }
diff --git a/apps/ssi-abstraction/src/agent/connections/connections.service.ts b/apps/ssi-abstraction/src/agent/connections/connections.service.ts
index 4c6901c..ba72b2c 100644
--- a/apps/ssi-abstraction/src/agent/connections/connections.service.ts
+++ b/apps/ssi-abstraction/src/agent/connections/connections.service.ts
@@ -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;
diff --git a/apps/ssi-abstraction/test/connections.e2e-spec.ts b/apps/ssi-abstraction/test/connections.e2e-spec.ts
index 8fa8364..3f52769 100644
--- a/apps/ssi-abstraction/test/connections.e2e-spec.ts
+++ b/apps/ssi-abstraction/test/connections.e2e-spec.ts
@@ -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();
+  });
 });
-- 
GitLab