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

Merge branch 'more-basic-events' into 'main'

feat(ssi): added public did and event

See merge request !6
parents 4ff432cb b198ae78
No related branches found
No related tags found
1 merge request!6feat(ssi): added public did and event
Showing
with 166 additions and 82 deletions
import { utils, type ConnectionRecord } from '@aries-framework/core';
import type { DidDocument, ConnectionRecord } from '@aries-framework/core';
import { utils } from '@aries-framework/core';
export class BaseEvent<
T extends Record<string, unknown> = Record<string, unknown>,
......@@ -16,6 +18,14 @@ export class BaseEvent<
}
}
export class EventInfoPublicDid extends BaseEvent<{
didDocument: DidDocument;
}> {}
export class EventDidcommConnectionsGetAll extends BaseEvent<{
connections: Array<ConnectionRecord>;
}> {}
export class EventDidcommConnectionsGetById extends BaseEvent<{
connection: ConnectionRecord | null;
}> {}
......@@ -51,7 +51,6 @@ $ pnpm install
[.env.example](.env.example)
- PORT is the port for the signing and verification interface
- AFJ_EXT_PORT is the port for the openapi documentation described in [swagger.json](swagger.json)
- AGENT_AUTO_ACCEPT_CONNECTION can be either true or false
- AGENT_AUTO_ACCEPT_CREDENTIAL can be either: always, contentApproved, never
- AGENT_PUBLIC_DID_SEED will generate the did and verkey (32 symbols)
......
......@@ -44,13 +44,12 @@ ssi-abstraction deployment
| security.runAsNonRoot | bool | `false` | by default, apps run as non-root |
| security.runAsUid | int | `0` | User used by the apps |
| service.port | int | `3009` | |
| ssiAbstraction.afjExtPort | int | `3010` | |
| ssiAbstraction.agent.autoAccept.connection | bool | `true` | |
| ssiAbstraction.agent.autoAccept.credential | bool | `true` | |
| ssiAbstraction.agent.host | string | `"gaiax.vereign.com"` | |
| ssiAbstraction.agent.ledgerId | string | `"ID_UNION"` | |
| ssiAbstraction.agent.name | string | `"ssi-abstraction-agent"` | |
| ssiAbstraction.agent.peerPort | int | `443` | |
| ssiAbstraction.agent.inboundPort | int | `443` | |
| ssiAbstraction.agent.protocol | string | `"http"` | |
| ssiAbstraction.agent.publicDidSeed | string | `"6b8b882e2618fa5d45ee7229ca880083"` | |
| ssiAbstraction.agent.urlPath | string | `"/ocm/didcomm"` | |
......
......@@ -38,8 +38,6 @@ spec:
env:
- name: PORT
value: {{ .Values.service.port | quote }}
- name: AFJ_EXT_PORT
value: {{ .Values.ssiAbstraction.afjExtPort | quote }}
- name: DATABASE_URL
value: {{ template "app.postgresql.connectionstring" (merge (dict "application" "true") .) }}
- name: NATS_URL
......@@ -52,8 +50,8 @@ spec:
value: {{ .Values.ssiAbstraction.agent.urlPath }}
- name: AGENT_NAME
value: {{ .Values.ssiAbstraction.agent.name }}
- name: AGENT_PEER_PORT
value: ":{{ .Values.ssiAbstraction.agent.peerPort }}"
- name: AGENT_INBOUND_PORT
value: ":{{ .Values.ssiAbstraction.agent.inboundPort }}"
- name: AGENT_PUBLIC_DID_SEED
value: {{ .Values.ssiAbstraction.agent.publicDidSeed | quote }}
- name: AGENT_AUTO_ACCEPT_CONNECTION
......@@ -80,8 +78,6 @@ spec:
{{- end }}
- name: http
containerPort: {{ .Values.service.port }}
- name: afj
containerPort: {{ .Values.ssiAbstraction.afjExtPort }}
- name: peer
containerPort: {{ .Values.ssiAbstraction.agent.peerPort }}
readinessProbe:
......
......@@ -25,5 +25,5 @@ spec:
service:
name: {{ template "app.name" . }}
port:
number: {{ .Values.ssiAbstraction.agent.peerPort }}
{{- end }}
\ No newline at end of file
number: {{ .Values.ssiAbstraction.agent.inboundPort }}
{{- end }}
......@@ -10,10 +10,7 @@ spec:
- name: http
port: { { .Values.service.port } }
targetPort: { { .Values.service.port } }
- name: afj
port: { { .Values.ssiAbstraction.afjExtPort } }
targetPort: { { .Values.ssiAbstraction.afjExtPort } }
- name: peer
port: { { .Values.ssiAbstraction.agent.peerPort } }
targetPort: { { .Values.ssiAbstraction.agent.peerPort } }
port: { { .Values.ssiAbstraction.agent.inboundPort } }
targetPort: { { .Values.ssiAbstraction.agent.inboundPort } }
selector: { { - include "app.selectorLabels" . | nindent 4 } }
......@@ -90,7 +90,7 @@ ssiAbstraction:
name: ssi-abstraction-agent
host: gaiax.vereign.com
protocol: http
peerPort: 443
inboundPort: 443
urlPath: /ocm/didcomm
publicDidSeed: 6b8b882e2618fa5d45ee7229ca880083
autoAccept:
......@@ -100,7 +100,6 @@ ssiAbstraction:
key: ssi-wallet-key
id: ssi-wallet-id
ledgerId: ID_UNION
afjExtPort: 3010
database:
host: postgresql.infra
user: root
......
import { DidDocument } from '@aries-framework/core';
import { Test } from '@nestjs/testing';
import { mockConfigModule } from '../../config/__tests__/mockConfig.js';
......@@ -10,7 +11,7 @@ describe('AgentController', () => {
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [mockConfigModule],
imports: [mockConfigModule()],
controllers: [AgentController],
providers: [AgentService],
}).compile();
......@@ -21,12 +22,11 @@ describe('AgentController', () => {
describe('public did', () => {
it('should get the public did information of the agent', async () => {
const result = { id: 'test' };
jest
.spyOn(agentService, 'getPublicDid')
.mockResolvedValue(result)
const result = new DidDocument({ id: 'did:key:123' });
jest.spyOn(agentService, 'getPublicDid').mockResolvedValue(result);
expect(await agentController.publicDid()).toBe(result);
const event = await agentController.publicDid();
expect(event.data).toMatchObject({ didDocument: result });
});
});
});
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { EventInfoPublicDid } from '@ocm/shared';
import { AgentService } from './agent.service.js';
......@@ -9,6 +10,8 @@ export class AgentController {
@MessagePattern('info.publicDid')
public async publicDid() {
return await this.agent.getPublicDid()
const didDocument = await this.agent.getPublicDid();
return new EventInfoPublicDid({ didDocument });
}
}
......@@ -49,7 +49,7 @@ export class AgentService {
public constructor(configService: ConfigService) {
this.configService = configService;
const peerPort = this.configService.get('agent.peerPort');
const inboundPort = this.configService.get('agent.inboundPort');
this.agent = new Agent({
config: this.config,
......@@ -58,7 +58,7 @@ export class AgentService {
});
const httpInbound = new HttpInboundTransport({
port: Number(peerPort.replace(':', '')),
port: inboundPort,
});
this.agent.registerInboundTransport(httpInbound);
......@@ -67,10 +67,10 @@ export class AgentService {
}
public get config(): InitConfig {
const { name, walletId, walletKey, host, peerPort, path } =
const { name, walletId, walletKey, host, inboundPort, path } =
this.configService.get('agent');
const endpoints = [`${host}${peerPort}${path}`];
const endpoints = [`${host}${inboundPort}${path}`];
return {
label: name,
......@@ -119,9 +119,7 @@ export class AgentService {
const ledgerIds = this.configService.get('agent.ledgerIds');
if (!ledgerIds || ledgerIds.length < 1 || ledgerIds[0] === '') {
throw new Error(
'Agent could not start, please provide a ledger environment variable.',
);
return [];
}
return ledgerIds.map((id: LedgerIds) => {
......@@ -148,12 +146,11 @@ export class AgentService {
if (!publicDidSeed) {
logger.info('No public did seed provided, skipping registration');
return;
}
if (!ledgerIds || ledgerIds.length < 1 || ledgerIds[0] === '') {
throw new Error(
'Agent could not start, please provide a ledger environment variable.',
);
return;
}
const registeredPublicDidResponses = await registerPublicDids({
......@@ -177,9 +174,24 @@ export class AgentService {
}
public async getPublicDid() {
return {
id: 'test',
};
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() {
......
......@@ -13,7 +13,7 @@ describe('ConnectionsController', () => {
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [mockConfigModule, AgentModule],
imports: [mockConfigModule(), AgentModule],
controllers: [ConnectionsController],
providers: [ConnectionsService],
}).compile();
......@@ -34,4 +34,19 @@ describe('ConnectionsController', () => {
expect(connectionsEvent.data).toStrictEqual({ connections: result });
});
});
describe('get by id', () => {
it('should get a connection record by id', async () => {
const result: ConnectionRecord | null = null;
jest
.spyOn(connectionsService, 'getById')
.mockImplementation(() => Promise.resolve(result));
const connectionsEvent = await connectionsController.getById({
id: 'id',
});
expect(connectionsEvent.data).toStrictEqual({ connection: result });
});
});
});
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { EventDidcommConnectionsGetAll } from '@ocm/shared';
import {
EventDidcommConnectionsGetById,
EventDidcommConnectionsGetAll,
} from '@ocm/shared';
import { ConnectionsService } from './connections.service.js';
......@@ -14,4 +17,15 @@ export class ConnectionsController {
connections: await this.connectionsService.getAll(),
});
}
@MessagePattern('didcomm.connections.getById')
public async getById({
id,
}: {
id: string;
}): Promise<EventDidcommConnectionsGetById> {
return new EventDidcommConnectionsGetById({
connection: await this.connectionsService.getById(id),
});
}
}
......@@ -16,4 +16,8 @@ export class ConnectionsService {
public async getAll(): Promise<Array<ConnectionRecord>> {
return await this.agent.connections.getAll();
}
public async getById(id: string): Promise<ConnectionRecord | null> {
return await this.agent.connections.findById(id);
}
}
......@@ -40,10 +40,11 @@ export const registerPublicDids = async ({
seed,
};
const res = await new axios.Axios().post<RegisterPublicDidResponse>(
ledgerRegisterUrl,
body,
);
const res = await axios({
method: 'post',
url: ledgerRegisterUrl,
data: body,
});
if (res.data) {
logger.info('Agent DID registered.');
......
......@@ -5,9 +5,9 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { validationSchema } from '../validation.js';
const mockConfig: AppConfig = {
const mockConfig = (port: number = 3001): AppConfig => ({
agentHost: '',
port: 3000,
port:3000,
jwtSecret: '',
nats: {
url: 'localhost',
......@@ -16,45 +16,48 @@ const mockConfig: AppConfig = {
name: 'my-test-agent',
walletId: 'some-id',
walletKey: 'some-key',
ledgerIds: ['BCOVRIN_TEST'],
ledgerIds: [],
host: '3000',
peerPort: '3001',
inboundPort: port,
path: '',
publicDidSeed: 'none',
publicDidSeed: '',
autoAcceptConnection: false,
autoAcceptCredential: AutoAcceptCredential.ContentApproved,
},
};
export const mockConfigModule = ConfigModule.forRoot({
load: [() => mockConfig],
validationSchema,
});
export const mockConfigModule = (port: number = 3000) =>
ConfigModule.forRoot({
load: [() => mockConfig(port)],
validationSchema,
});
describe('configuration', () => {
const mockedConfig = mockConfig();
describe('service', () => {
it('should be able to instantiate a config service', () => {
const configuration = new ConfigService(mockConfig);
const configuration = new ConfigService(mockConfig());
expect(configuration).toBeInstanceOf(ConfigService);
});
it('should be able to extract root value', () => {
const configuration = new ConfigService(mockConfig);
const configuration = new ConfigService(mockConfig());
expect(configuration.get('port')).toStrictEqual(mockConfig.port);
expect(configuration.get('port')).toStrictEqual(mockedConfig.port);
});
it('should be able to extract root value as object', () => {
const configuration = new ConfigService(mockConfig);
const configuration = new ConfigService(mockConfig());
expect(configuration.get('agent')).toMatchObject(mockConfig.agent);
expect(configuration.get('agent')).toMatchObject(mockedConfig.agent);
});
it('should be able to extract nested values', () => {
const configuration = new ConfigService(mockConfig);
const configuration = new ConfigService(mockConfig());
expect(configuration.get('agent.autoAcceptCredential')).toStrictEqual(
mockConfig.agent.autoAcceptCredential,
mockedConfig.agent.autoAcceptCredential,
);
});
});
......
......@@ -15,7 +15,7 @@ export interface AppConfig {
walletKey: string;
ledgerIds?: string[];
host: string;
peerPort: string;
inboundPort: number;
path: string;
publicDidSeed: string;
autoAcceptConnection: boolean;
......@@ -25,7 +25,7 @@ export interface AppConfig {
export const config = (): AppConfig => ({
agentHost: process.env.AGENT_HOST || '',
port: Number(process.env.PORT),
port: parseInt(process.env.PORT || '3000'),
jwtSecret: process.env.JWT_SECRET || '',
nats: {
......@@ -38,7 +38,7 @@ export const config = (): AppConfig => ({
walletKey: process.env.AGENT_WALLET_KEY || '',
ledgerIds: process.env.AGENT_LEDGER_ID?.split(','),
host: process.env.AGENT_HOST || '',
peerPort: process.env.AGENT_PEER_PORT || '',
inboundPort: parseInt(process.env.AGENT_INBOUND_PORT || '3001'),
path: process.env.AGENT_URL_PATH || '',
publicDidSeed: process.env.AGENT_PUBLIC_DID_SEED || '',
autoAcceptConnection: process.env.AGENT_AUTO_ACCEPT_CONNECTION === 'true',
......
......@@ -8,7 +8,7 @@ export const validationSchema = Joi.object({
AGENT_WALLET_ID: Joi.string().required(),
AGENT_WALLET_KEY: Joi.string().required(),
AGENT_HOST: Joi.string().required(),
AGENT_PEER_PORT: Joi.string(),
AGENT_INBOUND_PORT: Joi.string(),
AGENT_URL_PATH: Joi.string(),
AGENT_PUBLIC_DID_SEED: Joi.string().required(),
AGENT_AUTO_ACCEPT_CONNECTION: Joi.boolean().required(),
......
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type { EventInfoPublicDid } from '@ocm/shared';
import { DidDocument } from '@aries-framework/core';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import { firstValueFrom, type Observable } from 'rxjs';
......@@ -9,29 +11,39 @@ import { AgentModule } from '../src/agent/agent.module.js';
import { AgentService } from '../src/agent/agent.service.js';
import { mockConfigModule } from '../src/config/__tests__/mockConfig.js';
const mockDidDocument = {
'@context': ['https://w3id.org/did/v1'],
id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
verificationMethod: [
{
id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey',
type: 'Ed25519VerificationKey2018',
controller: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
publicKeyBase58: '4SySYXQUtuK26zfC7RpQpWYMThfbXphUf8LWyXXmxyTX',
},
],
authentication: ['did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey'],
};
describe('Agent', () => {
const TOKEN = 'AGENT_CLIENT_SERVICE';
let app: INestApplication;
let client: ClientProxy;
const agentService = {
getPublicDid: () =>
Promise.resolve({
id: 'test',
}),
};
beforeAll(async () => {
jest
.spyOn(AgentService.prototype, 'getPublicDid')
.mockImplementation(() =>
Promise.resolve(new DidDocument(mockDidDocument)),
);
const moduleRef = await Test.createTestingModule({
imports: [
mockConfigModule,
mockConfigModule(3000),
AgentModule,
ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]),
],
})
.overrideProvider(AgentService)
.useValue(agentService)
.compile();
}).compile();
app = moduleRef.createNestApplication();
......@@ -45,9 +57,16 @@ describe('Agent', () => {
});
it('info.publicDid', async () => {
const response$: Observable<unknown> = client.send('info.publicDid', {});
const response$: Observable<EventInfoPublicDid> = client.send(
'info.publicDid',
{},
);
const response = await firstValueFrom(response$);
expect(response).toMatchObject({ id: 'test' });
expect(response.data).toMatchObject({
didDocument: mockDidDocument,
});
});
afterAll(async () => {
......
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type { EventDidcommConnectionsGetAll } from '@ocm/shared';
import type {
EventDidcommConnectionsGetById,
EventDidcommConnectionsGetAll,
} from '@ocm/shared';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
......@@ -18,7 +21,7 @@ describe('Connections', () => {
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
mockConfigModule,
mockConfigModule(3004),
AgentModule,
ConnectionsModule,
ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]),
......@@ -45,6 +48,15 @@ describe('Connections', () => {
expect(response.data).toMatchObject({ connections: [] });
});
it('didcomm.connections.getById', async () => {
const response$: Observable<EventDidcommConnectionsGetById> = client.send(
'didcomm.connections.getById',
{ id: 'some-id' },
);
const response = await firstValueFrom(response$);
expect(response.data).toMatchObject({ connection: null });
});
afterAll(async () => {
await app.close();
client.close();
......
......@@ -17,6 +17,7 @@ describe('HealthController (e2e)', () => {
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
......
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