UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

381 lines • 18.3 kB
"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