/** * @fileOverview Node.js Bridge Server Application for Bridge Pay * @preserve Copyright 2017 Comcarde Ltd. * @author Keith Symington * @see #bridge_server-core */ /* eslint-disable no-process-env, no-process-exit, no-console, import/max-dependencies */ /** * Requirements. */ const path = require('path'); const exitCodes = require('./exitcodes.js'); const logUtils = require('./utils/logging'); const logger = logUtils(__filename, 'bridge:server'); /** * Environment defines. Use to change endpoints automatically in other sections of the code. * This is global */ global.DEPLOYMENT_ENVS = ['AWS', 'Bluemix', 'Azure', 'Flexiion', 'Local']; global.CURRENT_DEPLOYMENT_ENV = 'Azure'; // Sets the default environment. /** * Parse the command line using minimist to find command line parameters. * Valid command line parameters are: *
*
--path
Prefix for the javascript modules path. Defaults to /node_server/ComServe/
*
--config
Path to the config file. Defaults to /ComServe/config.js
*
--env
Deployment environment switch to change end points. Defaults to 'Azure'
*
* @type {object} */ const opts = { string: ['path', 'config', 'env'], alias: { path: ['p'], env: ['e'] } }; /** * Use the opts above to parse the command line, and store the parameters in argv * @type object */ const argv = require('minimist')(process.argv.slice(2), opts); /** * If the env property is present, change the environment to the requested one. This is taken from the command line. * Most of the environmental differences are endpoints. */ if (argv.hasOwnProperty('env')) { if (global.DEPLOYMENT_ENVS.indexOf(argv.env) < 0) { console.log('\nBad deployment environment. Options are: ' + JSON.stringify(global.DEPLOYMENT_ENVS)); process.exit(exitCodes.EXIT_CODE_NO_ENVIRONMENT); } else { global.CURRENT_DEPLOYMENT_ENV = argv.env; } } /** * Change default paths based on environment. * Note that adding the command line switch overrides everything else. */ if (argv.hasOwnProperty('path')) { global.rootPath = argv.path; } else { switch (global.CURRENT_DEPLOYMENT_ENV) { case 'Azure': global.rootPath = '/home/comcardeadmin/node_server/'; break; case 'Bluemix': global.rootPath = '/node_server/'; break; case 'Flexiion': global.rootPath = '/home/flexops/node_server/'; break; default: global.rootPath = path.join(__dirname, '/'); // Expected to end with a '/' } } /** * Store the command line parameters. This will also normalise the path to the OS. */ global.pathPrefix = global.rootPath + 'ComServe/'; if (argv.hasOwnProperty('config')) { global.configFile = argv.config; } else { global.configFile = global.rootPath + 'ComServe/config.js'; } global.rootPath = path.normalize(global.rootPath); global.pathPrefix = path.normalize(global.pathPrefix); global.configFile = path.normalize(global.configFile); /** * Log what startup params we are using. */ console.log('\nLoading Bridge Node Server config files...'); console.log('Source Path Prefix:', global.pathPrefix); console.log('ConfigFile:', global.configFile); /** * Load the basic files. Always config first. */ let config; let utils; try { // eslint-disable-next-line global-require config = require(global.configFile); // eslint-disable-next-line global-require utils = require(global.pathPrefix + 'utils.js'); } catch (error) { console.log('Unable to load configuration files: ' + error); process.exit(exitCodes.EXIT_CODE_CONFIG_FILE_ERROR); } /** * Print server information. */ console.log(utils.CarriageReturn + 'COMCARDE BRIDGE NODE SERVER (' + config.CCServerName + ', VIP ' + config.CCServerIP + ')'); console.log(global.CURRENT_DEPLOYMENT_ENV + ' Deployment (' + config.CCServerGroup + ', UUID ' + config.CCUUID + ')'); console.log('Config: https://' + config.CCWebsiteAddress + ', ' + config.CCServerReleaseType + ' V' + config.CCServerVersion + ' (Node: ' + config.ServerCommit + ', Portal: ' + config.PortalCommit + ').'); /** * Load the rest of the include files. */ const http = require('http'); const fs = require('fs'); const express = require('express'); const helmet = require('helmet'); const async = require('async'); const url = require('url'); const querystring = require('querystring'); const mainDB = require(global.pathPrefix + 'mainDB.js'); const log = require(global.pathPrefix + 'log.js'); const sms = require(global.pathPrefix + 'sms.js'); const hJSON = require(global.pathPrefix + 'hJSON.js'); const credorax = require(global.pathPrefix + 'credorax.js'); const worldpay = require(global.pathPrefix + 'worldpay.js'); const rateLimit = require(global.pathPrefix + 'rate_limit.js'); const migrations = require(global.pathPrefix + 'migrations.js'); /** * Load default images. */ try { let inputImage; inputImage = fs.readFileSync(global.rootPath + 'WebApp/defaultSelfie.png'); config.defaultSelfieData = Buffer.from(inputImage).toString('base64'); inputImage = fs.readFileSync(global.rootPath + 'WebApp/defaultCompanyLogo0.png'); config.defaultCompanyLogo0Data = Buffer.from(inputImage).toString('base64'); console.log('Default image data loaded for \'defaultSelfie\' and \'CompanyLogo0\''); } catch (error) { console.log('Unable to load default images: ' + error); process.exit(exitCodes.EXIT_CODE_NO_DEFAULT_IMAGES); } /** * Note whether verbose mode is on or off. */ if (log.verbose) { console.log('Verbose mode is on. Events, warnings and errors will be written to stdout.' + utils.CarriageReturn); } else { console.log('Verbose mode is off. Only errors will be written to stdout.' + utils.CarriageReturn); } /** * System state defines and web server configuration. Since offload to load balancer, the code only uses HTTP. * The SSL certificate has been offloaded to the balancer for performance reasons. */ const startupServices = { httpOnline: 1 }; /** * Load and pre-compile the templates */ const templates = require(global.pathPrefix + '../utils/templates.js'); templates.initTemplates(); /** * Web server defines. */ const verboseWebServer = 1; // Additional web server logging for debug. const serverHTTPport = config.serverHttpPort; // Will be redirected to HTTPS. const rootServerDirectory = global.rootPath + 'WebApp'; const rootPortalDirectory = global.rootPath + 'portal/'; let filesServed = 0; const longTickTime = 15 * 12; // Change this value for the long tick return. Multiply by 5 seconds for real time. let longTick = longTickTime; /** * Define the mongodb configuration parameters. */ let cert; let key; let mongoConnectOptions = {}; if (config.mongoUseSSL) { const certPath = path.join( __dirname, config.mongoCACertBase64 ); cert = fs.readFileSync(certPath); key = fs.readFileSync(certPath); mongoConnectOptions = { ssl: true, sslKey: key, sslCert: cert }; } /** * Connect to the mongodb server and open the collections. * Function takes no parameters. */ function startupDatabase() { // eslint-disable-next-line no-negated-condition if (!mainDB.dbOnline) { try { /** * Attempt to open the database connection. */ mainDB.MClient.connect( mainDB.dbAddress, mongoConnectOptions, (err, db) => { if (err) { if (!utils.systemState.dbWaiting) { log.system( 'CRITICAL', ('Could not connect to primary database. ' + JSON.stringify(err) + ' Retrying every 5 seconds...'), 'node_server.startupDatabase', '', 'System', '127.0.0.1'); utils.systemState.dbWaiting = 1; } return; } /** * Connect to collections. */ mainDB.mdb = db; if (mainDB.mdb) { mainDB.collectionAccount = mainDB.mdb.collection(mainDB.dbAccount); mainDB.collectionAccountArchive = mainDB.mdb.collection(mainDB.dbAccountArchive); mainDB.collectionPaymentInstrument = mainDB.mdb.collection(mainDB.dbPaymentInstrument); mainDB.collectionPaymentInstrumentArchive = mainDB.mdb.collection(mainDB.dbPaymentInstrumentArchive); mainDB.collectionAddresses = mainDB.mdb.collection(mainDB.dbAddresses); mainDB.collectionAddressArchive = mainDB.mdb.collection(mainDB.dbAddressArchive); mainDB.collectionBridgeLogin = mainDB.mdb.collection(mainDB.dbBridgeLogin); mainDB.collectionClient = mainDB.mdb.collection(mainDB.dbClient); mainDB.collectionClientArchive = mainDB.mdb.collection(mainDB.dbClientArchive); mainDB.collectionDevice = mainDB.mdb.collection(mainDB.dbDevice); mainDB.collectionDeviceArchive = mainDB.mdb.collection(mainDB.dbDeviceArchive); mainDB.collectionImages = mainDB.mdb.collection(mainDB.dbImages); mainDB.collectionItems = mainDB.mdb.collection(mainDB.dbItems); mainDB.collectionMessages = mainDB.mdb.collection(mainDB.dbMessages); mainDB.collectionMessagesArchive = mainDB.mdb.collection(mainDB.dbMessagesArchive); mainDB.collectionPayCode = mainDB.mdb.collection(mainDB.dbPayCode); mainDB.collectionSystemLog = mainDB.mdb.collection(mainDB.dbLog); mainDB.collectionTransaction = mainDB.mdb.collection(mainDB.dbTransaction); mainDB.collectionTransactionArchive = mainDB.mdb.collection(mainDB.dbTransactionArchive); mainDB.collectionTransactionHistory = mainDB.mdb.collection(mainDB.dbTransactionHistory); mainDB.collectionTwoFARequests = mainDB.mdb.collection(mainDB.dbTwoFARequests); mainDB.collectionActivityLog = mainDB.mdb.collection(mainDB.dbActivityLog); /** * Test the database connection. Set up a test log entry. */ const logData = {}; logData.DateTime = new Date(); logData.ServerID = config.CCServerName + ' (VIP ' + config.CCServerIP + ')'; logData.Class = 'STARTUP'; logData.Function = 'node_server.startupDatabase'; logData.Code = ''; logData.Info = 'SERVER ONLINE: ' + config.CCServerName + ', Config: https://' + config.CCWebsiteAddress + ', ' + config.CCServerReleaseType + ' V' + config.CCServerVersion + ' (Node: ' + config.ServerCommit + ', Portal: ' + config.PortalCommit + ').'; logData.User = 'System'; logData.Source = '127.0.0.1'; /** * Write the new log entry. */ mainDB.dbOnline = 1; utils.systemState.dbWaiting = 0; // eslint-disable-next-line no-shadow mainDB.addObject(mainDB.collectionSystemLog, logData, undefined, false, (err) => { if ((err) || (mainDB.dbOnline === 0)) { // Unable to store info. log.system( 'CRITICAL', 'Database connection test failed. Will retry in 5 seconds.', 'node_server.startupDatabase', '', 'System', '127.0.0.1'); return; } /** * Success. Database online. */ log.system( 'STARTUP', ('Connected to requested primary database ' + config.mongoDBAddress), 'node_server.startupDatabase', '', 'System', '127.0.0.1'); /** * Update the logger utils to connect to this db instance */ logUtils.init.initMongoTransport(db); logger.info( {}, // No request 'Connected to requested primary database', { dbAddress: config.mongoDBAddress } ); /** * Scan the database if required - this runs in the background using a cursor. * It is suggested that this is run for deployment of new versions then subsequently disabled. */ if (config.databaseUpdate) { mainDB.updateDatabase(); } if (config.migrateEmailToID) { migrations.migrateClientNameToID(); } /** * Get the current number of text messages that are left. */ const tempSMSTestMode = sms.smsTestMode; sms.smsTestMode = true; sms.sendSMS(null, sms.adminMobile, (config.CCServerName + ' startup complete.'), // eslint-disable-next-line no-shadow (err, smsBalance) => { if (err) { sms.smsTestMode = tempSMSTestMode; log.system( 'CRITICAL', ('Cannot send SMS or connect to SMS server. ' + err), 'node_server.startupDatabase', '', 'System', '127.0.0.1'); return; } sms.smsTestMode = tempSMSTestMode; if (50 < smsBalance) { log.system( 'STARTUP', ('Successfully connected to TextLocal (SMS balance is ' + smsBalance + ').'), 'node_server.startupDatabase', '', 'System', '127.0.0.1'); } else { log.system( 'WARNING', ('Successfully connected to TextLocal but balance is low (' + smsBalance + ' remaining).'), 'node_server.startupDatabase', '', 'System', '127.0.0.1'); } }); }); } else { // Error connecting to collections. Force shutdown. log.system( 'CRITICAL', 'Could not open collections. Please contact the administrator to ensure they are set up.', 'node_server.startupDatabase', '', 'System', '127.0.0.1'); utils.systemState.shutdownTick = 2; } }); } catch (error) { log.system( 'WARNING', ('Database still attempting to connect. ' + error), 'node_server.startupDatabase', '', 'System', '127.0.0.1'); } } else { log.system( 'ERROR', 'Erroneous call to startupDatabase() ignored as database is already online.', 'node_server.startupDatabase', '', 'System', '127.0.0.1'); } } /** * First fast timeout to start up the system. */ setTimeout(systemCheck, 100); /** * System check watchdog. Runs every 5 seconds and is used to manage shutdown. * Can also be used for general housekeeping. * * @type {function} systemCheck * * Need to ignore eslint complexity warnings here: */ // eslint-disable-next-line complexity function systemCheck() { /** * The shutdown tick allows housekeeping before shutdown. */ if (utils.systemState.shutdownTick === -1) { // Check the database state. if (!mainDB.dbOnline) { /** * Database is not online. Figure out why. */ if (utils.systemState.firstTime) { /** * OK, just starting up for the first time. Connect to database. */ utils.systemState.firstTime = 0; log.system( 'STARTUP', 'Initialising database and web servers...', 'node_server.systemCheck', '', 'System', '127.0.0.1'); startupDatabase(); } else { /** * Looks like we lost connection. Try to start up again. */ startupDatabase(); } } /** * Executes every 15 minutes. */ if (longTick === 0) { let status = ''; /** * HTTP services: */ if (startupServices.httpOnline) { status += 'HTTP:80 Up, '; } else { status += 'HTTP:80 Down, '; } if (mainDB.dbOnline) { status += 'MDB Up, '; } else { status += 'MDB Down, '; } status += 'WWW ' + filesServed + ', '; status += 'JSON ' + hJSON.JSONServed + ', '; status += 'SMS ' + sms.smsCredits + ', '; status += 'CRX ' + credorax.primaryFailedComms + ', '; status += 'WP ' + worldpay.primaryFailedComms + '.'; /** * Output the status information. */ log.system( 'SERVER', status, 'node_server.systemCheck', '', 'System', '127.0.0.1'); /** * Reduce the number of failed comms for active acquirers. * Credorax */ if (config.credoraxCurrentGateway === config.credoraxPrimaryGateway) { if (credorax.primaryFailedComms > 0) { credorax.primaryFailedComms -= config.credoraxChangeRate; } } else { log.system( 'WARNING', 'System using secondary Credorax server.', 'node_server.systemCheck', '', 'System', '127.0.0.1'); } /** * Worldpay */ if (worldpay.primaryFailedComms > config.worldpayNotificationThreshold) { log.system( 'WARNING', 'Unexpected number of communciations failures with Worldpay primary gateway.', 'node_server.systemCheck', '', 'System', '127.0.0.1'); } if (worldpay.primaryFailedComms > 0) { worldpay.primaryFailedComms -= config.worldpayChangeRate; } /** * Reset the tick. */ longTick = longTickTime; } else { /** * Do nothing other than decrement. */ longTick--; } } else if (utils.systemState.shutdownTick > 1) { /** * Tick set to higher than 1 - shutdown requested. */ utils.systemState.shutdownTick -= 1; /** * Close off the servers. */ startupServices.httpOnline = 0; /** * Close off the database. */ if (mainDB.dbOnline === 1) { /** * Database is online. Log shutdown info. */ const logData = {}; logData.DateTime = new Date(); logData.ServerID = config.CCServerName + ' (VIP ' + config.CCServerIP + ')'; logData.Class = 'SHUTDOWN'; logData.Function = 'node_server.systemCheck'; logData.Code = ''; logData.Info = 'Servers offline. Database shutdown in progress...'; logData.User = 'System'; logData.Source = '127.0.0.1'; console.log('[' + logData.DateTime.toISOString() + '] ' + logData.Class + ': ' + logData.Info); /** * Add the info to the log. */ mainDB.collectionSystemLog.insert(logData, (err) => { if (err) { console.log('[' + String(new Date().toISOString()) + '] ERROR: Database write error during shutdown. Shutdown complete.' + utils.CarriageReturn); process.exit(exitCodes.EXIT_CODE_DATABASE_WRITE_ERROR); } else { /** * Close off database. */ // eslint-disable-next-line no-shadow mainDB.mdb.close((err) => { if (err) { console.log('[' + String(new Date().toISOString()) + '] ERROR: Could not correctly close database. Shutdown complete.' + utils.CarriageReturn); process.exit(exitCodes.EXIT_CODE_DATABASE_NOT_CLOSED); } else { console.log('[' + String(new Date().toISOString()) + '] SHUTDOWN: Cleanup complete. Exiting process.' + utils.CarriageReturn); process.exit(exitCodes.EXIT_CODE_SUCCESS); } }); } }); } else { console.log('[' + String(new Date().toISOString()) + '] ERROR: Database unexpectedly offline. Shutdown complete.' + utils.CarriageReturn); process.exit(exitCodes.EXIT_CODE_DATABASE_OFFLINE); } } else if (utils.systemState.shutdownTick === 1) { /** * Give the database time to shut down. */ console.log('[' + String(new Date().toISOString()) + '] SHUTDOWN: Waiting for database shutdown. 5 seconds until forced termination...'); utils.systemState.shutdownTick -= 1; } else if (utils.systemState.shutdownTick === 0) { /** * Shut down anyway. */ console.log('[' + String(new Date().toISOString()) + '] WARNING: Node server shutdown forced. Database may not have been properly closed.' + utils.CarriageReturn); process.exit(exitCodes.EXIT_CODE_FORCED_SHUTDOWN); } /** * Set five second tick. */ setTimeout(systemCheck, 5000); } /** * Simple web server functionality. * For reasons to do with the way Express leaves ports open, a custom web server is used in this instance. * * @param {!object} req - The Mongo collection in which the object exists. * @param {!object} req.connection - Detail about the connection. * @param {!object} res - The search parameters for the object(s) to delete in JSON format. * @param {!function} res.writeHead - Write the response header. * @param {!function} res.end - Return the header. * @param {!string} remoteAddress - the remote address the request is made from * @param {!string} protocolPort - Protocol followed by incoming port e.g. 'HTTP:80'. * @param {!string} location - Optional callback for async operation. */ function serveFile(req, res, remoteAddress, protocolPort, location) { /** * Default behaviour is to look for a file to send. */ const filename = location; filesServed++; /** * Check for a null. */ // eslint-disable-next-line no-negated-condition if (filename.indexOf('\0') !== -1) { log.system( 'ATTACK', 'Null byte in path rejected.', 'node_server.serveFile', '', 'UU', (remoteAddress + ' (' + protocolPort + ')')); res.sendStatus(400); } else { /** * Check for someone trying to escape the root directory. */ const normalizedFile = path.normalize((rootServerDirectory + filename)); const normalizedRootDir = path.normalize(rootServerDirectory); // eslint-disable-next-line no-negated-condition if (normalizedFile.indexOf(normalizedRootDir) !== 0) { log.system( 'ATTACK', 'Directory traversal rejected.', 'node_server.serveFile', '', 'UU', (remoteAddress + ' (' + protocolPort + ')')); res.sendStatus(403); } else { /** * All good. Serve the file. */ async.series([ function(callback) { fs.readFile(normalizedFile, (err, data) => { if (err) { /** * Error reading file. Pass error forward. */ log.system( 'WARNING', ('404 File not found. [' + normalizedFile + ']'), 'node_server.serveFile', '', 'UU', (remoteAddress + ' (' + protocolPort + ')')); return callback(err); } else { /** * Read successfully. */ if (verboseWebServer) { log.system( 'FILE', 'File returned [' + normalizedFile + ']', 'node_server.serveFile', '', 'UU', (remoteAddress + ' (' + protocolPort + ')')); } /** * Deal with extensions. */ switch (path.extname(filename)) { case '.png': res.writeHead(200, {'Content-Type': 'image/png'}); break; default: res.writeHead(200); } /** * Fill with the rest of the data. Watch for zero length files. */ if (data) { res.end(data); } else { res.end(); } return callback(); } }); }], /** * Final clause which is executed after everything else or when an error is detected. */ (err) => { if (err) { res.sendStatus(404); } } ); } } } /** * Web Server elements. Processes an HTTP request, be it JSON or HTTP. * * @param {!object} req - The Mongo collection in which the object exists. * @param {!object} req.connection - Detail about the connection. * @param {!object} req.url - Detail about the requested url. * @param {!string[]} req.headers - The headers in the request packet. * @param {!object} res - The search parameters for the object(s) to delete in JSON format. * @param {!function} res.writeHead - Write the response header. * @param {!function} res.end - Return the header. * @param {!function} res.setHeader - Sets the response header. * @param {!string} remoteAddress - the remote address the request is made from * @param {!string} protocolPort - Protocol followed by incoming port e.g. 'HTTP:80' */ function processRequest(req, res, remoteAddress, protocolPort) { try { /** * Parse the URL in case anything needs to be removed. */ const currentUrl = url.parse(req.url); /** * Switch on path name. */ switch (currentUrl.pathname.toUpperCase()) { case '/SERVER_POST': // Use this for JSON requests. All requests should use one of the two. hJSON.handleJSONRequest(req, res, remoteAddress, protocolPort, querystring.parse(currentUrl.query), hJSON.REST); break; default: /* * Default action is to consider this a file request. */ serveFile(req, res, remoteAddress, protocolPort, currentUrl.pathname); } } catch (error) { /** * Unhandled exception. */ log.system( 'CRITICAL', ('Unhandled error condition. ' + error.name + ' (' + error.message + ')'), 'node_server.processRequest', '', 'UU', (remoteAddress + ' (' + protocolPort + ')')); res.status(500).json({ code: -1, info: 'Unexpected server error' }); } } /** * HTTP server (80). */ const appHttp = express(); const serverHTTP = http.createServer(appHttp); /** * Set up an error handler */ serverHTTP.on('error', (err) => { log.system( 'CRITICAL', String(err), 'node_server.serverHTTP.on', '', 'UU', '127.0.0.1'); }); /** * Next start up the listener. */ serverHTTP.listen(serverHTTPport); serverHTTP.timeout = utils.webTimeout; /* * Security related settings * See https://www.npmjs.com/package/helmet for more on why we need these */ const ninetyDaysInS = 90 * 24 * 60 * 60; appHttp.use(helmet.frameguard({action: 'deny'})); // Protect against click-jacking appHttp.use(helmet.xssFilter()); // Browser internal xss protection if (config.useHTTPS) { appHttp.use(helmet.hsts({ // Request *subsequent* browser visits use https maxAge: ninetyDaysInS // for the next 90 days (not enforceable). })); } appHttp.use(helmet.hidePoweredBy()); // Hide the "x-powered-by: Express" header appHttp.use(helmet.ieNoOpen()); // IE specific issue appHttp.use(helmet.noSniff()); // Prevent dynamic mime type "sniffing" appHttp.set('trust proxy', config.CCServerIP); // Sets the proxy up correctly which is required for containers. /** * Load the swagger API router to handle `/api/*` routes */ const initConsoleApi = require('./swagger_api/api_server.js'); /** * Load the integration API router to handle `/int/*` routes */ const initIntegrationApi = require('./integration_api/int_api_server.js'); const integrationApiRouter = initIntegrationApi.init(); appHttp.use('/int', integrationApiRouter); /** * Load the dev API router to handle `/dev/*` routes */ const initDevApi = require('./dev_api/dev_server.js'); const devApiRouter = initDevApi.init(); appHttp.use('/dev', devApiRouter); /* * Load the router to serve the web console from /portal/ */ const portalRouterFactory = require('./portal-router.js'); const portalRouter = portalRouterFactory(rootPortalDirectory); appHttp.use('/portal', portalRouter); /* * Redirect any calls to '/' to the portal. */ appHttp.get('/', (req, res) => { res.redirect('/portal/login'); }); /* * Load the router to serve the metrics from /metrics/ */ const promRouterFactory = require('./prometheus-router.js'); const promRouter = promRouterFactory(); appHttp.use('/metrics', promRouter); /** * Enable rate limits for the other paths */ rateLimit.enableLimits(appHttp); /* * Load the swagger definitions of the API. This is asynchronous, but must be * loaded before setting up the processRequest handler. */ (async () => { const consoleApiRouter = await initConsoleApi(mainDB.dbAddress, mongoConnectOptions, 'WebConsoleSessions'); appHttp.use('/api', consoleApiRouter); /** * Route everything else to the processRequest handlers. */ appHttp.all('*', (req, res) => { if (startupServices.httpOnline && utils.isLBHTTPS(req, res)) { /** * Different firewall headers depending on the source of the data. * To get in to this code the services have been called from a trusted proxy. * Technically the protocolPort should always be 'HTTPS:443' if the code has * reached here, but it is taken from the headers if available for verification. */ let remoteAddress; let protocolPort; switch (global.CURRENT_DEPLOYMENT_ENV) { case 'Azure': remoteAddress = req.ip.split(':')[0]; protocolPort = req.protocol + ':' + req.headers['x-forwarded-port']; break; case 'Bluemix': remoteAddress = req.headers.$wsra; protocolPort = req.headers.$wssc + ':' + req.headers.$wssp; break; case 'Flexiion': default: remoteAddress = req.ip; protocolPort = req.protocol + ':443'; } /** * Process the request. */ processRequest(req, res, remoteAddress, protocolPort); } }); })(); /** * Indicate startup is complete. */ if (startupServices.httpOnline) { log.system( 'STARTUP', ('HTTP server listening on port ' + serverHTTPport + '.'), 'node_server.serverHTTP', '', 'System', '127.0.0.1'); } else { log.system( 'WARNING', ('HTTP server attached to port ' + serverHTTPport + ' but service is disabled.'), 'node_server.serverHTTP', '', 'System', '127.0.0.1'); }