const url = require("url"); const express = require("express"); const { createProxyMiddleware } = require('http-proxy-middleware'); const Arena = require('bull-arena'); const MYQUEUES = require('@QMI/qmi-cloud-common/queues'); const TF_APPLY_QUEUE = MYQUEUES.TF_APPLY_QUEUE; const TF_APPLY_QSEOK_QUEUE = MYQUEUES.TF_APPLY_QSEOK_QUEUE; const TF_DESTROY_QUEUE = MYQUEUES.TF_DESTROY_QUEUE; const STOP_CONTAINER_QUEUE = MYQUEUES.STOP_CONTAINER_QUEUE; const SYNAPSE_QUEUE = MYQUEUES.SYNAPSE_QUEUE; const WEBHOOK_QUEUE = MYQUEUES.WEBHOOK_QUEUE; const app = express(); const routesApiScenarios = require('./routes/api-scenarios'); const routesApiUsers = require('./routes/api-users'); const routesApiProvisions = require('./routes/api-provisions'); const routesApiDestroyProvisions = require('./routes/api-destroyprovisions'); const routesApiNotifications = require('./routes/api-notifications'); const routesApiDivvy = require('./routes/api-divvy'); const routesApiDeployOpts = require('./routes/api-deployopts'); const routesApiApikeys = require('./routes/api-apikeys'); const routesApiWebhooks = require('./routes/api-webhooks'); const routesApiStats = require('./routes/api-stats'); const routesApiTraining = require('./routes/api-training'); const routesApiConfig = require('./routes/api-config'); const routesApiSnapshots= require('./routes/api-snapshots'); const swaggerUi = require('swagger-ui-express'); const swaggerJsdoc = require('swagger-jsdoc'); const cookieParser = require('cookie-parser'); const bodyParser = require('body-parser'); const https = require('https'); const fs = require('fs'); const path = require('path'); const passport = require('./passport-okta'); //const qsProxy = require("./routes/qsProxy"); const QMI_MONGO_URL = process.env.QMI_MONGO_URL || "http://host.docker.internal:8081/"; const BACKEND_LOGS_URL = process.env.BACKEND_LOGS_URL || "http://host.docker.internal:8888/"; const GUACAMOLE_URL = process.env.GUACAMOLE_URL || "http://qmicloud-dev.qliktech.com:8080/guacamole/"; const IS_SECURE = process.env.CERT_PFX_PASSWORD && process.env.CERT_PFX_FILENAME; function _getRedisConfig(redisUrl) { const redisConfig = url.parse(redisUrl); return { host: redisConfig.hostname || 'localhost', port: Number(redisConfig.port || 6379), database: (redisConfig.pathname || '/0').substr(1) || '0', password: redisConfig.auth ? redisConfig.auth.split(':')[1] : undefined }; } //----------------------------------------------------------------------------- // Config the app, include middlewares //----------------------------------------------------------------------------- //app.set('views', __dirname + '/views'); //app.set('view engine', 'ejs'); app.use(cookieParser()); /*app.use(function myMiddleware(req, res, next) { res.set('X-Content-Type-Options', 'nosniff'); res.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); res.set('Cache-Control', 'max-age=86400'); res.set('Content-Security-Policy', "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://*.qlikcloud.com; connect-src 'self' https://*.amazonaws.com https://*.launchdarkly.com https://*.qlikcloud.com https://*.qlikdataengineering.com wss://innovation.us.qlikcloud.com; style-src 'self' 'unsafe-inline'; font-src 'self' *.qlikcloud.com; img-src 'self' data: https:; frame-src 'self' *.qlik-innovation.com; frame-ancestors 'self' *.qlikcloud.com *.qlik-innovation.com;"); next(); });*/ app.use('/', function myMiddleware(req, res, next) { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); res.setHeader('Cache-Control', 'public, max-age=86400'); res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://*.qlikcloud.com 'unsafe-inline'; connect-src 'self' https://*.amazonaws.com https://*.launchdarkly.com https://*.qlikcloud.com https://*.qlikdataengineering.com wss://innovation.us.qlikcloud.com; style-src 'self' 'unsafe-inline'; font-src 'self' *.qlikcloud.com; img-src 'self' data: https:; frame-src 'self' *.qlik-innovation.com; frame-ancestors 'self' *.qlikcloud.com *.qlik-innovation.com;"); next(); }, express.static(__dirname + '/../dist/qmi-cloud')); passport.init(app, IS_SECURE ? true : false); /* GUACAMOLE */ app.use('/guacamole/', passport.ensureAuthenticatedAndVPNDoLogin, createProxyMiddleware({ target: GUACAMOLE_URL, ws: true, changeOrigin: true, followRedirects: true, secure: false, on: { proxyReq: (proxyReq, req, res) => { if (req.user && req.user.mail) { proxyReq.setHeader('X-Guaca-Auth', req.user.mail); } }, error: (err, req, res) => { console.log("error guac proxy", err); } } })); /* DOZZLE */ app.use('/dockerlogs/', passport.ensureAuthenticatedAndAdmin, createProxyMiddleware({ target: BACKEND_LOGS_URL, ws: false, changeOrigin: true, followRedirects: false, secure: false } )); /* MONGOEXPRESS */ app.use('/mongo', passport.ensureAuthenticatedAndAdmin, createProxyMiddleware({ target: QMI_MONGO_URL, ws: false, changeOrigin: true, followRedirects: true, secure: false } )); /* Swagger DOCS */ const specs = swaggerJsdoc({ definition: { openapi: "3.0.3", // Like the one described here: https://swagger.io/specification/#infoObject info: { title: 'QMI Cloud - API', version: '1.0.0', description: 'REST API for QMI Cloud solutions', contact: { "name": "Qlik Presales - Innovation & Excellence Team", "email": "DL-PresalesGlobalInnovation@qlik.com" } }, servers: [{ "url": "/api/v1", "description": "Production Server" }], components: { securitySchemes: { ApiKeyAuth: { type: "apiKey", name: "apiKey", in: "query" } } }, security: [{ ApiKeyAuth: [] }] }, // List of files to be processes. You can also set globs './routes/*.js' apis: [ 'server/routes/api-*.js' ] }); app.use('/api-docs', [passport.ensureAuthenticatedAndVPNDoLogin,swaggerUi.serve], swaggerUi.setup(specs, { "customSiteTitle": "QMI Cloud - API", "customfavIcon": "/assets/favicon.svg" })); /*app.use('/arena', passport.ensureAuthenticatedAndVPNDoLogin, Arena( { queues: [ { name: TF_APPLY_QUEUE, hostId: 'Worker', redis: _getRedisConfig(process.env.REDIS_URL) }, { name: TF_APPLY_QSEOK_QUEUE, hostId: 'Worker', redis: _getRedisConfig(process.env.REDIS_URL) }, { name: TF_DESTROY_QUEUE, hostId: 'Worker', redis: _getRedisConfig(process.env.REDIS_URL) }, { name: STOP_CONTAINER_QUEUE, hostId: 'Worker', redis: _getRedisConfig(process.env.REDIS_URL) }, { name: SYNAPSE_QUEUE, hostId: 'Worker', redis: _getRedisConfig(process.env.REDIS_URL) }, { name: WEBHOOK_QUEUE, hostId: 'Worker', redis: _getRedisConfig(process.env.REDIS_URL) } ] }, { basePath: '/', disableListen: true } ));*/ app.use(bodyParser.urlencoded({ extended: false })); // parse application/json app.use(bodyParser.json()); const middlewareApi = function(req, res, next){ res.setHeader("Cache-Control", "no-cache, no-store"); res.removeHeader("Content-Security-Policy"); next(); } app.use("/api/v1/scenarios", middlewareApi, routesApiScenarios); app.use("/api/v1/users", middlewareApi, routesApiUsers); app.use("/api/v1/provisions", middlewareApi, routesApiProvisions); app.use("/api/v1/destroyprovisions", middlewareApi, routesApiDestroyProvisions); app.use("/api/v1/notifications", middlewareApi, routesApiNotifications); app.use("/api/v1/divvy", middlewareApi, routesApiDivvy); app.use("/api/v1/deployopts", middlewareApi, routesApiDeployOpts); app.use("/api/v1/apikeys", middlewareApi ,routesApiApikeys); app.use("/api/v1/webhooks", middlewareApi, routesApiWebhooks); app.use("/api/v1/stats", middlewareApi, routesApiStats); app.use("/api/v1/training", middlewareApi, routesApiTraining); app.use("/api/v1/config", middlewareApi, routesApiConfig); app.use("/api/v1/snapshots", middlewareApi, routesApiSnapshots); //app.use("/qcsproxy", qsProxy.router); function _isAllowedPath(path) { const allowedPaths = ['/api-docs', '/arena', '/costexport', '/backendlogs', '/photos/user/', '/qmimongo', '/guacamole/']; let isAllowed = false; for (let i = 0; i < allowedPaths.length; i++) { if (path.startsWith(allowedPaths[i])) { isAllowed = true; break; } } return isAllowed; } /* Checking allowedPaths */ app.get('/*', (req, res, next) => { if (_isAllowedPath(req.originalUrl)) { return next(); } else if (req.originalUrl.indexOf("oauth-callback.html") !== -1) { res.sendFile(path.join(__dirname, '/../dist/qmi-cloud/oauth-callback.html')); } else { res.setHeader("Cache-Control", "no-cache, no-store"); res.sendFile(path.join(__dirname, '/../dist/qmi-cloud/index.html')); } }); /* -----------------------*/ app.get('/qmimongo/*', function (req, res) { let path = req.path.split("/qmimongo")[1]; console.log("QMI-Mongo# Redirect: " + `${QMI_MONGO_URL}${path}`); res.redirect(`${QMI_MONGO_URL}${path}`); res.end(); }); app.use('/costexport*', passport.ensureAuthenticatedAndAdmin, function (req, res) { if (!req.query.file) { res.status(404).send("Not found"); } else { res.header("Content-Type", 'application/json'); res.sendFile(path.resolve(__dirname, '..', 'costexport', req.query.file)); } }); app.use('/photos/user/:oid', passport.ensureAuthenticatedPhoto, function (req, res) { if (!req.params.oid) { res.status(404).send("Not found"); } else { var pic = path.resolve(__dirname, '..', 'photos', `${req.params.oid}.jpg`); if (fs.existsSync(pic)) { res.sendFile(pic); } else { res.status(404).send(); } } }); /** * Create necessary folders */ var dirs = ['/logs', '/logs/provision', '/logs/destroy', '/costexports', '/photos']; dirs.forEach(d => { if (!fs.existsSync(d)) { console.log(`--- Creating folder '${d}' since it does not exist`); fs.mkdirSync(d); } }); /** * Start App */ const server = app.listen(3000, () => { console.log(`Server listening on port 3000`) }); //qsProxy.init(server); if (IS_SECURE) { var optionsHttps = { pfx: fs.readFileSync(path.resolve(__dirname, 'certs', process.env.CERT_PFX_FILENAME)), passphrase: process.env.CERT_PFX_PASSWORD }; const httpsServer = https.createServer(optionsHttps, app).listen(3100, function () { console.log(`Secure server listening on port 3100`); }); //qsProxy.init(httpsServer); }