@5minds/processcube_engine
Version:
The ProcessCube Engine. Stores and executes BPMNs.
381 lines • 18.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EngineServer = void 0;
const compression_1 = __importDefault(require("compression"));
const cors_1 = __importDefault(require("cors"));
const express_1 = __importDefault(require("express"));
const helmet_1 = __importDefault(require("helmet"));
const http_1 = __importDefault(require("http"));
const inversify_1 = require("inversify");
const node_crypto_1 = require("node:crypto");
const socket_io_1 = require("socket.io");
const swagger_ui_dist_1 = require("swagger-ui-dist");
const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk");
const index_1 = require("./Contracts/index");
const Setups_1 = require("./Setups");
const FetchAndLock_1 = require("./Setups/FetchAndLock");
const Configurator_1 = __importDefault(require("./Tools/Configurator"));
const Environment_1 = require("./Tools/Environment");
const index_2 = require("./Tools/Http/index");
const index_3 = require("./Tools/Iam/index");
const MonitoringManager_1 = require("./Tools/MonitoringManager");
const corsOptions = {
methods: 'GET,HEAD,OPTIONS,PUT,POST,DELETE',
origin: function (origin, callback) {
const allowedCorsOrigins = Configurator_1.default.httpServer().allowedCorsOrigins;
if (allowedCorsOrigins.includes('*') || allowedCorsOrigins.includes(origin) || !origin) {
callback(null, true);
}
else {
callback(new Error('Not allowed by CORS'));
}
},
preflightContinue: false,
};
function setMaxSocketListenersMiddleware(request, response, next) {
request.setMaxListeners(5000);
request.socket.setMaxListeners(5000);
next();
}
function logIncomingRequestMiddleware(expressLogger) {
return (request, response, next) => {
expressLogger.trace(`${request.method.toUpperCase()} ${request.url}`, {
url: request.url,
headers: request.headers,
body: request.body,
method: request.method,
params: request.params,
query: request.query,
});
next();
};
}
function errorHandlerMiddleware() {
const errorHandlerLogger = new processcube_engine_sdk_1.Logger('engine:api:error_handler');
return (error, request, response, next) => {
const isFromEngine = (0, processcube_engine_sdk_1.isEngineError)(error);
const statusCode = isFromEngine ? error.code : processcube_engine_sdk_1.ErrorCodes.InternalServerError;
let responsePayload;
// Note that BpmnErrors cannot be transmitted across HTTP directly, because they usually won't use standard http status codes.
// Since express uses 500 as a fallback, if an invalid code is provided, the original BPMN error code would be lost.
// To get around this problem, we attach the BpmnError to the "additionalInformation" of the serialized error. The client can then retrieve and decode the error.
if (error instanceof processcube_engine_sdk_1.BpmnError) {
responsePayload = JSON.stringify({
message: error.message,
additionalInformation: {
bpmnError: {
...error,
code: error.code,
message: error.message,
name: error.name,
},
},
});
}
else {
responsePayload = (0, processcube_engine_sdk_1.serializeJson)(error);
}
errorHandlerLogger.error(`Intercepted an error with status code \`${statusCode}\`: `, {
err: error,
requestHeader: request.header,
requestMethod: request.method,
requestUrl: request.url,
requestBody: request.body,
});
response.status(statusCode).send(responsePayload);
};
}
let EngineServer = class EngineServer {
app;
httpServer;
config;
_socketServer;
applicationRouter;
fetchAndLockRouter;
queryRouter;
runtimeRouter;
iamService;
container;
expressLogger;
constructor(applicationRouter, runtimeRouter, queryRouter, fetchAndLockRouter, container, iamService) {
this.applicationRouter = applicationRouter;
this.fetchAndLockRouter = fetchAndLockRouter;
this.queryRouter = queryRouter;
this.runtimeRouter = runtimeRouter;
this.iamService = iamService;
this.container = container;
this.expressLogger = new processcube_engine_sdk_1.Logger('express');
}
get socketIoServer() {
return this._socketServer;
}
getHttpAddress() {
return this.httpServer.address();
}
async initialize() {
this.config = Configurator_1.default.httpServer();
this.app = (0, express_1.default)();
this.app.set('view engine', 'ejs');
this.app.set('views', process.env.appRootDir);
this.app.set('trust proxy', true);
// This notation comes from an external module, which we have no control over.
this.httpServer = http_1.default.Server(this.app);
await this.initializeServer();
await this.initializeMiddlewareBeforeRouters();
await this.initializeRouter();
await this.initializeMiddlewareAfterRouters();
this.initializeService();
await this.start();
}
async start() {
return new Promise(async (resolve) => {
this.httpServer.listen(this.config?.port ?? 8000, this.config?.host ?? '0.0.0.0', () => resolve());
});
}
async close() {
await this.closeHttpEndpoints();
await this.dispose();
this.expressLogger.flush();
}
async dispose() {
this.expressLogger.trace('Disposing services from ioc container...');
await this.disposeServices();
this.expressLogger.trace('Disposing runtime worker...');
await this.runtimeRouter.dispose();
if (!(0, Environment_1.isWindowsAndSQLite)()) {
this.expressLogger.trace('Disposing query worker...');
await this.queryRouter.dispose();
this.expressLogger.trace('Disposing fetchAndLock worker...');
await this.fetchAndLockRouter.dispose();
}
try {
this.expressLogger.trace('Disposing main thread services...');
const discoveredDisposableBinding = this.container.getAll(index_1.disposableDiscoveryTag);
for (const disposableBinding of discoveredDisposableBinding) {
await disposableBinding.dispose();
}
}
catch (error) {
// Occurs, if nothing disposable is stored in the container, in which case there's nothing to do here.
}
}
async resumeProcessInstances() {
return this.runtimeRouter.resumeProcessInstances();
}
initializeService() {
this.container.get(index_1.IocRegistrationKeys.api.services.ApplicationInfoService).init();
}
initializeServer() {
this.app.use(index_2.apiMonitoringMiddleware);
this.app.use(logIncomingRequestMiddleware(this.expressLogger));
this._socketServer = new socket_io_1.Server(this.httpServer, {
allowEIO3: true,
cors: corsOptions,
});
const options = {
limit: '250mb',
verify: (req, res, buf) => {
req.rawBody = buf.toString();
},
};
this.app.use(express_1.default.json(options));
}
initializeMiddlewareBeforeRouters() {
this.app.use(setMaxSocketListenersMiddleware);
this.app.use((0, compression_1.default)());
const urlEncodedOptions = {
limit: '250mb',
extended: true,
};
this.app.use(express_1.default.urlencoded(urlEncodedOptions));
this.app.use((req, res, next) => {
res.setHeader('Permissions-Policy', 'accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), clipboard-read=(), clipboard-write=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials=(), screen-wake-lock=(), sync-xhr=(), usb=(), vr=(), xr=()');
const scriptAndStyleSrcDirectives = ["'self'"];
if (req.accepts('html')) {
const nonce = (0, node_crypto_1.randomBytes)(16).toString('base64');
res.locals.nonce = nonce;
scriptAndStyleSrcDirectives.push(`'nonce-${nonce}'`);
}
(0, helmet_1.default)({
xssFilter: true,
frameguard: true,
noSniff: true,
hidePoweredBy: true,
strictTransportSecurity: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: scriptAndStyleSrcDirectives,
styleSrc: scriptAndStyleSrcDirectives,
imgSrc: ["'self'", 'data:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'none'"],
frameSrc: ["'none'"],
workerSrc: ["'none'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
manifestSrc: ["'self'"],
},
},
ieNoOpen: true,
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: true,
originAgentCluster: true,
referrerPolicy: true,
xPermittedCrossDomainPolicies: true,
})(req, res, next);
});
this.app.use((0, cors_1.default)(corsOptions));
this.app.use((req, res, next) => {
if (req.method === 'TRACE' || req.method === 'TRACK' || req.method === 'CONNECT') {
res.status(405).send('Method Not Allowed');
}
next();
});
}
initializeMiddlewareAfterRouters() {
this.app.use(errorHandlerMiddleware());
this.app.use(express_1.default.static((0, swagger_ui_dist_1.absolutePath)(), { immutable: true, maxAge: '7d', lastModified: false }));
}
async initializeRouter() {
let runtimeChildProcess;
let queryChildProcess;
let fetchAndLockChildProcess;
await this.applicationRouter.init();
await this.runtimeRouter.init();
runtimeChildProcess = this.runtimeRouter.getChildProcess();
if (!(0, Environment_1.isWindowsAndSQLite)()) {
await this.queryRouter.init();
queryChildProcess = this.queryRouter.getChildProcess();
await this.fetchAndLockRouter.init();
fetchAndLockChildProcess = this.fetchAndLockRouter.getChildProcess();
}
const monitoringManager = this.container.get(index_1.IocRegistrationKeys.internal.MonitoringManager);
monitoringManager.init(runtimeChildProcess, queryChildProcess, fetchAndLockChildProcess);
const identityService = this.container.get(index_1.IocRegistrationKeys.internal.IdentityService);
if (!MonitoringManager_1.MonitoringManager.disableAuth()) {
this.app.use(`/${index_1.restSettings.baseRoute}/v1/metrics`, (0, index_2.createResolveIdentityMiddleware)(identityService), this.runtimeRouter.extensionRouter);
}
this.app.use('/', this.applicationRouter.expressRouter);
this.app.use('/', this.runtimeRouter.extensionRouter);
this.app.use(`/${index_1.restSettings.baseRoute}`, (0, index_2.createResolveIdentityMiddleware)(identityService), this.runtimeRouter.engineApiRouter);
if (!(0, Environment_1.isWindowsAndSQLite)()) {
this.app.use(`/${index_1.restSettings.baseRoute}`, (0, index_2.createResolveIdentityMiddleware)(identityService), this.queryRouter.engineApiRouter);
this.app.use(`/${index_1.restSettings.baseRoute}`, (0, index_2.createResolveIdentityMiddleware)(identityService), this.fetchAndLockRouter.engineApiRouter);
}
await this.initSocketIo();
}
async initSocketIo() {
const sockets = [];
const identityService = this.container.get(index_1.IocRegistrationKeys.internal.IdentityService);
function publishNotificationToSockets(socketPath, message) {
sockets.forEach((socket) => socket.emit(socketPath, message));
}
this.runtimeRouter.onNotificationPublished((socketPath, message) => publishNotificationToSockets(socketPath, message));
this.queryRouter.onNotificationPublished((socketPath, message) => publishNotificationToSockets(socketPath, message));
this.fetchAndLockRouter.onNotificationPublished((socketPath, message) => publishNotificationToSockets(socketPath, message));
const namespace = this.socketIoServer.of(index_1.socketSettings.namespace);
namespace.on('connect', async (socket) => {
const token = socket.handshake.headers.authorization;
const userId = socket.handshake.headers.userId;
const identityNotSet = token === undefined;
if (identityNotSet) {
this.expressLogger.warn('A Socket.IO client attempted to connect without providing an Auth-Token!');
socket.disconnect();
throw new processcube_engine_sdk_1.UnauthorizedError('No auth token provided!');
}
const identity = await identityService.getIdentity({ token: token, userId: userId });
try {
await this.ensureHasClaim(identity, processcube_engine_sdk_1.claims.canSubscribeToEvents);
}
catch (error) {
this.expressLogger.warn('A Socket.IO client attempted to connect without providing the correct claim!', {
claimsProvided: identity?.claims,
claimsNeeded: [processcube_engine_sdk_1.claims.canSubscribeToEvents],
});
return;
}
this.expressLogger.debug(`Client with socket ID \`${socket.id}\` and user ID \`${identity.userId}\` connected.`);
sockets.push(socket);
socket.on('disconnect', () => {
const index = sockets.findIndex((currentSock) => currentSock === socket);
if (index > -1) {
sockets.splice(index, 1);
}
this.expressLogger.debug(`Client with socket ID \`${socket.id}\` and user ID \`${identity.userId}\` disconnected`);
});
});
}
async closeSockets() {
this.expressLogger.trace('Closing sockets...');
for (const socket of this.socketIoServer.of('/').sockets.values()) {
socket.disconnect();
}
}
async closeHttpEndpoints() {
this.expressLogger.trace('Closing http endpoints...');
this.socketIoServer.close();
this.httpServer.close();
await this.closeSockets();
}
async disposeServices() {
try {
const discoveredDisposableBinding = this.container.getAll(index_1.disposableDiscoveryTag);
for (const disposableBinding of discoveredDisposableBinding) {
if (typeof disposableBinding.dispose === 'function') {
await disposableBinding.dispose();
}
}
}
catch (error) {
// Occurs, if nothing disposable is stored in the container, in which case there's nothing to do here.
}
}
async ensureHasClaim(identity, claimName) {
const isAdminOrObserver = this.iamService.checkIfUserIsSuperAdmin(identity) || this.iamService.checkIfUserIsObserver(identity);
if (isAdminOrObserver) {
return;
}
this.iamService.ensureHasClaim(identity, claimName);
}
};
exports.EngineServer = EngineServer;
exports.EngineServer = EngineServer = __decorate([
(0, inversify_1.injectable)(),
__param(0, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.ApplicationRouter)),
__param(1, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.RuntimeRouter)),
__param(2, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.QueryRouter)),
__param(3, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.FetchAndLockRouter)),
__param(4, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.Container)),
__param(5, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.IamService)),
__metadata("design:paramtypes", [Setups_1.ApplicationRouter,
Setups_1.RuntimeRouter,
Setups_1.QueryRouter,
FetchAndLock_1.FetchAndLockRouter,
inversify_1.Container,
index_3.IamService])
], EngineServer);
//# sourceMappingURL=EngineServer.js.map