127.0.0.1:8000 budget / master server / budget_server.js
master

Tree @master (Download .tar.gz)

budget_server.js @masterraw · history · blame

require("dotenv").config();

const utils = require("./utils");
const commandLineArgs = require("command-line-args");
const options = commandLineArgs([{
    name: "no-verbose",
    type: Boolean,
    defaultValue: false
}, {
    name: "save-verbose",
    type: Boolean,
    defaultValue: false
}]);
console.log("CLI OPTIONS:", options);
const logger = require("./utils/log");
logger.set_verbose(options);

const express = require("express");
const app = express();
const exphbs = require("express-handlebars");
const rate_limit = require("express-rate-limit");
const cookie_parser = require("cookie-parser");
const session = require("express-session");
const MySQLStore = require("express-mysql-session")(session);
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");
const time = require("./utils/time");
const cookies = require("./utils/cookies");
const buildRoutes = require("./build_routes")(app);

const PORT = process.env.PORT || 3000;

// Enable if you're behind a reverse proxy (Heroku, Bluemix, AWS ELB, Nginx, etc)
// see https://expressjs.com/en/guide/behind-proxies.html
app.set("trust proxy", true);

app.engine("handlebars", exphbs({
    defaultLayout: "main"
}));
app.set("view engine", "handlebars");

app.use(express.json({
    limit: 1000000
}));
app.use(express.urlencoded({
    extended: true
}));
app.use(cookie_parser());

app.use(express.static("./dist"));
app.use(express.static("./public"));

const passport_log = logger.create_logger("Passport");

// configure passport.js to use the local strategy
passport.use(new LocalStrategy({
    usernameField: "username",
    passwordField: "password",
    session: true
}, async function(username, password, done) {
    try {
        const [user] = await utils.query("SELECT * FROM users WHERE ?", {
            username
        });

        console.log(user);

        if (!user) {
            return done(null, false, {
                message: "Unknown User"
            });
        }

        const bcrypt_result = await bcrypt.compare(password, user.password);
        if (bcrypt_result) {
            return done(null, user);
        }
        return done(null, false, {
            message: "Incorrect Password"
        });
    } catch (error) {
        console.log(error);
        passport_log.general.error(error);
        done(error);
    }
}));

passport.serializeUser(function(user, done) {
    process.nextTick(function() {
        done(null, {
            user_id: user.user_id,
            username: user.username
        });
    });
});

passport.deserializeUser(function(user, done) {
    process.nextTick(function() {
        return done(null, user);
    });
});

app.use(session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    store: new MySQLStore({
        connectionLimit: 1,
        clearExpired: true,
        expiration: time.one_day,
        checkExpirationInterval: time.one_day * 0.25
    }, utils.MYSQL_POOL),
    proxy: !process.env.DEBUG,
    cookie: cookies.options()
}));

app.use(passport.session());

app.use(function(req, res, next) {
    res.locals.ip = req.header("X-Forwarded-For") || "localhost";
    res.locals.log = logger.create_logger(res.locals.ip, req.method, req.url);
    res.locals.log.mark(req);
    next();
});

// Use a map because we want to maintain order of keys
let defaultMiddleware = new Map();
defaultMiddleware.set("rate_limit", {
    default: true,
    func: rate_limit({
        windowMs: 15 * 60 * 1000, // 15 minutes
        max: 100 // limit each IP to 100 requests per windowMs
    })
});
defaultMiddleware.set("harsh_rate_limit", {
    default: false,
    func: rate_limit({
        windowMs: 60 * 60 * 1000, // 1 hour
        max: 100 // limit each IP to 100 requests per windowMs
    })
});
defaultMiddleware.set("auth", {
    default: true,
    func: function(req, res, next) {
        if (!req.isAuthenticated || !req.isAuthenticated()) {
            if (req.session) {
                req.session.returnTo = req.originalUrl || req.url;
            }

            let target = "/login";
            let searchIndex = req.url.indexOf("?");
            if (searchIndex >= 0) {
                target += req.url.substr(req.url.indexOf("?"));
            }

            res.locals.log.general.warning("Client has no session, redirect to", target);
            return res.redirect(target);
        }
        next();
    }
});

buildRoutes("/api/", defaultMiddleware, [
    "budget",
    "log"
], true);

buildRoutes("/api/", defaultMiddleware, [
    "base_routes"
], false);

buildRoutes("/api/account/", defaultMiddleware, [
    "login",
    "logout",
    "update"
], true);

buildRoutes("/", defaultMiddleware, [
    "html"
], false);

// Add fallbacks for unknown requests
app.get("/*", function(req, res) {
    res.render("error", {
        title: ' - Not Found'
    });
});

app.use(function(error, req, res, next) {
    res.header("Content-Type", 'application/json');
    const status = error.status || 400;
    res.status(status).send(error.message);
});

app.use(function(req, res, next) {
    res.status(400);
    res.send('invalid path');
});

// Make sure we have directories or everything will just fucking crash
utils.fs.mkdir_if_not_exists(logger.LOGS_ROOT).then(function() {
    return logger.process_log_queue();
}).then(function() {
    return utils.query("SELECT value FROM balance");
}).then(function(rows) {
    if (!rows.length) {
        return utils.query("INSERT INTO balance SET name=?", ["Checking"]);
    }
}).then(function() {
    time.start_daily_interval();

    app.listen(PORT, function() {
        console.log("Server running on port " + PORT);
    });
}).catch(console.error);

process.on("SIGINT", function(code) {
    console.log(`About to exit with code: ${code}`);
    logger.process_log_queue().then(function() {
        process.exit(0);
    }).catch(function(error) {
        console.error(error);
        process.exit(1);
    });
});