bridge-node-server/node_server/node_server.js
Martin Donnelly 57bd6c8e6a init
2018-06-24 21:15:03 +01:00

949 lines
36 KiB
JavaScript

/**
* @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:
* <dl>
* <dt>--path <pathPrefix></dt><dd>Prefix for the javascript modules path. Defaults to /node_server/ComServe/</dd>
* <dt>--config <configFile></dt><dd>Path to the config file. Defaults to <pathPrefix>/ComServe/config.js</dd>
* <dt>--env <deploymentEnv></dt><dd>Deployment environment switch to change end points. Defaults to 'Azure'</dd>
* </dl>
* @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');
}