UNPKG

@frangoteam/fuxa

Version:

Web-based Process Visualization (SCADA/HMI/Dashboard) software

302 lines (273 loc) 13.2 kB
/** * 'api/project': API server initialization and general GET/POST */ const fs = require('fs'); var express = require('express'); var morgan = require('morgan'); var bodyParser = require('body-parser'); const authJwt = require('./jwt-helper'); const rateLimit = require("express-rate-limit"); var prjApi = require('./projects'); var authApi = require('./auth'); var usersApi = require('./users'); var apiKeysApi = require('./apikeys'); var alarmsApi = require('./alarms'); var pluginsApi = require('./plugins'); var diagnoseApi = require('./diagnose'); var scriptsApi = require('./scripts'); var resourcesApi = require('./resources'); var daqApi = require('./daq'); var schedulerApi = require('./scheduler'); var commandApi = require('./command'); const reports = require('../dist/reports.service'); const reportsApi = new reports.ReportsApiService(); const verifyApiOrToken = require('./apikeys/verify-api-or-token'); const utils = require('../runtime/utils'); const version = '1.0.0'; var apiApp; var server; var runtime; function init(_server, _runtime) { server = _server; runtime = _runtime; return new Promise(function (resolve, reject) { if (runtime.settings.disableServer !== false) { apiApp = express(); if (runtime.settings.logApiLevel !== 'none') { apiApp.use(morgan(['combined', 'common', 'dev', 'short', 'tiny']. includes(runtime.settings.logApiLevel) ? runtime.settings.logApiLevel : 'combined')); } var maxApiRequestSize = runtime.settings.apiMaxLength || '100mb'; apiApp.use(bodyParser.json({limit:maxApiRequestSize})); apiApp.use(bodyParser.urlencoded({limit:maxApiRequestSize, extended: true})); authJwt.init(runtime.settings.secureEnabled, runtime.settings.secretCode, runtime.settings.tokenExpiresIn); const authMiddleware = verifyApiOrToken(runtime); prjApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(prjApi.app()); usersApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(usersApi.app()); alarmsApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(alarmsApi.app()); authApi.init(runtime, authJwt.secretCode, authJwt.tokenExpiresIn, runtime.settings.enableRefreshCookieAuth, runtime.settings.refreshTokenExpiresIn); apiApp.use(authApi.app()); pluginsApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(pluginsApi.app()); diagnoseApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(diagnoseApi.app()); daqApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(daqApi.app()); schedulerApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(schedulerApi.app()); scriptsApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(scriptsApi.app()); resourcesApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(resourcesApi.app()); commandApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(commandApi.app()); reportsApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(reportsApi.app()); apiKeysApi.init(runtime, authMiddleware, verifyGroups); apiApp.use(apiKeysApi.app()); const limiter = rateLimit({ windowMs: 5 * 60 * 1000, // 5 minutes max: 100, // limit each IP to 100 requests per windowMs // Keep lightweight health/version checks unthrottled skip: (req) => req.path === '/api/version' }); // apply to all requests apiApp.use(limiter); apiApp.use((err, req, res, next) => { if (err?.type === 'entity.too.large') { return res.status(413).json({ message: `The submitted content exceeds the maximum allowed size (${maxApiRequestSize})` }); } next(err); }); /** * GET Server setting data */ apiApp.get('/api/version', function (req, res) { res.json(version); }); /** * GET Server setting data */ apiApp.get('/api/settings', function (req, res) { if (runtime.settings) { let tosend = JSON.parse(JSON.stringify(runtime.settings)); delete tosend.secretCode; if (tosend.smtp) { delete tosend.smtp.password; } if (tosend.daqstore?.credentials) { delete tosend.daqstore.credentials; } // res.header("Access-Control-Allow-Origin", "*"); // res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.json(tosend); } else { res.status(404).end(); runtime.logger.error('api get settings: Value Not Found!'); } }); /** * POST Server user settings */ apiApp.post("/api/settings", authMiddleware, function(req, res, next) { const permission = verifyGroups(req); if (res.statusCode === 403) { runtime.logger.error("api post settings: Tocken Expired"); } else if (!authJwt.haveAdminPermission(permission)) { res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"}); runtime.logger.error("api post settings: Unauthorized"); } else { try { if (req.body.smtp && !req.body.smtp.password && runtime.settings.smtp && runtime.settings.smtp.password) { req.body.smtp.password = runtime.settings.smtp.password; } if (utils.isEmptyObject(req.body.daqstore?.credentials) && runtime.settings.daqstore?.credentials) { req.body.daqstore.credentials = runtime.settings.daqstore?.credentials; } if (!req.body.secretCode && runtime.settings.secretCode) { req.body.secretCode = runtime.settings.secretCode; } if (req.body.secureEnabled && !req.body.secretCode) { req.body.secretCode = utils.generateSecretCode(); runtime.logger.warn('Generated random JWT secret because secureEnabled=true and no secretCode was provided.'); } const prevAuth = { secureEnabled: runtime.settings.secureEnabled, tokenExpiresIn: runtime.settings.tokenExpiresIn, enableRefreshCookieAuth: runtime.settings.enableRefreshCookieAuth, refreshTokenExpiresIn: runtime.settings.refreshTokenExpiresIn, secretCode: runtime.settings.secretCode }; if (req.body.nodeRedEnabled === true && utils.isNullOrUndefined(req.body.nodeRedAuthMode) && runtime.settings.nodeRedEnabled === false) { req.body.nodeRedAuthMode = 'secure'; } fs.writeFileSync(runtime.settings.userSettingsFile, JSON.stringify(req.body, null, 4)); mergeUserSettings(req.body); if (prevAuth.secureEnabled !== runtime.settings.secureEnabled || prevAuth.tokenExpiresIn !== runtime.settings.tokenExpiresIn || prevAuth.enableRefreshCookieAuth !== runtime.settings.enableRefreshCookieAuth || prevAuth.refreshTokenExpiresIn !== runtime.settings.refreshTokenExpiresIn || prevAuth.secretCode !== runtime.settings.secretCode) { authJwt.init(runtime.settings.secureEnabled, runtime.settings.secretCode, runtime.settings.tokenExpiresIn); authApi.init(runtime, authJwt.secretCode, authJwt.tokenExpiresIn, runtime.settings.enableRefreshCookieAuth, runtime.settings.refreshTokenExpiresIn); } runtime.restart(true).then(function(result) { res.end(); }); } catch (err) { res.status(400).json({ error: "unexpected_error", message: err }); runtime.logger.error("api post settings: " + err); } } }); /** * GET Heartbeat to check token */ apiApp.post('/api/heartbeat', authMiddleware, function (req, res) { if (!runtime.settings.secureEnabled) { return res.end(); } if (req.body.params) { if (!req.isAuthenticated) { // guest → NON puo rinnovare token return res.status(200).json({ message: 'guest' }); } const token = authJwt.getNewTokenFromRequest(req); return res.status(200).json({ message: 'tokenRefresh', token }); } // Guest heartbeat if (req.userId === 'guest') { return res.status(200).json({ message: 'guest', token: authJwt.getGuestToken() }); } return res.end(); }); runtime.logger.info('api: init successful!', true); } else { } resolve(); }); } function mergeUserSettings(settings) { if (settings.language) { runtime.settings.language = settings.language; } runtime.settings.broadcastAll = settings.broadcastAll; runtime.settings.secureEnabled = settings.secureEnabled; runtime.settings.logFull = settings.logFull; runtime.settings.userRole = settings.userRole; runtime.settings.nodeRedEnabled = settings.nodeRedEnabled; if (!utils.isNullOrUndefined(settings.nodeRedAuthMode)) { runtime.settings.nodeRedAuthMode = settings.nodeRedAuthMode; } if (!utils.isNullOrUndefined(settings.enableRefreshCookieAuth)) { runtime.settings.enableRefreshCookieAuth = settings.enableRefreshCookieAuth; } if (!utils.isNullOrUndefined(settings.refreshTokenExpiresIn)) { runtime.settings.refreshTokenExpiresIn = settings.refreshTokenExpiresIn; } if (!utils.isNullOrUndefined(settings.nodeRedUnsafeModules)) { runtime.settings.nodeRedUnsafeModules = settings.nodeRedUnsafeModules; } runtime.settings.swaggerEnabled = settings.swaggerEnabled; if (settings.secretCode) { runtime.settings.secretCode = settings.secretCode; } if (settings.secureEnabled) { runtime.settings.tokenExpiresIn = settings.tokenExpiresIn; runtime.settings.enableRefreshCookieAuth = settings.enableRefreshCookieAuth; runtime.settings.refreshTokenExpiresIn = settings.refreshTokenExpiresIn; } if (settings.smtp) { runtime.settings.smtp = settings.smtp; } if (settings.daqstore) { runtime.settings.daqstore = settings.daqstore; } if (settings.alarms) { runtime.settings.alarms = settings.alarms; } if (settings.logs) { runtime.settings.logs = settings.logs; } } function verifyGroups(req) { if (runtime.settings && runtime.settings.secureEnabled) { if (req.apiKey) { return authJwt.adminGroups[0]; } if (req.tokenExpired) { return (runtime.settings.userRole) ? null : 0; } const userInfo = runtime.users.getUserCache(req.userId); return (runtime.settings.userRole && req.userId !== 'admin') ? userInfo : userInfo ? userInfo.groups : req.userGroups; } else { return authJwt.adminGroups[0]; } } function start() { } function stop() { } module.exports = { init: init, start: start, stop: stop, get apiApp() { return apiApp; }, get server() { return server; }, get authJwt() { return authJwt; } };