Skip to content
Snippets Groups Projects
Commit 85192304 authored by Alexey Lunin's avatar Alexey Lunin Committed by Zdravko Iliev
Browse files

feat: enhanced server functionality

parent 2f059c81
Branches
Tags
1 merge request!48feat: enhanced server functionality
Pipeline #67830 waiting for manual action
Showing
with 4899 additions and 766 deletions
......@@ -35,4 +35,5 @@ ATTESTATION_SERVICE_HOST=0.0.0.0
PROOF_SERVICE_TCP_PORT=8884
PROOF_SERVICE_HOST=0.0.0.0
ALLOWED_ORIGINS=*
SWAGGER=false
This diff is collapsed.
......@@ -12,6 +12,12 @@ import * as fs from "fs";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const origins = (process.env.ALLOWED_ORIGINS || "").split(",");
app.enableCors({
origin: origins.length > 1 ? origins : origins[0] || "",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
});
const globalPrefix = "api";
app.setGlobalPrefix(globalPrefix);
const port = process.env.AGENT_PORT || 3001;
......@@ -25,7 +31,7 @@ async function bootstrap() {
.build();
const document = SwaggerModule.createDocument(app, config);
fs.writeFileSync("./agent-swagger.json", JSON.stringify(document));
fs.writeFileSync("./agent-swagger.json", JSON.stringify(document, null, 2));
SwaggerModule.setup("api", app, document);
Logger.log(`Swagger file written`);
return process.kill(0);
......
......@@ -4,11 +4,11 @@ import { MessagePattern, RpcException } from "@nestjs/microservices";
import {
BasicMessageEvent,
CloudEventDto,
CreateCredentialDefinitionRequsetDto,
CreateCredentialDefinitionRequestDto,
CreateSchemaRequestDto,
CredentialEvent,
GatewayAcceptedResponseDto,
IssueCredentialRequestDto,
OfferCredentialRequestDto,
MakeBasicMessageRequestDto,
makeEvent,
SchemaEvent,
......@@ -60,8 +60,8 @@ export class AppController {
payload: {
data:
| null
| CreateCredentialDefinitionRequsetDto
| IssueCredentialRequestDto;
| CreateCredentialDefinitionRequestDto
| OfferCredentialRequestDto;
type: CredentialEvent;
source: string;
},
......@@ -91,7 +91,7 @@ export class AppController {
}
@MessagePattern("messages")
async sendMeesage(
async sendMessage(
@Body()
payload: {
data: MakeBasicMessageRequestDto;
......
......@@ -7,8 +7,8 @@ import {
ConnectionEvent,
CreateInvitationResponseDto,
GatewayAcceptedResponseDto,
GetConnectionRequestDto,
makeEvent,
IdReqDto,
} from "@ocm-engine/dtos";
@Controller()
......@@ -21,7 +21,7 @@ export class AppController {
async create(
@Body()
payload: {
data: null | CreateInvitationResponseDto | GetConnectionRequestDto;
data: null | CreateInvitationResponseDto | IdReqDto;
type: ConnectionEvent;
source: string;
},
......
import { ApiExtraModels } from "@nestjs/swagger";
import * as dtos from "@ocm-engine/dtos";
import { applyDecorators } from "@nestjs/common";
const IncludeOcmDtos = () => {
// Filter and get only the classes from the dtos object
// eslint-disable-next-line @typescript-eslint/ban-types
const dtoClasses: Function[] = Object.values(dtos).filter(
(dto) => typeof dto === "function",
// eslint-disable-next-line @typescript-eslint/ban-types
) as Function[];
return applyDecorators(ApiExtraModels(...dtoClasses));
};
export default IncludeOcmDtos;
import { ApiResponse } from "@nestjs/swagger";
import { applyDecorators } from "@nestjs/common";
import { GatewayAcceptedResponseDto } from "@ocm-engine/dtos";
const OcmGatewayResponse = () => {
return applyDecorators(
ApiResponse({
status: 201,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
}),
);
};
export default OcmGatewayResponse;
import { ApiInternalServerErrorResponse } from "@nestjs/swagger";
import { applyDecorators } from "@nestjs/common";
const OcmInternalServerErrorResponse = (name: string) => {
return applyDecorators(
ApiInternalServerErrorResponse({
description: `Error in sending data to ${name}. This error shows that ${name} could not convert request to event or ${name} could not send the event to the broker.`,
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0:1234",
},
},
},
},
},
}),
);
};
export default OcmInternalServerErrorResponse;
This diff is collapsed.
......@@ -2,6 +2,7 @@ import {
BadRequestException,
Body,
Controller,
Delete,
Get,
Param,
Post,
......@@ -11,65 +12,39 @@ import { ConnectionManagerClient } from "@ocm-engine/clients";
import {
CONNECTION_ACCEPT,
CONNECTION_CREATE,
CONNECTION_DELETE,
CONNECTION_GET,
CONNECTION_LIST,
CreateInvitationResponseDto,
GatewayAcceptedResponseDto,
GetConnectionRequestDto,
IdReqDto,
} from "@ocm-engine/dtos";
import { AllExceptionsHandler } from "../exception.handler";
import {
ApiBadRequestResponse,
ApiInternalServerErrorResponse,
ApiOperation,
ApiResponse,
} from "@nestjs/swagger";
import { ApiBadRequestResponse, ApiOperation } from "@nestjs/swagger";
import IncludeOcmDtos from "../decorators/IncludeOcmDtos";
import OcmGatewayResponse from "../decorators/OcmGatewayResponse";
import OcmInternalServerErrorResponse from "../decorators/OcmInternalServerErrorResponse";
@UseFilters(AllExceptionsHandler)
@Controller("v1")
@IncludeOcmDtos()
export class ConnectionController {
constructor(private readonly cmClient: ConnectionManagerClient) {}
@Post("/invitations")
@ApiResponse({
status: 201,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to connection manager. This error shows that connection manager could not convert request to event or connection manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:4312",
},
},
},
},
},
})
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("connection manager")
@ApiOperation({
summary: "Create invitation for connection",
description:
"Method will create invitation url. The id of the response will be matched when you receive event from the websocket",
tags: ["Connections"],
})
createInvitation(): Promise<GatewayAcceptedResponseDto> {
async createInvitation(): Promise<GatewayAcceptedResponseDto> {
return this.cmClient.sendPayload<null>({
pattern: "connections",
payload: {
source: "/invitation",
source: "/invitations",
data: null,
type: CONNECTION_CREATE,
},
......@@ -77,12 +52,8 @@ export class ConnectionController {
}
@Post("/invitations/accept")
@ApiResponse({
status: 201,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
})
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("connection manager")
@ApiBadRequestResponse({
description: "Validation error",
content: {
......@@ -111,28 +82,6 @@ export class ConnectionController {
},
},
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to connection manager. This error shows that connection manager could not convert request to event or connection manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:4312",
},
},
},
},
},
})
@ApiOperation({
summary: "Accept invitation long and short urls for connection",
description:
......@@ -141,7 +90,7 @@ export class ConnectionController {
})
async acceptInvitation(
@Body() createInvitationDto: CreateInvitationResponseDto,
) {
): Promise<GatewayAcceptedResponseDto> {
try {
return this.cmClient.sendPayload<CreateInvitationResponseDto>({
pattern: "connections",
......@@ -157,41 +106,15 @@ export class ConnectionController {
}
@Get("/connections")
@ApiResponse({
status: 200,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to connection manager. This error shows that connection manager could not convert request to event or connection manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:4312",
},
},
},
},
},
})
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("connection manager")
@ApiOperation({
summary: "List all connections",
description:
"The id of the response will be matched when you receive event from the websocket",
tags: ["Connections"],
})
async list() {
async fetchConnections(): Promise<GatewayAcceptedResponseDto> {
return this.cmClient.sendPayload<null>({
pattern: "connections",
payload: {
......@@ -203,50 +126,51 @@ export class ConnectionController {
}
@Get("/connections/:id")
@ApiResponse({
status: 200,
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("connection manager")
@ApiOperation({
summary: "Get connection by id",
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
"The method will search for connection id, if not found null will be returned. The id of the response will be matched when you receive event from the websocket",
tags: ["Connections"],
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to connection manager. This error shows that connection manager could not convert request to event or connection manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
async getConnectionById(
@Param("id") id: string,
): Promise<GatewayAcceptedResponseDto> {
const request = new IdReqDto();
request.id = id;
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:4312",
},
},
},
return this.cmClient.sendPayload<IdReqDto>({
pattern: "connections",
payload: {
source: "/connections/:id",
data: request,
type: CONNECTION_GET,
},
},
})
});
}
@Delete("/connections/:id")
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("connection manager")
@ApiOperation({
summary: "Get connection by id",
summary: "Delete connection by id",
description:
"The method will search for connection id, if not found null will be returned. The id of the response will be matched when you receive event from the websocket",
"The method will attempt to delete a connection by its ID. The id of the response will be matched when you receive an event from the websocket.",
tags: ["Connections"],
})
async getById(@Param("id") id: string) {
const request = new GetConnectionRequestDto();
request.connectionId = id;
async deleteConnectionById(
@Param("id") id: string,
): Promise<GatewayAcceptedResponseDto> {
const request = new IdReqDto();
request.id = id;
return this.cmClient.sendPayload<GetConnectionRequestDto>({
return this.cmClient.sendPayload<IdReqDto>({
pattern: "connections",
payload: {
source: "/connections/:id",
data: request,
type: CONNECTION_GET,
type: CONNECTION_DELETE,
},
});
}
......
import { Body, Controller, Get, Param, Post, UseFilters } from "@nestjs/common";
import {
AcceptProofRequestDto,
DeclineProofRequestDto,
Body,
Controller,
Get,
Param,
Post,
Delete,
UseFilters,
Query,
} from "@nestjs/common";
import {
AcceptProofDto,
GatewayAcceptedResponseDto,
GetProofRequestDto,
GetSchemaRequestDto,
IssueProofRequestDto,
RequestProofDto,
ProofFilterDto,
PROOF_ACCEPT,
PROOF_DECLINE,
PROOF_GET,
PROOF_ISSUE,
PROOF_REQUEST,
PROOF_LIST,
PROOF_DELETE,
IdReqDto,
} from "@ocm-engine/dtos";
import { AllExceptionsHandler } from "../exception.handler";
import { ProofManagerClient } from "@ocm-engine/clients";
import {
ApiBadRequestResponse,
ApiInternalServerErrorResponse,
ApiOperation,
ApiResponse,
} from "@nestjs/swagger";
import { ApiBadRequestResponse, ApiOperation } from "@nestjs/swagger";
import IncludeOcmDtos from "../decorators/IncludeOcmDtos";
import OcmGatewayResponse from "../decorators/OcmGatewayResponse";
import OcmInternalServerErrorResponse from "../decorators/OcmInternalServerErrorResponse";
@UseFilters(AllExceptionsHandler)
@Controller("v1")
@IncludeOcmDtos()
export class ProofController {
constructor(private readonly pmClient: ProofManagerClient) {}
@Get("/credentials/proof")
@ApiResponse({
status: 200,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to proof manager. This error shows that proof manager could not convert request to event or proof manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:1919",
},
},
},
},
},
})
@Get("/proofs")
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("proof manager")
@ApiOperation({
summary: "List received unaccepted proofs",
description:
"Method list all received unaccepted proofs. Status - request-receive. The id of the response will be matched when you receive event from the websocket",
"This method list proofs based on provided filters. The id of the response will be matched when you receive event from the websocket",
tags: ["Credentials Proof"],
})
proofs() {
return this.pmClient.sendPayload<GetSchemaRequestDto>({
async fetchProofs(
@Query() proofFilterDto: ProofFilterDto,
): Promise<GatewayAcceptedResponseDto> {
return this.pmClient.sendPayload({
pattern: "proofs",
payload: {
source: "/credential/proofs",
data: null,
source: "/proofs",
data: proofFilterDto,
type: PROOF_LIST,
},
});
}
@Get("/credentials/proof/:proof_record_id")
@ApiResponse({
status: 200,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to proof manager. This error shows that proof manager could not convert request to event or proof manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:1919",
},
},
},
},
},
})
@Get("/proofs/:proof_record_id")
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("proof manager")
@ApiOperation({
summary: "Get a single proof record by providing proof record id.",
description:
"Method get proof by id. Status - request-receive. The id of the response will be matched when you receive event from the websocket",
tags: ["Credentials Proof"],
})
getProofById(@Param("proof_record_id") proofRecordId: string) {
const data = new GetProofRequestDto();
data.proofRecordId = proofRecordId;
async getProofById(
@Param("proof_record_id") proofRecordId: string,
): Promise<GatewayAcceptedResponseDto> {
const data = new IdReqDto();
data.id = proofRecordId;
return this.pmClient.sendPayload<GetProofRequestDto>({
return this.pmClient.sendPayload<IdReqDto>({
pattern: "proofs",
payload: {
source: "/credentials/proof/:proof_record_id",
source: "/credentials/proofs/:proof_record_id",
data,
type: PROOF_GET,
},
});
}
@Post("/credentials/proof/issue")
@ApiResponse({
status: 201,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
})
@Post("/proofs/request")
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("proof manager")
@ApiBadRequestResponse({
description: "Validation error",
content: {
......@@ -156,136 +112,93 @@ export class ProofController {
},
},
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to proof manager. This error shows that proof manager could not convert request to event or proof manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:1919",
},
},
},
},
},
})
@ApiOperation({
summary: "Issue proof for credential",
summary: "Request proof for credential",
description:
"Method will issue proof. If connection id is not passed, the proof will be OOB. The id of the response will be matched when you receive event from the websocket",
"Method will request proof. If connection id is not passed, the proof will be OOB. The id of the response will be matched when you receive event from the websocket",
tags: ["Credentials Proof"],
})
issueProof(@Body() issueProofDto: IssueProofRequestDto) {
return this.pmClient.sendPayload<IssueProofRequestDto>({
async requestProof(
@Body() requestProofDto: RequestProofDto,
): Promise<GatewayAcceptedResponseDto> {
return this.pmClient.sendPayload<RequestProofDto>({
pattern: "proofs",
payload: {
source: "/credentials/proof/issue",
data: issueProofDto,
type: PROOF_ISSUE,
source: "/proofs/request",
data: requestProofDto,
type: PROOF_REQUEST,
},
});
}
@Post(`/credentials/proof/accept`)
@ApiResponse({
status: 201,
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to proof manager. This error shows that proof manager could not convert request to event or proof manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:1919",
},
},
},
},
},
})
@Post(`/proofs/accept`)
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("proof manager")
@ApiOperation({
summary: "Accept credential proof",
description:
"Method accept credential proof. The id of the response will be matched when you receive event from the websocket",
tags: ["Credentials Proof"],
})
acceptProof(@Body() acceptProofRequestDto: AcceptProofRequestDto) {
return this.pmClient.sendPayload<AcceptProofRequestDto>({
async acceptProof(
@Body() acceptProofRequestDto: AcceptProofDto,
): Promise<GatewayAcceptedResponseDto> {
return this.pmClient.sendPayload<AcceptProofDto>({
pattern: "proofs",
payload: {
source: "/credentials/proofs/accept",
source: "/proofs/accept",
data: acceptProofRequestDto,
type: PROOF_ACCEPT,
},
});
}
@Post("/credentials/proof/:proof_record_id/decline")
@ApiResponse({
status: 200,
@Post("/proofs/:proof_record_id/decline")
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("proof manager")
@ApiOperation({
summary: "Decline a proof request.",
description:
"Request is accepted for execution, the response id will match the event id received from the web socket",
type: GatewayAcceptedResponseDto,
"Method to decline a proof request by id. Status - request-receive. The id of the response will be matched when you receive event from the websocket",
tags: ["Credentials Proof"],
})
@ApiInternalServerErrorResponse({
description:
"Error in sending data to proof manager. This error shows that proof manager could not convert request to event or proof manager could not send the event to the broker.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
statusCode: {
type: "number",
example: 500,
},
async declineProofRequest(
@Param("proof_record_id") proofRecordId: string,
): Promise<GatewayAcceptedResponseDto> {
const data = new IdReqDto();
data.id = proofRecordId;
message: {
type: "string",
example: "connect ECONNREFUSED 0.0.0.0.0:1919",
},
},
},
return this.pmClient.sendPayload<IdReqDto>({
pattern: "proofs",
payload: {
source: "/proofs/:proof_record_id/decline",
data,
type: PROOF_DECLINE,
},
},
})
});
}
@Delete("/proofs/:proof_record_id")
@OcmGatewayResponse()
@OcmInternalServerErrorResponse("proof manager")
@ApiOperation({
summary: "Decline a proof request.",
summary: "Delete a proof record by its ID",
description:
"Method to decline a proof request by id. Status - request-receive. The id of the response will be matched when you receive event from the websocket",
"This method deletes a specific proof based on the provided ID. The ID of the response will be matched when you receive an event from the websocket",
tags: ["Credentials Proof"],
})
declineProofRequest(@Param("proof_record_id") proofRecordId: string) {
const data = new DeclineProofRequestDto();
data.proofRecordId = proofRecordId;
async deleteProofById(
@Param("proof_record_id") proofRecordId: string,
): Promise<GatewayAcceptedResponseDto> {
const data = new IdReqDto();
data.id = proofRecordId;
return this.pmClient.sendPayload<DeclineProofRequestDto>({
return this.pmClient.sendPayload<IdReqDto>({
pattern: "proofs",
payload: {
source: "/credentials/proof/:proof_record_id/decline",
source: "/proofs/:proof_record_id",
data,
type: PROOF_DECLINE,
type: PROOF_DELETE,
},
});
}
......
......@@ -17,6 +17,12 @@ import * as fs from "fs";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const origins = (process.env.ALLOWED_ORIGINS || "").split(",");
app.enableCors({
origin: origins.length > 1 ? origins : origins[0] || "",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
});
app.useWebSocketAdapter(new WsAdapter(app));
const configService = app.get(ConfigService);
......@@ -47,7 +53,10 @@ async function bootstrap() {
.build();
const document = SwaggerModule.createDocument(app, config);
fs.writeFileSync("./gateway-swagger.json", JSON.stringify(document));
fs.writeFileSync(
"./gateway-swagger.json",
JSON.stringify(document, null, 2),
);
SwaggerModule.setup("api", app, document);
Logger.log(`Swagger file written`);
......
......@@ -3,7 +3,8 @@ import { Body, Controller, Logger } from "@nestjs/common";
import { ProducerService } from "@ocm-engine/nats";
import { MessagePattern, RpcException } from "@nestjs/microservices";
import {
AcceptProofRequestDto,
AcceptProofDto,
IdReqDto,
CloudEventDto,
GatewayAcceptedResponseDto,
makeEvent,
......@@ -20,7 +21,7 @@ export class AppController {
async create(
@Body()
payload: {
data: null | AcceptProofRequestDto;
data: null | AcceptProofDto | IdReqDto;
type: ProofEvent;
source: string;
},
......
......@@ -14,6 +14,9 @@ AGENT_IS_REST=false
AGENT_MAX_MESSAGES=10
AGENT_RETE_LIMIT=5
AGENT_PEER_PORT=6001
NATS_SUBJECTS="connections.,proofs.,credentials.,credentials.definition.,credentials.offer.,schemas.,messages.*"
NATS_SERVERS=broker-holder:4222
NATS_STREAM_NAME=ssi_holder_stream
NATS_SUBJECTS="connections.*,proofs.*,credentials.*,schemas.*,messages.*"
......
This diff is collapsed.
......@@ -23,6 +23,10 @@ import {
WalletError,
WalletKeyExistsError,
OutOfBandState,
CredentialStateChangedEvent,
CredentialEventTypes,
CredentialState,
ProofExchangeRecord,
} from "@aries-framework/core";
import {
AnonCredsCredentialFormatService,
......@@ -50,7 +54,7 @@ import {
lastValueFrom,
map,
Observable,
ReplaySubject,
BehaviorSubject,
Subject,
take,
timeout,
......@@ -58,7 +62,7 @@ import {
import { SubjectInboundTransport } from "./askar/transports/agent.subject.inbound.transport";
import { SubjectOutboundTransport } from "./askar/transports/agent.subject.outbound.transport";
export type EventReplaySubject = ReplaySubject<BaseEvent>;
export type EventBehaviorSubject = BehaviorSubject<BaseEvent>;
export type SubjectMessage = {
message: EncryptedMessage;
replySubject?: Subject<SubjectMessage>;
......@@ -149,9 +153,13 @@ export const getAskarAnonCredsIndyModules = (networks: any) => {
}),
anoncreds: new AnonCredsModule({
registries: [new IndyVdrAnonCredsRegistry()],
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
autoCreateLinkSecret: true,
}),
anoncredsRs: new AnonCredsRsModule({
anoncreds,
autoCreateLinkSecret: true,
}),
indyVdr: new IndyVdrModule({
indyVdr,
......@@ -171,23 +179,31 @@ export const getAskarAnonCredsIndyModules = (networks: any) => {
} as const;
};
export const setupEventReplaySubjects = (
const INITIAL_EVENT: BaseEvent = {
type: "unknown",
payload: {},
metadata: {
contextCorrelationId: "-1",
},
};
export const setupEventBehaviorSubjects = (
agents: Agent[],
eventTypes: string[],
): ReplaySubject<BaseEvent>[] => {
const replaySubjects: EventReplaySubject[] = [];
): BehaviorSubject<BaseEvent>[] => {
const behaviorSubjects: EventBehaviorSubject[] = [];
for (const agent of agents) {
const replaySubject = new ReplaySubject<BaseEvent>();
const behaviorSubject = new BehaviorSubject<BaseEvent>(INITIAL_EVENT);
for (const eventType of eventTypes) {
agent.events.observable(eventType).subscribe(replaySubject);
agent.events.observable(eventType).subscribe(behaviorSubject);
}
replaySubjects.push(replaySubject);
behaviorSubjects.push(behaviorSubject);
}
return replaySubjects;
return behaviorSubjects;
};
export const setupSubjectTransports = (agents: Agent[]) => {
......@@ -326,7 +342,7 @@ export const svdxConnectionStateChangeHandler = async (
});
} catch (e) {
console.log(JSON.stringify(e, null, 2));
console.log("failed to issue credential");
console.log("failed to offer credential");
}
};
......@@ -334,9 +350,15 @@ export const isProofStateChangedEvent = (
e: BaseEvent,
): e is ProofStateChangedEvent => e.type === ProofEventTypes.ProofStateChanged;
export const isCredentialStateChangedEvent = (
e: BaseEvent,
): e is CredentialStateChangedEvent =>
e.type === CredentialEventTypes.CredentialStateChanged;
export const waitForProofExchangeRecordSubject = (
subject: ReplaySubject<BaseEvent> | Observable<BaseEvent>,
subject: BehaviorSubject<BaseEvent> | Observable<BaseEvent>,
{
proofRecordId,
threadId,
parentThreadId,
state,
......@@ -344,6 +366,7 @@ export const waitForProofExchangeRecordSubject = (
timeoutMs = 10000,
count = 1,
}: {
proofRecordId?: string;
threadId?: string;
parentThreadId?: string;
state?: ProofState;
......@@ -351,33 +374,82 @@ export const waitForProofExchangeRecordSubject = (
timeoutMs?: number;
count?: number;
},
) => {
): Promise<ProofExchangeRecord> => {
const observable: Observable<BaseEvent> =
subject instanceof ReplaySubject ? subject.asObservable() : subject;
subject instanceof BehaviorSubject ? subject.asObservable() : subject;
return lastValueFrom(
observable.pipe(
filter(isProofStateChangedEvent),
filter(
(e) =>
previousState === undefined ||
e.payload.previousState === previousState,
),
filter(
(e) =>
threadId === undefined || e.payload.proofRecord.threadId === threadId,
(proofRecordId === undefined ||
e.payload.proofRecord.id === proofRecordId) &&
(previousState === undefined ||
e.payload.previousState === previousState) &&
(threadId === undefined ||
e.payload.proofRecord.threadId === threadId) &&
(parentThreadId === undefined ||
e.payload.proofRecord.parentThreadId === parentThreadId) &&
(state === undefined || e.payload.proofRecord.state === state),
),
timeout(timeoutMs),
catchError(() => {
throw new Error(
`ProofStateChangedEvent event not emitted within specified timeout: ${timeoutMs}
previousState: ${previousState},
threadId: ${threadId},
parentThreadId: ${parentThreadId},
state: ${state}
}`,
);
}),
take(count),
map((e) => e.payload.proofRecord),
),
);
};
export const waitForCredentialExchangeRecordSubject = (
subject: BehaviorSubject<BaseEvent> | Observable<BaseEvent>,
{
credentialRecordId,
threadId,
parentThreadId,
state,
previousState,
timeoutMs = 10000,
count = 1,
}: {
credentialRecordId?: string;
threadId?: string;
parentThreadId?: string;
state?: CredentialState;
previousState?: CredentialState | null;
timeoutMs?: number;
count?: number;
},
) => {
const observable: Observable<BaseEvent> =
subject instanceof BehaviorSubject ? subject.asObservable() : subject;
return lastValueFrom(
observable.pipe(
filter(isCredentialStateChangedEvent),
filter(
(e) =>
parentThreadId === undefined ||
e.payload.proofRecord.parentThreadId === parentThreadId,
),
filter(
(e) => state === undefined || e.payload.proofRecord.state === state,
(credentialRecordId === undefined ||
e.payload.credentialRecord.id === credentialRecordId) &&
(previousState === undefined ||
e.payload.previousState === previousState) &&
(threadId === undefined ||
e.payload.credentialRecord.threadId === threadId) &&
(parentThreadId === undefined ||
e.payload.credentialRecord.parentThreadId === parentThreadId) &&
(state === undefined || e.payload.credentialRecord.state === state),
),
timeout(timeoutMs),
catchError(() => {
throw new Error(
`ProofStateChangedEvent event not emitted within specified timeout: ${timeoutMs}
`CredentialStateChanged event not emitted within specified timeout: ${timeoutMs}
previousState: ${previousState},
threadId: ${threadId},
parentThreadId: ${parentThreadId},
......@@ -386,7 +458,7 @@ export const waitForProofExchangeRecordSubject = (
);
}),
take(count),
map((e) => e.payload.proofRecord),
map((e) => e.payload.credentialRecord),
),
);
};
......
import { Injectable, Logger } from "@nestjs/common";
import { AgentService } from "../askar/agent.service";
import {
AcceptCredentialOfferRequestDto,
AcceptProofRequestDto,
AcceptProofDto,
CloudEventDto,
CONNECTION_ACCEPT,
CONNECTION_CREATE,
CONNECTION_GET,
CONNECTION_LIST,
CreateCredentialDefinitionRequsetDto,
CONNECTION_DELETE,
CreateCredentialDefinitionRequestDto,
CreateInvitationResponseDto,
CreateSchemaRequestDto,
CredentialFilterDto,
CRED_DEF_CREATE,
CRED_ISSUE,
CRED_DEF_LIST,
CRED_DEF_GET,
CRED_SEND_OFFER,
CRED_LIST,
CRED_GET,
CRED_OFFER_ACCEPT,
CRED_OFFER_LIST,
GetConnectionRequestDto,
GetSchemaRequestDto,
IssueCredentialRequestDto,
IssueProofRequestDto,
MakeBasicMessageRequestDto,
DeclineProofRequestDto,
MESSAGE_MAKE,
CRED_OFFER_DECLINE,
CRED_DELETE,
OfferCredentialRequestDto,
RequestProofDto,
ProofFilterDto,
PROOF_ACCEPT,
PROOF_ISSUE,
PROOF_DECLINE,
PROOF_REQUEST,
PROOF_LIST,
PROOF_GET,
PROOF_DELETE,
SCHEMA_CREATE,
SCHEMA_GET,
SCHEMA_LIST,
GetProofRequestDto,
PROOF_DECLINE,
MakeBasicMessageRequestDto,
MessageFilterDto,
MESSAGE_MAKE,
MESSAGE_LIST,
MESSAGE_DELETE,
IdReqDto,
AcceptCredentialDto,
} from "@ocm-engine/dtos";
import asyncRetry from "async-retry";
@Injectable()
export class EventHandlerService {
......@@ -42,109 +49,141 @@ export class EventHandlerService {
constructor(private readonly agentService: AgentService) {}
async handle<T>(event: CloudEventDto<T>) {
let data;
let data: unknown;
let dto;
await asyncRetry(
async () => {
switch (event.type) {
case CONNECTION_CREATE:
data = await this.agentService.createInvitation();
break;
case CONNECTION_ACCEPT:
dto = event.data as CreateInvitationResponseDto;
data = await this.agentService.acceptInvitation(dto.invitationUrl);
break;
case CONNECTION_LIST:
data = await this.agentService.fetchConnections();
break;
case CONNECTION_GET:
dto = event.data as GetConnectionRequestDto;
data = await this.agentService.getConnectionById(dto.connectionId);
break;
case SCHEMA_CREATE:
dto = event.data as CreateSchemaRequestDto;
data = await this.agentService.createSchema(dto);
break;
case SCHEMA_LIST:
data = await this.agentService.fetchSchemas();
break;
case SCHEMA_GET:
dto = event.data as GetSchemaRequestDto;
data = await this.agentService.getSchemaById(dto.schemaId);
break;
case CRED_DEF_CREATE:
data = await this.agentService.createCredentialDefinition(
event.data as CreateCredentialDefinitionRequsetDto,
);
break;
case CRED_ISSUE:
data = await this.agentService.issueCredential(
event.data as IssueCredentialRequestDto,
);
break;
case CRED_LIST:
data = await this.agentService.credentials();
break;
case CRED_OFFER_LIST:
data = await this.agentService.credentialByStatedOfferReceived();
break;
case CRED_OFFER_ACCEPT:
dto = event.data as AcceptCredentialOfferRequestDto;
data = await this.agentService.acceptCredential(
dto.credentialRecordId,
);
break;
case PROOF_ISSUE:
dto = event.data as IssueProofRequestDto;
data = await this.agentService.issueProof(dto);
break;
case PROOF_LIST:
data = await this.agentService.proofs();
break;
case PROOF_GET:
dto = event.data as GetProofRequestDto;
data = await this.agentService.getProofById(dto.proofRecordId);
break;
case PROOF_ACCEPT:
data = await this.agentService.acceptProof(
event.data as AcceptProofRequestDto,
);
break;
case PROOF_DECLINE:
dto = event.data as DeclineProofRequestDto;
data = await this.agentService.declineProofRequest(
dto.proofRecordId,
);
break;
case MESSAGE_MAKE:
dto = event.data as MakeBasicMessageRequestDto;
data = await this.agentService.sendMessage(dto);
break;
}
},
{
retries: 5,
onRetry: (error: unknown) => {
this.logger.log(JSON.stringify(error, null, 2));
},
},
);
event.data = data;
switch (event.type) {
case CONNECTION_CREATE:
data = await this.agentService.createInvitation();
break;
case CONNECTION_ACCEPT:
dto = event.data as CreateInvitationResponseDto;
data = await this.agentService.acceptInvitation(dto.invitationUrl);
break;
case CONNECTION_LIST:
data = await this.agentService.fetchConnections();
break;
case CONNECTION_GET:
dto = event.data as IdReqDto;
data = await this.agentService.getConnectionById(dto.id);
break;
case CONNECTION_DELETE:
dto = event.data as IdReqDto;
data = await this.agentService.deleteConnectionById(dto.id);
break;
case SCHEMA_CREATE:
dto = event.data as CreateSchemaRequestDto;
data = await this.agentService.createSchema(dto);
break;
case SCHEMA_LIST:
data = await this.agentService.fetchSchemas();
break;
case SCHEMA_GET:
dto = event.data as IdReqDto;
data = await this.agentService.getSchemaById(dto.id);
break;
case CRED_DEF_CREATE:
data = await this.agentService.createCredentialDefinition(
event.data as CreateCredentialDefinitionRequestDto,
);
break;
case CRED_DEF_LIST:
data = await this.agentService.fetchCredentialDefinitions();
break;
case CRED_DEF_GET:
dto = event.data as IdReqDto;
data = await this.agentService.getCredentialDefinitionById(dto.id);
break;
case CRED_SEND_OFFER:
data = await this.agentService.offerCredential(
event.data as OfferCredentialRequestDto,
);
break;
case CRED_LIST:
dto = event.data as CredentialFilterDto;
data = await this.agentService.fetchCredentials(dto);
break;
case CRED_GET:
dto = event.data as IdReqDto;
data = await this.agentService.getCredentialById(dto.id);
break;
case CRED_OFFER_ACCEPT:
data = await this.agentService.acceptCredential(
event.data as AcceptCredentialDto,
);
break;
case CRED_OFFER_DECLINE:
dto = event.data as IdReqDto;
data = await this.agentService.declineCredential(dto.id);
break;
case CRED_DELETE:
dto = event.data as IdReqDto;
data = await this.agentService.deleteCredentialById(dto.id);
break;
case PROOF_REQUEST:
dto = event.data as RequestProofDto;
data = await this.agentService.requestProof(dto);
break;
case PROOF_LIST:
dto = event.data as ProofFilterDto;
data = await this.agentService.fetchProofs(dto);
break;
case PROOF_GET:
dto = event.data as IdReqDto;
data = await this.agentService.getProofById(dto.id);
break;
case PROOF_ACCEPT:
data = await this.agentService.acceptProof(
event.data as AcceptProofDto,
);
break;
case PROOF_DECLINE:
dto = event.data as IdReqDto;
data = await this.agentService.declineProofRequest(dto.id);
break;
case PROOF_DELETE:
dto = event.data as IdReqDto;
data = await this.agentService.deleteProofById(dto.id);
break;
case MESSAGE_MAKE:
dto = event.data as MakeBasicMessageRequestDto;
data = await this.agentService.sendMessage(dto);
break;
case MESSAGE_LIST:
dto = event.data as MessageFilterDto;
data = await this.agentService.fetchBasicMessages(dto);
break;
case MESSAGE_DELETE:
dto = event.data as IdReqDto;
data = await this.agentService.deleteMessageById(dto.id);
break;
}
event.data = data as T;
return event;
}
}
......@@ -6,6 +6,11 @@ import {
HttpStatus,
BadRequestException,
} from "@nestjs/common";
import {
AriesFrameworkError,
RecordNotFoundError,
} from "@aries-framework/core";
import { EntityNotFoundError } from "@ocm-engine/dtos";
@Catch()
export class AllExceptionsHandler implements ExceptionFilter {
......@@ -31,6 +36,25 @@ export class AllExceptionsHandler implements ExceptionFilter {
return;
}
if (
exception instanceof EntityNotFoundError ||
exception instanceof RecordNotFoundError
) {
response.status(HttpStatus.NOT_FOUND).json({
statusCode: HttpStatus.NOT_FOUND,
message: exception.message,
});
return;
}
if (exception instanceof AriesFrameworkError) {
response.status(HttpStatus.BAD_REQUEST).json({
statusCode: HttpStatus.BAD_REQUEST,
message: exception.message,
});
return;
}
response.status(status).json({
statusCode: status,
message,
......
import { Body, Controller, Get, Param, Post, UseFilters } from "@nestjs/common";
import {
Body,
Query,
Controller,
Delete,
Get,
Param,
Post,
UseFilters,
} from "@nestjs/common";
import { AgentService } from "../askar/agent.service";
import {
CreateCredentialDefinitionRequsetDto,
CreateCredentialDefinitionRequestDto,
CreateInvitationResponseDto,
IssueCredentialRequestDto,
IssueProofRequestDto,
OfferCredentialRequestDto,
RequestProofDto,
CreateSchemaRequestDto,
MakeBasicMessageRequestDto,
AcceptProofRequestDto,
GetProofRequestDto,
AcceptProofDto,
IdReqDto,
CredentialFilterDto,
MessageFilterDto,
ProofFilterDto,
AcceptCredentialDto,
} from "@ocm-engine/dtos";
import { AllExceptionsHandler } from "./exception.handler";
import { DidResolutionResult } from "@aries-framework/core";
@UseFilters(AllExceptionsHandler)
@Controller("v1")
export class RestController {
constructor(private readonly agentService: AgentService) {}
@Post("/invitation")
createInvitation() {
@Post("/invitations")
async createInvitation() {
return this.agentService.createInvitation();
}
@Post("/invitation/accept")
acceptInvitation(@Body() createInvitationDto: CreateInvitationResponseDto) {
@Post("/invitations/accept")
async acceptInvitation(
@Body() createInvitationDto: CreateInvitationResponseDto,
) {
return this.agentService.acceptInvitation(
createInvitationDto.invitationUrl,
);
}
@Get("connections")
connections() {
@Get("/connections")
async fetchConnections() {
return this.agentService.fetchConnections();
}
@Post("schema")
createSchema(@Body() schemaDto: CreateSchemaRequestDto) {
@Get("/connections/:id")
async getConnectionById(@Param("id") id: string) {
return this.agentService.getConnectionById(id);
}
@Delete("/connections/:id")
async deleteConnectionById(@Param("id") id: string) {
return this.agentService.deleteConnectionById(id);
}
@Post("/schemas")
async createSchema(@Body() schemaDto: CreateSchemaRequestDto) {
return this.agentService.createSchema(schemaDto);
}
@Post("credential/definition")
createCredentialDefinition(
@Body() credentialDefinitionDto: CreateCredentialDefinitionRequsetDto,
@Post("/schemas/get-by-id")
async getSchemaById(@Body() dto: IdReqDto) {
return this.agentService.getSchemaById(dto.id);
}
@Get("/schemas")
async fetchSchemas() {
return this.agentService.fetchSchemas();
}
@Get("/definitions")
async fetchCredentialDefinitions() {
return this.agentService.fetchCredentialDefinitions();
}
@Post("/definitions/get-by-id")
async getCredentialDefinitionById(@Body() dto: IdReqDto) {
return this.agentService.getCredentialDefinitionById(dto.id);
}
@Post("/definitions")
async createCredentialDefinition(
@Body() credentialDefinitionDto: CreateCredentialDefinitionRequestDto,
) {
return this.agentService.createCredentialDefinition(
credentialDefinitionDto,
);
}
@Post("credential/issue")
issueCredential(@Body() issueCredentialDto: IssueCredentialRequestDto) {
return this.agentService.issueCredential(issueCredentialDto);
@Post("/credentials/offers")
async offerCredential(@Body() dto: OfferCredentialRequestDto) {
return this.agentService.offerCredential(dto);
}
@Get("/credentials")
credentials() {
return this.agentService.credentials();
async fetchCredentials(@Query() credentialFilterDto: CredentialFilterDto) {
return this.agentService.fetchCredentials(credentialFilterDto);
}
@Get("/credentials/:id")
async getCredentialById(@Param("id") credentialId: string) {
return this.agentService.getCredentialById(credentialId);
}
@Get("/credentials/:id/format-data")
async getCredentialFormatDataById(@Param("id") credentialId: string) {
return this.agentService.getCredentialFormatDataById(credentialId);
}
@Get("/credential/offers")
getCredentialOffers() {
return this.agentService.credentialByStatedOfferReceived();
@Post("/credentials/offers/accept")
async acceptCredential(@Body() dto: AcceptCredentialDto) {
return this.agentService.acceptCredential(dto);
}
@Post("/credential/:credential_record_id/accept")
acceptCredential(@Param("credential_record_id") credentialRecordId: string) {
return this.agentService.acceptCredential(credentialRecordId);
@Post("/credentials/offers/:credential_record_id/decline")
async declineCredential(
@Param("credential_record_id") credentialRecordId: string,
) {
return this.agentService.declineCredential(credentialRecordId);
}
@Delete("/credentials/:id")
async deleteCredentialById(@Param("id") credentialId: string) {
return this.agentService.deleteCredentialById(credentialId);
}
@Post("/messages")
async sendMessage(@Body() message: MakeBasicMessageRequestDto) {
return this.agentService.sendMessage(message);
}
@Get("/messages")
async fetchBasicMessages(@Query() filter: MessageFilterDto) {
return this.agentService.fetchBasicMessages(filter);
}
@Get("/credential/proof")
proofs() {
return this.agentService.proofs();
@Delete("/messages/:id")
async deleteBasicMessage(@Param("id") messageId: string) {
return this.agentService.deleteMessageById(messageId);
}
@Post("credential/proof/issue")
issueProof(@Body() issueProofDto: IssueProofRequestDto) {
return this.agentService.issueProof(issueProofDto);
@Get("/proofs")
async fetchProofs(@Query() proofFilterDto: ProofFilterDto) {
return this.agentService.fetchProofs(proofFilterDto);
}
@Get("credential/proof/:proof_record_id")
getProof(@Param("proof_record_id") data: GetProofRequestDto) {
return this.agentService.getProofById(data.proofRecordId);
@Get("/proofs/:proof_record_id")
async getProofById(@Param("proof_record_id") proofRecordId: string) {
return this.agentService.getProofById(proofRecordId);
}
@Post(`/credential/proof/accept`)
acceptProof(@Body() acceptProofRequestDto: AcceptProofRequestDto) {
@Get("/proofs/:proof_record_id/format-data")
async getProofFormatDataById(
@Param("proof_record_id") proofRecordId: string,
) {
return this.agentService.getProofFormatDataById(proofRecordId);
}
@Post("/proofs/:proof_record_id/acceptance-wait")
async proofAcceptanceWait(@Param("proof_record_id") proofRecordId: string) {
return this.agentService.proofAcceptanceWait(proofRecordId);
}
@Post("/proofs/request")
async requestProof(@Body() requestProofDto: RequestProofDto) {
return this.agentService.requestProof(requestProofDto);
}
@Post(`/proofs/accept`)
async acceptProof(@Body() acceptProofRequestDto: AcceptProofDto) {
return this.agentService.acceptProof(acceptProofRequestDto);
}
@Post(`/credential/proof/:proof_record_id/decline`)
declineProofRequest(@Param("proof_record_id") proofRecordId: string) {
@Post("/proofs/:proof_record_id/decline")
async declineProofRequest(@Param("proof_record_id") proofRecordId: string) {
return this.agentService.declineProofRequest(proofRecordId);
}
@Post("/resolve")
resolve(@Body("did") did: string) {
return this.agentService.resolve(did);
@Delete("/proofs/:proof_record_id")
async deleteProofById(@Param("proof_record_id") proofRecordId: string) {
return this.agentService.deleteProofById(proofRecordId);
}
@Post("/messages")
sendMeesage(@Body() message: MakeBasicMessageRequestDto) {
return this.agentService.sendMessage(message);
@Post("/resolve-did")
async resolveDid(@Body() dto: IdReqDto): Promise<DidResolutionResult> {
return this.agentService.resolve(dto.id);
}
}
......@@ -10,11 +10,7 @@ import {
ProofEventTypes,
ProofStateChangedEvent,
} from "@aries-framework/core";
import {
MakeBasicMessageResponseDto,
makeEvent,
MESSAGE_MAKE,
} from "@ocm-engine/dtos";
import { MessageRecordDto, makeEvent, MESSAGE_MAKE } from "@ocm-engine/dtos";
import { IConfAgent } from "@ocm-engine/config";
import { ConfigService } from "@nestjs/config";
import {
......@@ -72,15 +68,24 @@ export class AgentEventListenerService implements OnModuleInit {
if (ev.payload.basicMessageRecord.role === BasicMessageRole.Receiver) {
this.logger.debug(JSON.stringify(ev, null, 2));
const messageRecord = ev.payload.basicMessageRecord;
const connectionInfo = await this.askar.agent.connections.findById(
ev.payload.basicMessageRecord.connectionId,
messageRecord.connectionId,
);
const label = connectionInfo?.theirLabel || "";
const dto = new MessageRecordDto();
const dto = new MakeBasicMessageResponseDto();
dto.message = ev.payload.basicMessageRecord.content;
dto.id = ev.payload.basicMessageRecord.id;
dto.connectionId = ev.payload.basicMessageRecord.connectionId;
dto.from = connectionInfo?.theirLabel;
dto.id = messageRecord.id;
dto.createdAt = messageRecord.createdAt;
dto.updatedAt = messageRecord.updatedAt;
dto.connectionId = messageRecord.connectionId;
dto.role = messageRecord.role;
dto.content = messageRecord.content;
dto.sentTime = messageRecord.sentTime;
dto.from =
messageRecord.role === BasicMessageRole.Receiver ? label : "";
dto.to = messageRecord.role === BasicMessageRole.Sender ? label : "";
if (this.agentConfig?.agentIsRest) {
this.logger.debug(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment