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);
});
});