/* jshint -W097 */ /* jshint strict: false */ /* jslint node: true */ /* jshint -W061 */ 'use strict'; const Stream = require('stream'); const utils = require(__dirname + '/utils'); // Get common adapter utils const LE = require(utils.controllerDir + '/lib/letsencrypt.js'); const express = require('express'); const fs = require('fs'); let path; // will be loaded later let session; let bodyParser; let AdapterStore; let password; let passport; let LocalStrategy; let flash; let cookieParser; let fileUpload; function Web(settings, adapter, onReady) { if (!(this instanceof Web)) return new Web(settings, adapter, onReady); const server = { app: null, server: null }; const bruteForce = {}; let store = null; let loginPage; this.server = server; this.close = () => server.server && server.server.close(); function decorateLogFile(filename) { const prefix = '
' + '\n' + '\n\n\n'; const suffix = ''; const log = fs.readFileSync(filename).toString(); return prefix + log + suffix; } function prepareLoginTemplate() { let def = 'background: #64b5f6;\n'; let template = fs.readFileSync(__dirname + '/../www/login/index.html').toString('utf8'); if (adapter.config.loginBackgroundColor) { def = 'background-color: ' + adapter.config.loginBackgroundColor + ';\n' } if (adapter.config.loginBackgroundImage) { def += ' background-image: url(../' + adapter.namespace + '/login-bg.png);\n'; } if (adapter.config.loginHideLogo) { template = template.replace('.logo { display: block }', '.logo { display: none }'); } if (adapter.config.loginMotto) { template = template.replace('Discover awesome. yunkong2', adapter.config.loginMotto); } return template.replace('background: #64b5f6;', def); } //settings: { // "port": 8080, // "auth": false, // "secure": false, // "bind": "0.0.0.0", // "::" // "cache": false //} (function __construct () { if (settings.port) { server.app = express(); if (settings.auth) { session = require('express-session'); cookieParser = require('cookie-parser'); bodyParser = require('body-parser'); AdapterStore = require(utils.controllerDir + '/lib/session.js')(session, settings.ttl); password = require(utils.controllerDir + '/lib/password.js'); passport = require('passport'); LocalStrategy = require('passport-local').Strategy; flash = require('connect-flash'); // TODO report error to user store = new AdapterStore({adapter: adapter}); passport.use(new LocalStrategy( (username, password, done) => { if (bruteForce[username] && bruteForce[username].errors > 4) { let minutes = (new Date().getTime() - bruteForce[username].time); if (bruteForce[username].errors < 7) { if ((new Date().getTime() - bruteForce[username].time) < 60000) { minutes = 1; } else { minutes = 0; } } else if (bruteForce[username].errors < 10) { if ((new Date().getTime() - bruteForce[username].time) < 180000) { minutes = Math.ceil((180000 - minutes) / 60000); } else { minutes = 0; } } else if (bruteForce[username].errors < 15) { if ((new Date().getTime() - bruteForce[username].time) < 600000) { minutes = Math.ceil((600000 - minutes) / 60000); } else { minutes = 0; } } else if ((new Date().getTime() - bruteForce[username].time) < 3600000) { minutes = Math.ceil((3600000 - minutes) / 60000); } else { minutes = 0; } if (minutes) { return done('Too many errors. Try again in ' + minutes + ' ' + (minutes === 1 ? 'minute' : 'minutes') + '.', false); } } adapter.checkPassword(username, password, res => { if (!res) { bruteForce[username] = bruteForce[username] || {errors: 0}; bruteForce[username].time = new Date().getTime(); bruteForce[username].errors++; } else if (bruteForce[username]) { delete bruteForce[username]; } if (res) { return done(null, username); } else { return done(null, false); } }); } )); passport.serializeUser((user, done) => done(null, user)); passport.deserializeUser((user, done) => done(null, user)); server.app.use(cookieParser()); server.app.use(bodyParser.urlencoded({ extended: true })); server.app.use(bodyParser.json()); server.app.use(session({ secret: settings.secret, saveUninitialized: true, resave: true, cookie: { maxAge: adapter.config.ttl * 1000 }, store: store })); server.app.use(passport.initialize()); server.app.use(passport.session()); server.app.use(flash()); server.app.post('/login', (req, res, next) => { let redirect = '/'; if (req.body.origin) { const parts = req.body.origin.match(/href=(.+)$/); if (parts && parts[1]) { redirect = decodeURIComponent(parts[1]); } } passport.authenticate('local', { successRedirect: redirect, failureRedirect: '/login/index.html' + req.body.origin + (req.body.origin ? '&error' : '?error'), failureFlash: 'Invalid username or password.' })(req, res, next); }); server.app.get('/logout', (req, res) => { req.logout(); res.redirect('/login/index.html'); }); server.app.get('/login/index.html', (req, res) => { loginPage = loginPage || prepareLoginTemplate(); res.contentType('text/html'); res.status(200).send(loginPage); }); // route middleware to make sure a user is logged in server.app.use((req, res, next) => { if (!req.isAuthenticated()) { if (/admin\.\d+\/login-bg\.png(\?.*)?$/.test(req.originalUrl)) { // Read names of files for gong adapter.objects.readFile(adapter.namespace, 'login-bg.png', null, (err, file) => { if (!err && file) { res.set('Content-Type', 'image/png'); res.status(200).send(file); } else { res.status(404).send(); } }); } else if (/^\/login\//.test(req.originalUrl) || /\.ico(\?.*)?$/.test(req.originalUrl)) { return next(); } else { res.redirect('/login/index.html?href=' + encodeURIComponent(req.originalUrl)); } } else { return next(); } }); } else { server.app.get('/login', (req, res) => { res.redirect('/'); }); server.app.get('/logout', (req, res) => { res.redirect('/'); }); } // send log files server.app.get('/log/*', (req, res) => { let parts = req.url.split('/'); parts = parts.splice(2); const transport = parts.shift(); let filename = parts.join('/'); const config = adapter.systemConfig; // detect file log if (config && config.log && config.log.transport) { if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') { path = path || require('path'); if (config.log.transport[transport].filename) { parts = config.log.transport[transport].filename.replace(/\\/g, '/').split('/'); parts.pop(); filename = path.join(parts.join('/'), filename); } else { filename = path.join('log/', filename) ; } if (filename[0] !== '/' && !filename.match(/^\W:/)) { filename = path.normalize(__dirname + '/../../../') + filename; } if (fs.existsSync(filename)) { const stat = fs.lstatSync(filename); if (stat.size > 2 * 1024 * 1024) { res.sendFile(filename); } else { res.send(decorateLogFile(filename)); } return; } } } res.status(404).send('File ' + filename + ' not found'); }); const appOptions = {}; if (settings.cache) { appOptions.maxAge = 30758400000; } if (settings.tmpPathAllow && settings.tmpPath) { server.app.use('/tmp/', express.static(settings.tmpPath, {maxAge: 0})); fileUpload = fileUpload || require('express-fileupload'); server.app.use(fileUpload({ useTempFiles: true, tempFilePath: settings.tmpPath })); server.app.post('/upload', (req, res) => { if (!req.files) { return res.status(400).send('No files were uploaded.'); } // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file let myFile; for (const name in req.files) { if (req.files.hasOwnProperty(name)) { myFile = req.files[name]; break; } } if (myFile) { if (myFile.data && myFile.data.length > 600 * 1024 * 1024) { return res.status(500).send('File is too big. (Max 600MB)'); } // Use the mv() method to place the file somewhere on your server myFile.mv(settings.tmpPath + '/restore.iob', err => { if (err) { res.status(500).send(err); } else { res.send('File uploaded!'); } }); } else { return res.status(500).send('File not uploaded'); } }); } if (!fs.existsSync(__dirname + '/../www')) { server.app.use('/', (req, res) => { res.send('This adapter cannot be installed directly from github.