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

feat(ssi): finished tenants module with e2e tests


Signed-off-by: default avatarBerend Sliedrecht <berend@animo.id>
parent 4468661d
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 294 additions and 168 deletions
......@@ -22,6 +22,7 @@
},
"dependencies": {
"@aries-framework/core": "0.4.2",
"@aries-framework/tenants": "^0.4.2",
"@elastic/ecs-winston-format": "^1.5.0",
"@nestjs/common": "^10.2.10",
"@nestjs/microservices": "^10.2.10",
......@@ -32,10 +33,10 @@
"winston": "^3.11.0"
},
"devDependencies": {
"@types/jest": "^29.5.9",
"@types/node": "^20.9.3",
"@nestjs/cli": "^10.2.1",
"@nestjs/testing": "^10.2.10",
"@types/jest": "^29.5.9",
"@types/node": "^20.9.3",
"rimraf": "^5.0.5",
"supertest": "^6.1.3",
"ts-jest": "^29.1.1",
......
......@@ -6,7 +6,7 @@ describe('Base Events', () => {
});
it('should create a new base event', () => {
const baseEvent = new BaseEvent({ some: 'data' });
const baseEvent = new BaseEvent({ some: 'data' }, 'tenantId');
expect(typeof baseEvent.id).toStrictEqual('string');
expect(baseEvent.type).toStrictEqual('BaseEvent');
......
......@@ -17,7 +17,7 @@ describe('Connection Events', () => {
});
it('should create a new connections get all event', () => {
const event = new EventDidcommConnectionsGetAll([]);
const event = new EventDidcommConnectionsGetAll([], 'tenantId');
expect(typeof event.id).toStrictEqual('string');
expect(event.type).toStrictEqual('EventDidcommConnectionsGetAll');
......@@ -26,7 +26,7 @@ describe('Connection Events', () => {
});
it('should create a new connections get by id event', () => {
const event = new EventDidcommConnectionsGetById(null);
const event = new EventDidcommConnectionsGetById(null, 'tenantId');
expect(typeof event.id).toStrictEqual('string');
expect(event.type).toStrictEqual('EventDidcommConnectionsGetById');
......@@ -40,6 +40,7 @@ describe('Connection Events', () => {
role: DidExchangeRole.Requester,
state: DidExchangeState.Completed,
}),
'tenantId',
);
expect(typeof event.id).toStrictEqual('string');
......@@ -57,6 +58,7 @@ describe('Connection Events', () => {
role: DidExchangeRole.Requester,
state: DidExchangeState.Completed,
}),
'tenantId',
);
expect(typeof event.id).toStrictEqual('string');
......
import { DidDocument } from '@aries-framework/core';
import { EventDidsPublicDid, EventDidsResolve } from '../didEvents.js';
describe('Did Events', () => {
it('should return module', () => {
jest.requireActual('../didEvents');
});
it('should create get public did event', () => {
const doc = new DidDocument({ id: 'did:web:123.com' });
const event = new EventDidsPublicDid(doc, 'tenantId');
expect(typeof event.id).toStrictEqual('string');
expect(event.type).toStrictEqual('EventDidsPublicDid');
expect(event.timestamp).toBeInstanceOf(Date);
expect(event.instance).toMatchObject(doc);
});
it('should create did resolve event', () => {
const doc = new DidDocument({ id: 'did:my:id' });
const event = new EventDidsResolve(doc, 'tenantId');
expect(typeof event.id).toStrictEqual('string');
expect(event.type).toStrictEqual('EventDidsResolve');
expect(event.timestamp).toBeInstanceOf(Date);
expect(event.instance).toMatchObject(doc);
});
});
import { TenantRecord } from '@aries-framework/tenants';
import { EventTenantsCreate } from '../tenantEvents.js';
describe('Tenant Events', () => {
it('should return module', () => {
jest.requireActual('../tenantEvents');
});
it('should create a create tenant event', () => {
const tenantRecord = new TenantRecord({
config: {
label: 'my-label',
walletConfig: { id: 'some-id', key: 'some-key' },
},
});
const event = new EventTenantsCreate(tenantRecord, undefined);
expect(typeof event.id).toStrictEqual('string');
expect(event.type).toStrictEqual('EventTenantsCreate');
expect(event.timestamp).toBeInstanceOf(Date);
expect(event.instance).toMatchObject(tenantRecord);
});
});
import { utils } from '@aries-framework/core';
export class BaseEvent<T = Record<string, unknown>> {
export class BaseEvent<T = Record<string, unknown>, TenantIdType = string> {
public readonly id: string;
public readonly type: string;
public readonly timestamp: Date;
public readonly data: T;
public readonly tenantId: TenantIdType;
public constructor(
data: T,
tenantId: TenantIdType,
id?: string,
type?: string,
timestamp?: Date,
) {
this.data = data;
this.tenantId = tenantId;
public constructor(data: T, id?: string, type?: string, timestamp?: Date) {
this.id = id ?? utils.uuid();
this.type = type ?? this.constructor.name;
this.timestamp = timestamp ?? new Date();
this.data = data;
}
}
export type BaseEventInput<
T extends Record<string, unknown> = Record<string, unknown>,
TenantIdType extends undefined | string = string,
> = TenantIdType extends string ? { tenantId: string } & T : T;
import type { BaseEventInput } from './baseEvents.js';
import { ConnectionRecord, JsonTransformer } from '@aries-framework/core';
import { BaseEvent } from './baseEvents.js';
export type EventDidcommConnectionsGetAllInput = BaseEventInput;
export class EventDidcommConnectionsGetAll extends BaseEvent<
Array<ConnectionRecord>
> {
......@@ -12,10 +15,19 @@ export class EventDidcommConnectionsGetAll extends BaseEvent<
}
public static fromEvent(e: EventDidcommConnectionsGetAll) {
return new EventDidcommConnectionsGetAll(e.data, e.id, e.type, e.timestamp);
return new EventDidcommConnectionsGetAll(
e.data,
e.tenantId,
e.id,
e.type,
e.timestamp,
);
}
}
export type EventDidcommConnectionsGetByIdInput = BaseEventInput<{
id: string;
}>;
export class EventDidcommConnectionsGetById extends BaseEvent<ConnectionRecord | null> {
public static token = 'didcomm.connections.getById';
......@@ -28,6 +40,7 @@ export class EventDidcommConnectionsGetById extends BaseEvent<ConnectionRecord |
public static fromEvent(e: EventDidcommConnectionsGetById) {
return new EventDidcommConnectionsGetById(
e.data,
e.tenantId,
e.id,
e.type,
e.timestamp,
......@@ -35,6 +48,7 @@ export class EventDidcommConnectionsGetById extends BaseEvent<ConnectionRecord |
}
}
export type EventDidcommConnectionsCreateWithSelfInput = BaseEventInput;
export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionRecord> {
public static token = 'didcomm.connections.createWithSelf';
......@@ -45,6 +59,7 @@ export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionR
public static fromEvent(e: EventDidcommConnectionsCreateWithSelf) {
return new EventDidcommConnectionsCreateWithSelf(
e.data,
e.tenantId,
e.id,
e.type,
e.timestamp,
......@@ -52,6 +67,9 @@ export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionR
}
}
export type EventDidcommConnectionsBlockInput = BaseEventInput<{
idOrDid: string;
}>;
export class EventDidcommConnectionsBlock extends BaseEvent<ConnectionRecord | null> {
public static token = 'didcomm.connections.block';
......@@ -62,6 +80,12 @@ export class EventDidcommConnectionsBlock extends BaseEvent<ConnectionRecord | n
}
public static fromEvent(e: EventDidcommConnectionsBlock) {
return new EventDidcommConnectionsBlock(e.data, e.id, e.type, e.timestamp);
return new EventDidcommConnectionsBlock(
e.data,
e.tenantId,
e.id,
e.type,
e.timestamp,
);
}
}
import type { BaseEventInput } from './baseEvents.js';
import { DidDocument, JsonTransformer } from '@aries-framework/core';
import { BaseEvent } from './baseEvents.js';
......@@ -7,18 +9,31 @@ 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> {
export type EventDidsPublicDidInput = BaseEventInput;
/**
*
* @todo: this should be removed as it is a weird event that should not be needed
*
*/
export class EventDidsPublicDid 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);
public static fromEvent(e: EventDidsPublicDid) {
return new EventDidsPublicDid(
e.data,
e.tenantId,
e.id,
e.type,
e.timestamp,
);
}
}
export type EventDidsResolveInput = BaseEventInput<{ did: string }>;
export class EventDidsResolve extends BaseEvent<DidDocument> {
public static token = 'dids.resolve';
......@@ -27,6 +42,6 @@ export class EventDidsResolve extends BaseEvent<DidDocument> {
}
public static fromEvent(e: EventDidsResolve) {
return new EventDidsResolve(e.data, e.id, e.type, e.timestamp);
return new EventDidsResolve(e.data, e.tenantId, e.id, e.type, e.timestamp);
}
}
import type { BaseEventInput } from './baseEvents.js';
import { JsonTransformer } from '@aries-framework/core';
import { TenantRecord } from '@aries-framework/tenants';
import { BaseEvent } from './baseEvents.js';
export type EventTenantsCreateInput = BaseEventInput<
{ label: string },
undefined
>;
export class EventTenantsCreate extends BaseEvent<TenantRecord, undefined> {
public static token = 'tenants.create';
public get instance() {
return JsonTransformer.fromJSON(this.data, TenantRecord);
}
public static fromEvent(e: EventTenantsCreate) {
return new EventTenantsCreate(e.data, undefined, e.id, e.type, e.timestamp);
}
}
......@@ -6,3 +6,4 @@ export * from './logging/logAxiosError.js';
export * from './events/connectionEvents.js';
export * from './events/didEvents.js';
export * from './events/tenantEvents.js';
......@@ -26,6 +26,7 @@
"@aries-framework/core": "0.4.2",
"@aries-framework/indy-vdr": "0.4.2",
"@aries-framework/node": "0.4.2",
"@aries-framework/tenants": "^0.4.2",
"@elastic/ecs-winston-format": "^1.5.0",
"@hyperledger/anoncreds-nodejs": "^0.1.0",
"@hyperledger/aries-askar-nodejs": "^0.1.0",
......@@ -45,13 +46,13 @@
"winston": "^3.11.0"
},
"devDependencies": {
"@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.0.3",
"@nestjs/testing": "^10.2.10",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.9",
"@types/node": "^20.9.3",
"@types/supertest": "^2.0.16",
"@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.0.3",
"@nestjs/testing": "^10.2.10",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"eslint": "^8.54.0",
......
import { DidDocument } from '@aries-framework/core';
import { Test } from '@nestjs/testing';
import { mockConfigModule } from '../../config/__tests__/mockConfig.js';
import { AgentController } from '../agent.controller.js';
import { AgentService } from '../agent.service.js';
describe('AgentController', () => {
let agentController: AgentController;
let agentService: AgentService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [mockConfigModule()],
controllers: [AgentController],
providers: [AgentService],
}).compile();
agentService = moduleRef.get(AgentService);
agentController = moduleRef.get(AgentController);
});
describe('public did', () => {
it('should get the public did information of the agent', async () => {
const result = new DidDocument({ id: 'did:key:123' });
jest.spyOn(agentService, 'getPublicDid').mockResolvedValue(result);
const event = await agentController.publicDid();
expect(event.data).toMatchObject(result);
});
});
});
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { EventInfoPublicDid } from '@ocm/shared';
import { AgentService } from './agent.service.js';
@Controller('agent')
export class AgentController {
public constructor(private agent: AgentService) {}
@MessagePattern(EventInfoPublicDid.token)
public async publicDid() {
const didDocument = await this.agent.getPublicDid();
return new EventInfoPublicDid(didDocument);
}
}
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AgentController } from './agent.controller.js';
import { AgentService } from './agent.service.js';
import { WithTenantService } from './withTenantService.js';
@Module({
imports: [ConfigModule],
providers: [AgentService],
controllers: [AgentController],
exports: [AgentService],
providers: [AgentService, WithTenantService],
exports: [AgentService, WithTenantService],
})
export class AgentModule {}
......@@ -30,6 +30,7 @@ import {
IndyVdrSovDidResolver,
} from '@aries-framework/indy-vdr';
import { agentDependencies, HttpInboundTransport } from '@aries-framework/node';
import { TenantsModule } from '@aries-framework/tenants';
import { anoncreds } from '@hyperledger/anoncreds-nodejs';
import { ariesAskar } from '@hyperledger/aries-askar-nodejs';
import { indyVdr } from '@hyperledger/indy-vdr-nodejs';
......@@ -113,10 +114,16 @@ export class AgentService implements OnApplicationShutdown {
new JwkDidResolver(),
new WebDidResolver(),
],
registrars: [new PeerDidRegistrar(), new KeyDidRegistrar(), new JwkDidRegistrar()],
registrars: [
new PeerDidRegistrar(),
new KeyDidRegistrar(),
new JwkDidRegistrar(),
],
}),
askar: new AskarModule({ ariesAskar }),
tenants: new TenantsModule(),
};
}
......@@ -178,27 +185,6 @@ export class AgentService implements OnApplicationShutdown {
}
}
public async getPublicDid() {
const dids = await this.agent.dids.getCreatedDids({ method: 'indy' });
if (dids.length === 0) {
throw new Error('No registered public DIDs');
}
if (dids.length > 1) {
throw new Error('Multiple public DIDs found');
}
const didRecord = dids[0];
if (!didRecord.didDocument) {
throw new Error(
'A public DID was found, but did not include a DID Document',
);
}
return didRecord.didDocument;
}
public async onModuleInit() {
await this.agent.initialize();
await this.registerPublicDid();
......
......@@ -30,7 +30,9 @@ describe('ConnectionsController', () => {
const result: Array<ConnectionRecord> = [];
jest.spyOn(connectionsService, 'getAll').mockResolvedValue(result);
const connectionsEvent = await connectionsController.getAll();
const connectionsEvent = await connectionsController.getAll({
tenantId: 'some-id',
});
expect(connectionsEvent.data).toStrictEqual(result);
});
......@@ -43,6 +45,7 @@ describe('ConnectionsController', () => {
const connectionsEvent = await connectionsController.getById({
id: 'id',
tenantId: 'some-id',
});
expect(connectionsEvent.data).toStrictEqual(result);
......@@ -61,7 +64,9 @@ describe('ConnectionsController', () => {
.mockResolvedValue(result);
const connectionsEvent =
await connectionsController.createConnectionWithSelf();
await connectionsController.createConnectionWithSelf({
tenantId: 'some-id',
});
expect(connectionsEvent.data).toStrictEqual(result);
});
......
......@@ -5,6 +5,10 @@ import {
EventDidcommConnectionsGetAll,
EventDidcommConnectionsCreateWithSelf,
EventDidcommConnectionsBlock,
EventDidcommConnectionsGetAllInput,
EventDidcommConnectionsGetByIdInput,
EventDidcommConnectionsCreateWithSelfInput,
EventDidcommConnectionsBlockInput,
} from '@ocm/shared';
import { ConnectionsService } from './connections.service.js';
......@@ -14,38 +18,42 @@ export class ConnectionsController {
public constructor(private connectionsService: ConnectionsService) {}
@MessagePattern(EventDidcommConnectionsGetAll.token)
public async getAll(): Promise<EventDidcommConnectionsGetAll> {
public async getAll(
options: EventDidcommConnectionsGetAllInput,
): Promise<EventDidcommConnectionsGetAll> {
return new EventDidcommConnectionsGetAll(
await this.connectionsService.getAll(),
await this.connectionsService.getAll(options),
options.tenantId,
);
}
@MessagePattern(EventDidcommConnectionsGetById.token)
public async getById({
id,
}: {
id: string;
}): Promise<EventDidcommConnectionsGetById> {
public async getById(
options: EventDidcommConnectionsGetByIdInput,
): Promise<EventDidcommConnectionsGetById> {
return new EventDidcommConnectionsGetById(
await this.connectionsService.getById(id),
await this.connectionsService.getById(options),
options.tenantId,
);
}
@MessagePattern(EventDidcommConnectionsCreateWithSelf.token)
public async createConnectionWithSelf(): Promise<EventDidcommConnectionsCreateWithSelf> {
public async createConnectionWithSelf(
options: EventDidcommConnectionsCreateWithSelfInput,
): Promise<EventDidcommConnectionsCreateWithSelf> {
return new EventDidcommConnectionsCreateWithSelf(
await this.connectionsService.createConnectionWithSelf(),
await this.connectionsService.createConnectionWithSelf(options),
options.tenantId,
);
}
@MessagePattern(EventDidcommConnectionsBlock.token)
public async blockConnection({
idOrDid,
}: {
idOrDid: string;
}): Promise<EventDidcommConnectionsBlock> {
public async blockConnection(
options: EventDidcommConnectionsBlockInput,
): Promise<EventDidcommConnectionsBlock> {
return new EventDidcommConnectionsBlock(
await this.connectionsService.blockByIdOrDid(idOrDid),
await this.connectionsService.blockByIdOrDid(options),
options.tenantId,
);
}
}
......@@ -3,6 +3,12 @@ import type {
ConnectionRecord,
ConnectionStateChangedEvent,
} from '@aries-framework/core';
import type {
EventDidcommConnectionsBlockInput,
EventDidcommConnectionsCreateWithSelfInput,
EventDidcommConnectionsGetAllInput,
EventDidcommConnectionsGetByIdInput,
} from '@ocm/shared';
import {
ConnectionEventTypes,
......@@ -14,79 +20,100 @@ import { Injectable } from '@nestjs/common';
import { MetadataTokens } from '../../common/constants.js';
import { AgentService } from '../agent.service.js';
import { WithTenantService } from '../withTenantService.js';
@Injectable()
export class ConnectionsService {
public agent: AppAgent;
public withTenantService: WithTenantService;
public constructor(agentService: AgentService) {
public constructor(
agentService: AgentService,
withTenantService: WithTenantService,
) {
this.agent = agentService.agent;
this.withTenantService = withTenantService;
}
public async getAll(): Promise<Array<ConnectionRecord>> {
return await this.agent.connections.getAll();
public async getAll({
tenantId,
}: EventDidcommConnectionsGetAllInput): Promise<Array<ConnectionRecord>> {
return this.withTenantService.invoke(tenantId, (t) =>
t.connections.getAll(),
);
}
public async getById(id: string): Promise<ConnectionRecord | null> {
return await this.agent.connections.findById(id);
public async getById({
tenantId,
id,
}: EventDidcommConnectionsGetByIdInput): Promise<ConnectionRecord | null> {
return this.withTenantService.invoke(tenantId, (t) =>
t.connections.findById(id),
);
}
public async blockByIdOrDid(
idOrDid: string,
): Promise<ConnectionRecord | null> {
if (isDid(idOrDid)) {
const records = await this.agent.connections.findAllByQuery({
theirDid: idOrDid,
});
public async blockByIdOrDid({
tenantId,
idOrDid,
}: EventDidcommConnectionsBlockInput): Promise<ConnectionRecord | null> {
return this.withTenantService.invoke(tenantId, async (t) => {
if (isDid(idOrDid)) {
const records = await t.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',
);
}
if (records.length === 0) {
return null;
}
await t.connections.deleteById(records[0].id);
if (records.length > 1) {
throw new Error(
'Found multiple records with the same DID. This should not be possible',
);
return records[0];
}
await this.agent.connections.deleteById(records[0].id);
return records[0];
}
const record = await t.connections.findById(idOrDid);
if (!record) return null;
const record = await this.agent.connections.findById(idOrDid);
if (!record) return null;
await t.connections.deleteById(record.id);
await this.agent.connections.deleteById(record.id);
return record;
return record;
});
}
public async createConnectionWithSelf(): Promise<ConnectionRecord> {
const outOfBandRecord = await this.agent.oob.createInvitation();
const invitation = outOfBandRecord.outOfBandInvitation;
void this.agent.oob.receiveInvitation(invitation);
return new Promise((resolve) =>
this.agent.events.on<ConnectionStateChangedEvent>(
ConnectionEventTypes.ConnectionStateChanged,
async ({ payload: { connectionRecord } }) => {
if (connectionRecord.state !== DidExchangeState.Completed) return;
connectionRecord.metadata.set(
MetadataTokens.GAIA_X_CONNECTION_METADATA_KEY,
{
trusted: true,
},
);
const connRepo =
this.agent.dependencyManager.resolve(ConnectionRepository);
await connRepo.update(this.agent.context, connectionRecord);
resolve(connectionRecord);
},
),
);
public async createConnectionWithSelf({
tenantId,
}: EventDidcommConnectionsCreateWithSelfInput): Promise<ConnectionRecord> {
return this.withTenantService.invoke(tenantId, async (t) => {
const outOfBandRecord = await t.oob.createInvitation();
const invitation = outOfBandRecord.outOfBandInvitation;
void t.oob.receiveInvitation(invitation);
return new Promise((resolve) =>
this.agent.events.on<ConnectionStateChangedEvent>(
ConnectionEventTypes.ConnectionStateChanged,
async ({ payload: { connectionRecord } }) => {
if (connectionRecord.state !== DidExchangeState.Completed) return;
connectionRecord.metadata.set(
MetadataTokens.GAIA_X_CONNECTION_METADATA_KEY,
{
trusted: true,
},
);
const connRepo = t.dependencyManager.resolve(ConnectionRepository);
await connRepo.update(t.context, connectionRecord);
resolve(connectionRecord);
},
),
);
});
}
}
......@@ -28,6 +28,7 @@ describe('DidsController', () => {
const event = await didsController.resolve({
did: 'did:key:foo',
tenantId: 'some-id',
});
expect(event.data).toStrictEqual(result);
......
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { EventDidsResolve } from '@ocm/shared';
import {
EventDidsPublicDid,
EventDidsPublicDidInput,
EventDidsResolve,
EventDidsResolveInput,
} from '@ocm/shared';
import { DidsService } from './dids.service.js';
......@@ -8,8 +13,19 @@ import { DidsService } from './dids.service.js';
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));
@MessagePattern(EventDidsPublicDid.token)
public async publicDid(options: EventDidsPublicDidInput) {
return new EventDidsPublicDid(
await this.didsService.getPublicDid(options),
options.tenantId,
);
}
@MessagePattern(EventDidsResolve.token)
public async resolve(options: EventDidsResolveInput) {
return new EventDidsResolve(
await this.didsService.resolve(options),
options.tenantId,
);
}
}
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