var http = require('http'); var fs = require('fs'); var crypto = require('crypto'); var path = require('path'); var uglify = require('uglify-js'); var winston = require('winston'); var connect = require('connect'); var route = require('connect-route'); var connect_st = require('st'); var connect_rate_limit = require('connect-ratelimit'); var DocumentHandler = require('./lib/document_handler'); var cspMiddleware = require('./lib/csp'); var templateHandler = require('./lib/template_handler'); // Load the configuration var config = require('./config.js'); // Set up the logger if (config.logging) { // Reset default logger winston.clear(); // Create format based on config const logFormat = winston.format.combine( config.logging[0].json ? winston.format.json() : winston.format.simple(), config.logging[0].colorize ? winston.format.colorize() : winston.format.uncolorize() ); // Configure logger winston.configure({ level: config.logging[0].level || 'info', format: logFormat, transports: [ new winston.transports.Console() ] }); } // build the store from the config on-demand - so that we don't load it // for statics if (!config.storage) { config.storage = { type: 'file' }; } if (!config.storage.type) { config.storage.type = 'file'; } var Store, preferredStore; // Support Redis URL format for KeyDB/Redis if ((process.env.REDIS_URL || process.env.REDISTOGO_URL) && (config.storage.type === 'redis' || config.storage.type === 'keydb')) { // redis-url 0.1.0 uses a different API than newer versions var redisUrl = require('redis-url'); var redisClient = redisUrl.connect(process.env.REDIS_URL || process.env.REDISTOGO_URL); Store = require('./lib/document_stores/redis'); preferredStore = new Store(config.storage, redisClient); } else { // If storage is set to keydb, use the redis driver (KeyDB is Redis API compatible) if (config.storage.type === 'keydb') { config.storage.type = 'redis'; winston.info('Using redis driver for KeyDB storage'); } Store = require('./lib/document_stores/' + config.storage.type); preferredStore = new Store(config.storage); } // Compress the static javascript assets if (config.recompressStaticAssets) { var list = fs.readdirSync('./static'); for (var j = 0; j < list.length; j++) { var item = list[j]; if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) { var dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3); var orig_code = fs.readFileSync('./static/' + item, 'utf8'); fs.writeFileSync('./static/' + dest, uglify.minify(orig_code).code, 'utf8'); winston.info('compressed ' + item + ' into ' + dest); } } } // Send the static documents into the preferred store, skipping expirations var docPath, data; for (var name in config.documents) { docPath = config.documents[name]; data = fs.readFileSync(docPath, 'utf8'); winston.info('loading static document', { name: name, path: docPath }); if (data) { preferredStore.set(name, data, function(cb) { winston.debug('loaded static document', { success: cb }); }, true); } else { winston.warn('failed to load static document', { name: name, path: docPath }); } } // Pick up a key generator var pwOptions = config.keyGenerator || {}; pwOptions.type = pwOptions.type || 'random'; var gen = require('./lib/key_generators/' + pwOptions.type); var keyGenerator = new gen(pwOptions); // Configure the document handler var documentHandler = new DocumentHandler({ store: preferredStore, maxLength: config.maxLength, keyLength: config.keyLength, keyGenerator: keyGenerator }); var app = connect(); // Add CSP middleware early in the chain app.use(cspMiddleware(config)); // Add CORS support app.use(function(req, res, next) { // Get origin or fallback to * var origin = req.headers.origin; // Check if the origin is allowed if (config.allowedOrigins && config.allowedOrigins.length > 0) { if (config.allowedOrigins.includes('*') || (origin && config.allowedOrigins.includes(origin))) { res.setHeader('Access-Control-Allow-Origin', origin || '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); } } // Handle preflight requests if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } next(); }); // Rate limit all requests if (config.rateLimits) { config.rateLimits.end = true; app.use(connect_rate_limit(config.rateLimits)); } // first look at API calls app.use(route(function(router) { // get raw documents - support getting with extension router.get('/raw/:id', function(request, response) { var key = request.params.id.split('.')[0]; var skipExpire = !!config.documents[key]; return documentHandler.handleRawGet(key, response, skipExpire); }); // add documents router.post('/documents', function(request, response) { return documentHandler.handlePost(request, response); }); // get documents router.get('/documents/:id', function(request, response) { var key = request.params.id.split('.')[0]; var skipExpire = !!config.documents[key]; return documentHandler.handleGet(key, response, skipExpire); }); })); // Static file handling - this needs to come before the template handler app.use(connect_st({ path: __dirname + '/static', content: { maxAge: config.staticMaxAge }, passthrough: true, index: false })); // Add the template handler for index.html - only handles the main URLs app.use(templateHandler(require('path').join(__dirname, 'static'))); // Then we can loop back - and everything else should be a token, // so route it back to / app.use(route(function(router) { router.get('/:id', function(request, response, next) { request.sturl = '/'; next(); }); })); // And match index as a fallback app.use(connect_st({ path: __dirname + '/static', content: { maxAge: config.staticMaxAge }, index: 'index.html' })); http.createServer(app).listen(config.port, config.host); winston.info('listening on ' + config.host + ':' + config.port);