From 6ed17497c5af494cde80cf328bdc74acb3aabaf6 Mon Sep 17 00:00:00 2001 From: Mystikfluu Date: Fri, 19 Aug 2022 19:29:14 +0200 Subject: [PATCH] rewrite to a module --- extra_modules/SHA.js | 35 +- extra_modules/getip.js | 19 +- extra_modules/unsign.js | 67 +- extra_modules/xor.js | 26 +- package.json | 1 + routes/api/all.js | 106 +-- routes/api/dms/PersonalMessages.js | 126 ++-- routes/api/dms/post.js | 202 +++--- routes/api/options.js | 73 +- routes/api/post.js | 190 +++-- routes/api/settingshandler.js | 97 ++- server.js | 84 ++- server_esm.js | 1036 ++++++++++++++++++++++++++++ 13 files changed, 1522 insertions(+), 540 deletions(-) create mode 100644 server_esm.js diff --git a/extra_modules/SHA.js b/extra_modules/SHA.js index 67b8772..3305760 100644 --- a/extra_modules/SHA.js +++ b/extra_modules/SHA.js @@ -1,5 +1,4 @@ -const crypto = require('crypto'); - +import crypto from "crypto"; /** * hashes with the secure hashing algorithm 256 * @param {string} str string to hash @@ -7,19 +6,21 @@ const crypto = require('crypto'); * @param {number} num amount of times to hash, defaults to 1 * @returns {string} base64 digested hash */ -function SHA256(str,salt,num) { - if(!num && num!==0)num=1; - if(!str)return; - let ret = str; - for (let i = 0; i < num; i++) { - ret = crypto - .createHash("sha256") - .update(ret+salt) - .digest("base64"); - } - return ret; -} - -module.exports = { - SHA256: SHA256 +function SHA256(str, salt, num) { + if (!num && num !== 0) + num = 1; + if (!str) + return; + let ret = str; + for (let i = 0; i < num; i++) { + ret = crypto + .createHash("sha256") + .update(ret + salt) + .digest("base64"); + } + return ret; } +export { SHA256 }; +export default { + SHA256: SHA256 +}; diff --git a/extra_modules/getip.js b/extra_modules/getip.js index d8211e3..c841a01 100644 --- a/extra_modules/getip.js +++ b/extra_modules/getip.js @@ -1,15 +1,14 @@ -const fs = require('fs'); -const config = JSON.parse(fs.readFileSync("server_config.json")) - +import fs from "fs"; +const config = JSON.parse(fs.readFileSync("server_config.json")); /** * gets ip of a request - * @param {request} req + * @param {request} req * @returns ip of the given request, after taking preferred headers into account */ - function getIP(req) { +function getIP(req) { let ip = req.socket.remoteAddress; - if(req.headers[config.preferred_ip_header] != undefined && ip == config.only_prefer_when_ip)ip = req.headers[config.preferred_ip_header] - return ip - } - -module.exports = getIP \ No newline at end of file + if (req.headers[config.preferred_ip_header] != undefined && ip == config.only_prefer_when_ip) + ip = req.headers[config.preferred_ip_header]; + return ip; +} +export default getIP; diff --git a/extra_modules/unsign.js b/extra_modules/unsign.js index 7143f24..9f109ea 100644 --- a/extra_modules/unsign.js +++ b/extra_modules/unsign.js @@ -1,7 +1,7 @@ -const signature = require('cookie-signature') -const fs = require('fs'); -const cookiesecret = fs.readFileSync("cookiesecret.txt").toString() -const getIP = require("./getip.js") +import * as signature from "cookie-signature"; +import fs from "fs"; +import getIP from "./getip.js"; +const cookiesecret = fs.readFileSync("cookiesecret.txt").toString(); /** * usignes a string * @param {string} text text to unsign @@ -9,40 +9,41 @@ const getIP = require("./getip.js") * @param {response} res response object * @return {string/boolean} unsigned text, or if unsigning was unsuccessful, false */ -function unsign(text,req,res) { - let ip = getIP(req) - let unsigned = signature.unsign(text,cookiesecret+ip) - if(!unsigned) { - return false - } - return unsigned +function unsign(text, req, res) { + let ip = getIP(req); + let unsigned = signature.unsign(text, cookiesecret + ip); + if (!unsigned) { + return false; + } + return unsigned; } - /** * unsignes the auth cookie of a request, also sends json response if auth cookie was invalid * @param {request} req request object * @param {response} res response object * @return {string/boolean} unsigned cookie, or if unsigning was unsuccessful, false */ -function getunsigned(req,res) { - let cookie = req.cookies.AUTH_COOKIE - if(!cookie){ - res.status(400) - res.json({"error":"you are not logged in! (no cookie)"}) - return - } - let unsigned = unsign(cookie,req,res) - if(!unsigned){ - try { - res.status(400) - res.json({"error":"Bad auth cookie set"}) - } catch (ignored) {} //sometimes it errors, gotta debug soon - return false - } - return decodeURIComponent(unsigned) -} - -module.exports = { - unsign: unsign, - getunsigned: getunsigned +function getunsigned(req, res) { + let cookie = req.cookies.AUTH_COOKIE; + if (!cookie) { + res.status(400); + res.json({ "error": "you are not logged in! (no cookie)" }); + return; + } + let unsigned = unsign(cookie, req, res); + if (!unsigned) { + try { + res.status(400); + res.json({ "error": "Bad auth cookie set" }); + } + catch (ignored) { } //sometimes it errors, gotta debug soon + return false; + } + return decodeURIComponent(unsigned); } +export { unsign }; +export { getunsigned }; +export default { + unsign: unsign, + getunsigned: getunsigned +}; diff --git a/extra_modules/xor.js b/extra_modules/xor.js index a4b4ba7..40719ac 100644 --- a/extra_modules/xor.js +++ b/extra_modules/xor.js @@ -1,27 +1,19 @@ function XOR_hex(a, b) { - var res = "", - i = a.length, - j = b.length; - while (i-->0 && j-->0) + var res = "", i = a.length, j = b.length; + while (i-- > 0 && j-- > 0) res = (parseInt(a.charAt(i), 16) ^ parseInt(b.charAt(j), 16)).toString(16) + res; return res; } - -function hexEncode(a){ +function hexEncode(a) { let hex; - let result = ""; - for (let i=0; i 100 || otherperson=="") { - res.status(400).json({"error": "invalid otherperson given"}) - return - } - - const columns = [ - "dms_user_name","dms_text","dms_time","dms_special_text","dms_id","dms_from_bot","dms_reply_id" - ] - //dms_user_name = sender - //dms_receiver = receiver - //if (sender == current and receiver == other) or (receiver == current and sender == other) - let sql = `select ${columns.join(",")} from ipost.dms where ((dms_receiver = ? and dms_user_name = ?) or (dms_receiver = ? and dms_user_name = ?)) order by dms_id desc;` - con.query(sql, [otherperson,encodeURIComponent(res.locals.username),encodeURIComponent(res.locals.username),otherperson], function (err, result) { - if (err) throw err; - res.json(result) - }); - }) - - router.get("/api/dms/conversations", async function(req,res) { - res.set("Access-Control-Allow-Origin","*") - - const columns = [ - "dms_user_name","dms_receiver" - ] - - let uriencusername = encodeURIComponent(res.locals.username) - - let sql = `select ${columns.join(",")} from ipost.dms where ((dms_receiver = ?) or (dms_user_name = ?)) group by dms_receiver,dms_user_name;` - con.query(sql, [uriencusername,uriencusername], function (err, result) { - if (err) throw err; - res.json(result) - }); - }) - - router.get("/api/dms/encrypt.js", async function(req,res) { - res.set("Access-Control-Allow-Origin","*") - res.send(web_version()) - }) - - // - router.get("/api/dms/getDM", async function(req,res) { - res.set("Access-Control-Allow-Origin","*") - let arg = req.query.id - let uriencusername = encodeURIComponent(res.locals.username) - let sql = `select dms_user_name,dms_text,dms_time,dms_special_text,dms_id,dms_from_bot,dms_reply_id,dms_receiver from ipost.dms where dms_id=? and (dms_user_name=? or dms_receiver=?);` - con.query(sql, [arg,uriencusername,uriencusername], function (err, result) { - if (err) throw err; - if(result[0]) { +//const web_version = require("unsafe_encrypt").web_version +import { web_version } from "unsafe_encrypt"; +export const setup = function (router, con, server) { + router.get("/api/getPersonalPosts", async function (req, res) { + res.set("Access-Control-Allow-Origin", ""); + let otherperson = encodeURIComponent(req.query.otherperson || ""); + if (typeof otherperson != "string" || otherperson.length > 100 || otherperson == "") { + res.status(400).json({ "error": "invalid otherperson given" }); + return; + } + const columns = [ + "dms_user_name", "dms_text", "dms_time", "dms_special_text", "dms_id", "dms_from_bot", "dms_reply_id" + ]; + //dms_user_name = sender + //dms_receiver = receiver + //if (sender == current and receiver == other) or (receiver == current and sender == other) + let sql = `select ${columns.join(",")} from ipost.dms where ((dms_receiver = ? and dms_user_name = ?) or (dms_receiver = ? and dms_user_name = ?)) order by dms_id desc;`; + con.query(sql, [otherperson, encodeURIComponent(res.locals.username), encodeURIComponent(res.locals.username), otherperson], function (err, result) { + if (err) + throw err; + res.json(result); + }); + }); + router.get("/api/dms/conversations", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + const columns = [ + "dms_user_name", "dms_receiver" + ]; + let uriencusername = encodeURIComponent(res.locals.username); + let sql = `select ${columns.join(",")} from ipost.dms where ((dms_receiver = ?) or (dms_user_name = ?)) group by dms_receiver,dms_user_name;`; + con.query(sql, [uriencusername, uriencusername], function (err, result) { + if (err) + throw err; + res.json(result); + }); + }); + router.get("/api/dms/encrypt.js", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + res.send(web_version()); + }); + // + router.get("/api/dms/getDM", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + let arg = req.query.id; + let uriencusername = encodeURIComponent(res.locals.username); + let sql = `select dms_user_name,dms_text,dms_time,dms_special_text,dms_id,dms_from_bot,dms_reply_id,dms_receiver from ipost.dms where dms_id=? and (dms_user_name=? or dms_receiver=?);`; + con.query(sql, [arg, uriencusername, uriencusername], function (err, result) { + if (err) + throw err; + if (result[0]) { res.set('Cache-Control', 'public, max-age=2592000'); //cache it for one month-ish - res.json(result[0]) - } else { - res.json({"error":"there is no such dm!"}) - } - }); - }) - } -} \ No newline at end of file + res.json(result[0]); + } + else { + res.json({ "error": "there is no such dm!" }); + } + }); + }); +}; +export default { + setup +}; diff --git a/routes/api/dms/post.js b/routes/api/dms/post.js index 4255f89..ed68448 100644 --- a/routes/api/dms/post.js +++ b/routes/api/dms/post.js @@ -1,106 +1,96 @@ -const xor = require("../../../extra_modules/xor.js") - -module.exports = { - "setup": function(router,con,server) { - - const PIDS = {} //[pid]: true/"already_used" - - router.get("/api/dms/pid", async function(req,res) { - res.set("Access-Control-Allow-Origin","*") - let pid = server.genstring(10) //collision chance is low enough, but we'll check anyways - while (PIDS[pid] != undefined){ - pid = server.genstring(10) - console.log(5,"pid collision"); - } - PIDS[pid] = true - setTimeout(function() { - PIDS[pid]=undefined - },40000) - res.json({"pid":pid}) - }) - - router.post("/api/dms/post", async function(req,res) { - if(!req.body.message) { - res.json({"error":"no message to post"}) - return - } - if((typeof req.body.message) != "string") { - res.json({"error":"no message to post"}) - return - } - if((typeof req.body.pid) != "string") { - res.json({"error":"no pid given"}) - return - } - if(req.body.pid.length != 10 || PIDS[req.body.pid] !== true) { - res.json({"error":"invalid pid given"}) - return - } - PIDS[req.body.pid] = "already_used" - - let reply_id - if(!req.body.reply_id || req.body.reply_id < 0) { - reply_id = 0 - } else { - reply_id = req.body.reply_id - } - - if((typeof reply_id) != "number") { - res.json({"error":"no valid reply id given"}) - return - } - - if(req.body.message.length > 1000) { - res.json({"error":"message too long"}) - return - } - - req.body.message = encodeURIComponent(req.body.message.trim()) - - if(req.body.message.length > 3000) { - res.json({"error":"message too long"}) //check again after URI encoding it - return - } - - req.body.receiver = encodeURIComponent(req.body.receiver||"") - if(req.body.receiver == "" || req.body.receiver == encodeURIComponent(res.locals.username) || req.body.receiver.length > 100) { - res.status(400).json({"error": "invalid receiver given"}) - return - } - let otherperson = req.body.receiver - - if(!req.body.message) { - res.json({"error":"no message to post"}) - return - } - - let sql = `insert into ipost.dms (dms_user_name,dms_text,dms_time,dms_receiver,dms_from_bot,dms_reply_id) values (?,?,?,?,?,?);` - let values = [encodeURIComponent(res.locals.username),req.body.message,Date.now(),otherperson,res.locals.isbot,reply_id] - con.query(sql, values, function (err, result) { - if (err) throw err; - // let post_obj = { - // post_user_name: encodeURIComponent(res.locals.username), - // post_text: req.body.message, - // post_time: Date.now(), - // post_special_text: "", - // post_receiver_name: req.body.receiver, - // post_from_bot: res.locals.isbot, - // post_reply_id: reply_id - // } - - // let message = { - // message: "new_post", - // data: post_obj - // } - // let messagestr = JSON.stringify(message) - // server.wss.clients.forEach(function(ws) { - // if(ws.channel == decodeURIComponent(req.body.receiver)) { - // ws.send(messagestr) - // } - // }); - res.json({"success":"successfully posted dm"}) - console.log(5,`posted new dm by ${res.locals.username} to ${otherperson} : ${xor(encodeURIComponent(res.locals.username),otherperson)}`); - }); - }) - } -} \ No newline at end of file +import xor from "../../../extra_modules/xor.js"; +export const setup = function (router, con, server) { + const PIDS = {}; //[pid]: true/"already_used" + router.get("/api/dms/pid", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + let pid = server.genstring(10); //collision chance is low enough, but we'll check anyways + while (PIDS[pid] != undefined) { + pid = server.genstring(10); + console.log(5, "pid collision"); + } + PIDS[pid] = true; + setTimeout(function () { + PIDS[pid] = undefined; + }, 40000); + res.json({ "pid": pid }); + }); + router.post("/api/dms/post", async function (req, res) { + if (!req.body.message) { + res.json({ "error": "no message to post" }); + return; + } + if ((typeof req.body.message) != "string") { + res.json({ "error": "no message to post" }); + return; + } + if ((typeof req.body.pid) != "string") { + res.json({ "error": "no pid given" }); + return; + } + if (req.body.pid.length != 10 || PIDS[req.body.pid] !== true) { + res.json({ "error": "invalid pid given" }); + return; + } + PIDS[req.body.pid] = "already_used"; + let reply_id; + if (!req.body.reply_id || req.body.reply_id < 0) { + reply_id = 0; + } + else { + reply_id = req.body.reply_id; + } + if ((typeof reply_id) != "number") { + res.json({ "error": "no valid reply id given" }); + return; + } + if (req.body.message.length > 1000) { + res.json({ "error": "message too long" }); + return; + } + req.body.message = encodeURIComponent(req.body.message.trim()); + if (req.body.message.length > 3000) { + res.json({ "error": "message too long" }); //check again after URI encoding it + return; + } + req.body.receiver = encodeURIComponent(req.body.receiver || ""); + if (req.body.receiver == "" || req.body.receiver == encodeURIComponent(res.locals.username) || req.body.receiver.length > 100) { + res.status(400).json({ "error": "invalid receiver given" }); + return; + } + let otherperson = req.body.receiver; + if (!req.body.message) { + res.json({ "error": "no message to post" }); + return; + } + let sql = `insert into ipost.dms (dms_user_name,dms_text,dms_time,dms_receiver,dms_from_bot,dms_reply_id) values (?,?,?,?,?,?);`; + let values = [encodeURIComponent(res.locals.username), req.body.message, Date.now(), otherperson, res.locals.isbot, reply_id]; + con.query(sql, values, function (err, result) { + if (err) + throw err; + // let post_obj = { + // post_user_name: encodeURIComponent(res.locals.username), + // post_text: req.body.message, + // post_time: Date.now(), + // post_special_text: "", + // post_receiver_name: req.body.receiver, + // post_from_bot: res.locals.isbot, + // post_reply_id: reply_id + // } + // let message = { + // message: "new_post", + // data: post_obj + // } + // let messagestr = JSON.stringify(message) + // server.wss.clients.forEach(function(ws) { + // if(ws.channel == decodeURIComponent(req.body.receiver)) { + // ws.send(messagestr) + // } + // }); + res.json({ "success": "successfully posted dm" }); + console.log(5, `posted new dm by ${res.locals.username} to ${otherperson} : ${xor(encodeURIComponent(res.locals.username), otherperson)}`); + }); + }); +}; +export default { + setup +}; diff --git a/routes/api/options.js b/routes/api/options.js index 53ab37a..3ac8b76 100644 --- a/routes/api/options.js +++ b/routes/api/options.js @@ -1,57 +1,18 @@ -function allowAllTraffic(router,str,type) { - router.options(str,async function(req,res,next) { - res.set("Access-Control-Allow-Origin","*") //we'll allow it for now - res.set("Access-Control-Allow-Methods",type || "GET") - res.set("Access-Control-Allow-Headers","Content-Type") - res.status(200).send("") - }) +function allowAllTraffic(router, str, type) { + router.options(str, async function (req, res, next) { + res.set("Access-Control-Allow-Origin", "*"); //we'll allow it for now + res.set("Access-Control-Allow-Methods", type || "GET"); + res.set("Access-Control-Allow-Headers", "Content-Type"); + res.status(200).send(""); + }); } - -module.exports = { - "setup": function(router,con,server) { - // router.options("/api/pid",async function(req,res,next) { - // res.set("Access-Control-Allow-Origin","*") //we'll allow it for now - // res.set("Access-Control-Allow-Methods","GET") - // res.set("Access-Control-Allow-Headers","Content-Type") - // res.status(200).send("") - // }) - - // router.options("/api/post",async function(req,res,next) { - // res.set("Access-Control-Allow-Origin","*") //we'll allow it for now - // res.set("Access-Control-Allow-Methods","POST") - // res.set("Access-Control-Allow-Headers","Content-Type") - // res.status(200).send("") - // }) - - // router.options("/api/getotheruser",async function(req,res,next) { - // res.set("Access-Control-Allow-Origin","*") //we'll allow it for now - // res.set("Access-Control-Allow-Methods","GET") - // res.set("Access-Control-Allow-Headers","Content-Type") - // res.status(200).send("") - // }) - - // router.options("/api/getPost",async function(req,res,next) { - // res.set("Access-Control-Allow-Origin","*") //we'll allow it for now - // res.set("Access-Control-Allow-Methods","GET") - // res.set("Access-Control-Allow-Headers","Content-Type") - // res.status(200).send("") - // }) - // - // router.options("/api/getPostsLowerThan",async function(req,res,next) { - // res.set("Access-Control-Allow-Origin","*") //we'll allow it for now - // res.set("Access-Control-Allow-Methods","GET") - // res.set("Access-Control-Allow-Headers","Content-Type") - // res.status(200).send("") - // }) - - allowAllTraffic(router,"/api/pid") - allowAllTraffic(router,"/api/post","POST") - allowAllTraffic(router,"/api/getotheruser") - allowAllTraffic(router,"/api/getPost") - allowAllTraffic(router,"/api/getPostsLowerThan") - allowAllTraffic(router,"/api/settings") - allowAllTraffic(router,"/api/settings","POST") - - - } -} \ No newline at end of file +function setup(router, con, server) { + allowAllTraffic(router, "/api/pid"); + allowAllTraffic(router, "/api/post", "POST"); + allowAllTraffic(router, "/api/getotheruser"); + allowAllTraffic(router, "/api/getPost"); + allowAllTraffic(router, "/api/getPostsLowerThan"); + allowAllTraffic(router, "/api/settings"); + allowAllTraffic(router, "/api/settings", "POST"); +} +export { setup }; diff --git a/routes/api/post.js b/routes/api/post.js index 7ebd966..6ddcc8b 100644 --- a/routes/api/post.js +++ b/routes/api/post.js @@ -1,101 +1,93 @@ -module.exports = { - "setup": function(router,con,server) { - - const PIDS = {} //[pid]: true/"already_used" - - router.get("/api/pid", async function(req,res) { - res.set("Access-Control-Allow-Origin","*") - let pid = server.genstring(10) //collision chance is low enough, but we'll check anyways - while (PIDS[pid] != undefined){ - pid = server.genstring(10) - console.log(5,"pid collision"); - } - PIDS[pid] = true - setTimeout(function() { - PIDS[pid]=undefined - },40000) - res.json({"pid":pid}) - }) - - router.post("/api/post", async function(req,res) { - if(!req.body.message) { - res.json({"error":"no message to post"}) - return - } - if((typeof req.body.message) != "string") { - res.json({"error":"no message to post"}) - return - } - if((typeof req.body.pid) != "string") { - res.json({"error":"no pid given"}) - return - } - if(req.body.pid.length != 10 || PIDS[req.body.pid] !== true) { - res.json({"error":"invalid pid given"}) - return - } - PIDS[req.body.pid] = "already_used" - - let reply_id - if(!req.body.reply_id || req.body.reply_id < 0) { - reply_id = 0 - } else { - reply_id = req.body.reply_id - } - - if((typeof req.body.reply_id) != "number") { - res.json({"error":"no valid reply id given"}) - return - } - - if(req.body.message.length > 1000) { - res.json({"error":"message too long"}) - return - } - - req.body.message = encodeURIComponent(req.body.message.trim()) - - if(req.body.message.length > 3000) { - res.json({"error":"message too long"}) //check again after URI encoding it - return - } - - req.body.receiver = encodeURIComponent(req.body.receiver||"") - if(req.body.receiver == "")req.body.receiver="everyone" - - if(!req.body.message) { - res.json({"error":"no message to post"}) - return - } - - let sql = `insert into ipost.posts (post_user_name,post_text,post_time,post_receiver_name,post_from_bot,post_reply_id) values (?,?,?,?,?,?);` - let values = [encodeURIComponent(res.locals.username),req.body.message,Date.now(),req.body.receiver,res.locals.isbot,reply_id] - con.query(sql, values, function (err, result) { - if (err) throw err; - let post_obj = { - post_user_name: encodeURIComponent(res.locals.username), - post_text: req.body.message, - post_time: Date.now(), - post_special_text: "", - post_receiver_name: req.body.receiver, - post_from_bot: res.locals.isbot, - post_reply_id: reply_id +export const setup = function (router, con, server) { + const PIDS = {}; //[pid]: true/"already_used" + router.get("/api/pid", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + let pid = server.genstring(10); //collision chance is low enough, but we'll check anyways + while (PIDS[pid] != undefined) { + pid = server.genstring(10); + console.log(5, "pid collision"); + } + PIDS[pid] = true; + setTimeout(function () { + PIDS[pid] = undefined; + }, 40000); + res.json({ "pid": pid }); + }); + router.post("/api/post", async function (req, res) { + if (!req.body.message) { + res.json({ "error": "no message to post" }); + return; + } + if ((typeof req.body.message) != "string") { + res.json({ "error": "no message to post" }); + return; + } + if ((typeof req.body.pid) != "string") { + res.json({ "error": "no pid given" }); + return; + } + if (req.body.pid.length != 10 || PIDS[req.body.pid] !== true) { + res.json({ "error": "invalid pid given" }); + return; + } + PIDS[req.body.pid] = "already_used"; + let reply_id; + if (!req.body.reply_id || req.body.reply_id < 0) { + reply_id = 0; + } + else { + reply_id = req.body.reply_id; + } + if ((typeof req.body.reply_id) != "number") { + res.json({ "error": "no valid reply id given" }); + return; + } + if (req.body.message.length > 1000) { + res.json({ "error": "message too long" }); + return; + } + req.body.message = encodeURIComponent(req.body.message.trim()); + if (req.body.message.length > 3000) { + res.json({ "error": "message too long" }); //check again after URI encoding it + return; + } + req.body.receiver = encodeURIComponent(req.body.receiver || ""); + if (req.body.receiver == "") + req.body.receiver = "everyone"; + if (!req.body.message) { + res.json({ "error": "no message to post" }); + return; + } + let sql = `insert into ipost.posts (post_user_name,post_text,post_time,post_receiver_name,post_from_bot,post_reply_id) values (?,?,?,?,?,?);`; + let values = [encodeURIComponent(res.locals.username), req.body.message, Date.now(), req.body.receiver, res.locals.isbot, reply_id]; + con.query(sql, values, function (err, result) { + if (err) + throw err; + let post_obj = { + post_user_name: encodeURIComponent(res.locals.username), + post_text: req.body.message, + post_time: Date.now(), + post_special_text: "", + post_receiver_name: req.body.receiver, + post_from_bot: res.locals.isbot, + post_reply_id: reply_id + }; + let message = { + message: "new_post", + data: post_obj + }; + let messagestr = JSON.stringify(message); + let channel = decodeURIComponent(req.body.receiver); + server.wss.clients.forEach(async function (ws) { + if (ws.channel == channel) { + ws.send(messagestr); } - - let message = { - message: "new_post", - data: post_obj - } - let messagestr = JSON.stringify(message) - let channel = decodeURIComponent(req.body.receiver) - server.wss.clients.forEach(async function(ws) { - if(ws.channel == channel) { - ws.send(messagestr) - } - }); - res.json({"success":"successfully posted message"}) - console.log(5,`posted new message by ${res.locals.username} : ${req.body.message}`); }); - }) - } -} \ No newline at end of file + res.json({ "success": "successfully posted message" }); + console.log(5, `posted new message by ${res.locals.username} : ${req.body.message}`); + }); + }); +}; +export default { + setup +}; diff --git a/routes/api/settingshandler.js b/routes/api/settingshandler.js index 0e39ef7..6ab6769 100644 --- a/routes/api/settingshandler.js +++ b/routes/api/settingshandler.js @@ -1,55 +1,48 @@ -const allowed_settings = { //"settingname":["validtypes"] +const allowed_settings = { "ACCR": ["boolean"] -} - -module.exports = { - "setup": function(router,con,server) { - router.get("/api/settings",function(req,res) { - res.json(res.locals.settings) - }) - - router.post("/api/settings",function(req,res) { - - if(!req.body.setting) { - res.json({"error":"no setting to change"}) - return +}; +export const setup = function (router, con, server) { + router.get("/api/settings", function (req, res) { + res.json(res.locals.settings); + }); + router.post("/api/settings", function (req, res) { + if (!req.body.setting) { + res.json({ "error": "no setting to change" }); + return; + } + if ((typeof req.body.setting) != "string") { + res.json({ "error": "no setting to change" }); + return; + } + let types = allowed_settings[req.body.setting]; + let allowed = false; + let got = typeof req.body.value; + for (let index = 0; index < types.length; index++) { + if (types[index] == got) { + allowed = true; + break; } - if((typeof req.body.setting) != "string") { - res.json({"error":"no setting to change"}) - return + } + if (!allowed) { + console.log(5, "incorrect type given, received, expected", typeof req.body.value, allowed_settings[req.body.setting]); + res.json({ "error": "no new setting value given" }); + return; + } + let setting_to_change = req.body.setting; + let setting_new_value = req.body.value; + res.locals.settings[setting_to_change] = setting_new_value; + console.log(5, "changing settings", setting_to_change, setting_new_value, res.locals.settings); + let sql = "update ipost.users set User_Settings=? where User_Name=?"; + let values = [JSON.stringify(res.locals.settings), res.locals.username]; + con.query(sql, values, function (err, result) { + if (err) { + res.json({ "status": "error", "code": err }); + return; } - - let types = allowed_settings[req.body.setting] - let allowed = false - let got = typeof req.body.value - for (let index = 0; index < types.length; index++) { - if(types[index] == got) { - allowed = true; - break; - } - } - if(!allowed) { - console.log(5,"incorrect type given, received, expected", typeof req.body.value, allowed_settings[req.body.setting]) - res.json({"error":"no new setting value given"}) - return - } - - let setting_to_change = req.body.setting - let setting_new_value = req.body.value - - res.locals.settings[setting_to_change] = setting_new_value - - console.log(5,"changing settings", setting_to_change, setting_new_value, res.locals.settings) - - let sql = "update ipost.users set User_Settings=? where User_Name=?" - let values = [JSON.stringify(res.locals.settings),res.locals.username] - con.query(sql, values, function (err, result) { - if(err) { - res.json({"status":"error","code":err}) - return - } - res.json({"status":"success"}) - }) - }) - } -} + res.json({ "status": "success" }); + }); + }); +}; +export default { + setup +}; diff --git a/server.js b/server.js index a784d79..b17abcb 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ -const fs = require("fs"); -const util = require('util'); +import "fs" +import {format} from "util" /** @@ -14,7 +14,7 @@ function ensureExists(path, mask, cb) { cb = mask; mask = 0o744; } - fs.mkdir(path, mask, function(err) { + mkdir(path, mask, function(err) { if (err) { if (err.code == 'EEXIST') cb(null); // Ignore the error if the folder already exists else cb(err); // Something else went wrong @@ -22,7 +22,7 @@ function ensureExists(path, mask, cb) { }); } -const config = JSON.parse(fs.readFileSync("server_config.json")) +const config = JSON.parse(readFileSync("server_config.json")) const time = Date.now() const original_log = console.log @@ -41,13 +41,13 @@ function log_info(level, ...info) { level = 5 } if(config["logs"] && config["logs"]["level"] && config["logs"]["level"] >= level) { - let tolog = `[INFO] [${Date.now()}] : ${util.format(text)} \n` + let tolog = `[INFO] [${Date.now()}] : ${format(text)} \n` original_log(tolog) //still has some nicer colors ensureExists(__dirname + '/logs/', function(err) { if(err) { process.stderr.write(tolog) //just write it to stderr } else { - fs.appendFile(__dirname+"/logs/"+time,tolog,function(err){ + appendFile(__dirname+"/logs/"+time,tolog,function(err){ if(err){ process.stderr.write(err) } @@ -67,7 +67,6 @@ console.log(5,"starting up") const http = require('http'); const https = require('https'); -const crypto = require("crypto"); const express = require("express"); const useragent = require('express-useragent'); const fileUpload = require('express-fileupload'); @@ -93,12 +92,12 @@ const con = mysql.createPool({ connectionLimit : config.mysql.connections, host: config.mysql.host, user: config.mysql.user, - password: fs.readFileSync(config.mysql.password_file).toString() + password: readFileSync(config.mysql.password_file).toString() }); const dir = __dirname + "/" -const cookiesecret = fs.readFileSync("cookiesecret.txt").toString() +const cookiesecret = readFileSync("cookiesecret.txt").toString() const SHA = require("./extra_modules/SHA.js") @@ -419,19 +418,34 @@ var commonfunctions = { genstring } -const toLoad = [ - "api/options.js", - "api/all.js", - "api/settingshandler.js", - "api/post.js", - "api/dms/PersonalMessages.js", - "api/dms/post.js", - -] +import {setup as optionssetup} from "./routes/api/options.js" +import {setup as allsetup} from "./routes/api/all.js" +import {setup as settingshandlersetup} from "./routes/api/settingshandler.js" +import {setup as postsetup} from "./routes/api/post.js" +import {setup as dmsPersonalMessagessetup} from "./routes/api/dms/PersonalMessages.js" +import {setup as dmspostsetup} from "./routes/api/dms/post.js" -for (let i = 0; i < toLoad.length; i++) { - require("./routes/"+toLoad[i]).setup(router,con,commonfunctions) -} + +optionssetup(router,con,commonfunctions) +allsetup(router,con,commonfunctions) +settingshandlersetup(router,con,commonfunctions) +postsetup(router,con,commonfunctions) +dmsPersonalMessagessetup(router,con,commonfunctions) +dmspostsetup(router,con,commonfunctions) + +// const toLoad = [ +// "api/options.js", +// "api/all.js", +// "api/settingshandler.js", +// "api/post.js", +// "api/dms/PersonalMessages.js", +// "api/dms/post.js", + +// ] + +// for (let i = 0; i < toLoad.length; i++) { +// require("./routes/"+toLoad[i]).setup(router,con,commonfunctions) +// } // let options = require("./routes/api/options.js") // options.setup(router,con,commonfunctions) @@ -494,10 +508,10 @@ router.post("/api/setavatar",function(req,res) { return res.status(500).json({"error":"there's been an internal server error."}) } if(res.locals.avatar) { - fs.unlinkSync(avatars + res.locals.avatar) + unlinkSync(avatars + res.locals.avatar) } let filename = genstring(96) + ".png" - while(fs.existsSync(avatars + "/" + filename) || filename == ".png") { + while(existsSync(avatars + "/" + filename) || filename == ".png") { console.log(5,"already have file: ",filename); original_log("already have file: ",filename) filename = genstring(96) + ".png" @@ -513,7 +527,7 @@ router.post("/api/setavatar",function(req,res) { con.query(sql, [filename,encodeURIComponent(res.locals.username)], function (err, result) { if (err) throw err; res.json({"success":"updated avatar"}) - fs.unlinkSync(avatars+"temp_"+filename) + unlinkSync(avatars+"temp_"+filename) }); }) }) @@ -769,7 +783,7 @@ router.get("/users/*", async function(req,res) { router.get("/css/*", (request, response) => { if(!increaseUSERCall(request,response))return - if(fs.existsSync(__dirname + request.originalUrl)){ + if(existsSync(__dirname + request.originalUrl)){ response.sendFile(__dirname + request.originalUrl); } else { response.status(404).send("no file with that name found") @@ -779,7 +793,7 @@ router.get("/css/*", (request, response) => { router.get("/js/*", (request, response) => { if(!increaseUSERCall(request,response))return - if(fs.existsSync(__dirname + request.originalUrl)){ + if(existsSync(__dirname + request.originalUrl)){ response.sendFile(__dirname + request.originalUrl); } else { response.status(404).send("no file with that name found") @@ -789,7 +803,7 @@ router.get("/js/*", (request, response) => { router.get("/images/*", (request, response) => { if(!increaseUSERCall(request,response))return - if(fs.existsSync(__dirname + request.originalUrl)){ + if(existsSync(__dirname + request.originalUrl)){ response.sendFile(__dirname + request.originalUrl); } else { response.status(404).send("no file with that name found") @@ -801,10 +815,10 @@ router.get("/avatars/*", (request, response, next) => { if(!increaseUSERCall(request,response))return response.set('Cache-Control', 'public, max-age=2592000'); //cache it for one month-ish let originalUrl = request.originalUrl.split("?").shift() - if(fs.existsSync(dir + originalUrl + ".png")) { + if(existsSync(dir + originalUrl + ".png")) { return response.sendFile(dir + originalUrl + ".png"); } - if(fs.existsSync(dir + originalUrl)) { + if(existsSync(dir + originalUrl)) { return response.sendFile(dir + originalUrl); } response.status(404).send("No avatar with that name found") @@ -818,16 +832,16 @@ router.get("/logout",async function(req,res) { router.get("/*", (request, response, next) => { if(!increaseUSERCall(request,response))return let originalUrl = request.originalUrl.split("?").shift() - if(fs.existsSync(dir + "views/"+originalUrl+".html")) { + if(existsSync(dir + "views/"+originalUrl+".html")) { return response.sendFile(dir + "views/"+originalUrl+".html"); } - if(fs.existsSync(dir + "views"+originalUrl)) { + if(existsSync(dir + "views"+originalUrl)) { return response.sendFile(dir + "views"+originalUrl); } - if(fs.existsSync(dir + "views"+originalUrl+".html")) { + if(existsSync(dir + "views"+originalUrl+".html")) { return response.sendFile(dir + "views"+originalUrl+".html"); } - if(fs.existsSync(dir + "views"+originalUrl)) { + if(existsSync(dir + "views"+originalUrl)) { return response.sendFile(dir + "views"+originalUrl); } response.status(404).send("No file with that name found") @@ -986,8 +1000,8 @@ httpServer.listen(config["ports"]["http"],function(){ console.log(5,"HTTP Server is listening") }); -const privateKey = fs.readFileSync(config["ssl"]["privateKey"]).toString() -const certificate = fs.readFileSync(config["ssl"]["certificate"]).toString() +const privateKey = readFileSync(config["ssl"]["privateKey"]).toString() +const certificate = readFileSync(config["ssl"]["certificate"]).toString() const credentials = {key: privateKey, cert: certificate}; diff --git a/server_esm.js b/server_esm.js new file mode 100644 index 0000000..e2c7669 --- /dev/null +++ b/server_esm.js @@ -0,0 +1,1036 @@ +import http from "http"; +import https from "https"; +import * as express from "express"; +import useragent from "express-useragent"; +import fileUpload from "express-fileupload"; +import * as bodyParser from "body-parser"; +import cookieParser from "cookie-parser"; +import * as signature from "cookie-signature"; +import * as mysql from "mysql"; +import ws from "ws"; +import Jimp from "jimp"; +import SHA from "./extra_modules/SHA.js"; +import getIP from "./extra_modules/getip.js"; +import unsign from "./extra_modules/unsign.js"; +import { readFileSync, mkdir, existsSync } from "fs"; +import { format } from "util"; +import { setup as optionssetup } from "./routes/api/options.js"; +import { setup as allsetup } from "./routes/api/all.js"; +import { setup as settingshandlersetup } from "./routes/api/settingshandler.js"; +import { setup as postsetup } from "./routes/api/post.js"; +import { setup as dmsPersonalMessagessetup } from "./routes/api/dms/PersonalMessages.js"; +import { setup as dmspostsetup } from "./routes/api/dms/post.js"; + +import { fileURLToPath } from 'url' +import { dirname } from 'path' +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + + +/** + * makes sure that a given folder exists, if it doesn't it creates one for you + * @param {string} path the path of the folder + * @param {permission} mask permission mask for the new folder to have + * @param {Function} cb callback, gives null if the folder exists, otherwise gives the error + * @return {undefined} see: callback + */ +function ensureExists(path, mask, cb) { + if (typeof mask == 'function') { // Allow the `mask` parameter to be optional + cb = mask; + mask = 0o744; + } + mkdir(path, mask, function (err) { + if (err) { + if (err.code == 'EEXIST') + cb(null); // Ignore the error if the folder already exists + else + cb(err); // Something else went wrong + } + else + cb(null); // Successfully created folder + }); +} +const config = JSON.parse(readFileSync("server_config.json")); +const time = Date.now(); +const original_log = console.log; +/** + * custom logging function + * @param {number} level importance level if information + * @param {any} info information to format + log + * @return {undefined} returns nothing + */ +function log_info(level, ...info) { + let text = info; + if (text == undefined || text.length == 0) { + text = level; + level = 5; + } + if (config["logs"] && config["logs"]["level"] && config["logs"]["level"] >= level) { + let tolog = `[INFO] [${Date.now()}] : ${format(text)} \n`; + original_log(tolog); //still has some nicer colors + ensureExists(__dirname + '/logs/', function (err) { + if (err) { + process.stderr.write(tolog); //just write it to stderr + } + else { + appendFile(__dirname + "/logs/" + time, tolog, function (err) { + if (err) { + process.stderr.write(err); + } + }); + } + }); + } +} +console.log = log_info; + +const WebSocket = { Server: ws }.Server; + +import {EventEmitter} from 'events'; +import merge from 'merge-descriptors'; + +/** + * Create an express application. + * + * @return {Function} + * @api public + */ + + function createApplication() { + var app = function(req, res, next) { + app.handle(req, res, next); + }; + + merge(app, EventEmitter.prototype, false); + merge(app, express.application, false); + + // expose the prototype that will get set on requests + app.request = Object.create(express.request, { + app: { configurable: true, enumerable: true, writable: true, value: app } + }) + + // expose the prototype that will get set on responses + app.response = Object.create(express.response, { + app: { configurable: true, enumerable: true, writable: true, value: app } + }) + + app.init(); + return app; +} + +const router = express.Router(); +const app = createApplication(); +const HASHES_DB = config.cookies.server_hashes; +const HASHES_COOKIE = config.cookies.client_hashes; +const HASHES_DIFF = HASHES_DB - HASHES_COOKIE; +const DID_I_FINALLY_ADD_HTTPS = true; +const con = mysql.createPool({ + connectionLimit: config.mysql.connections, + host: config.mysql.host, + user: config.mysql.user, + password: readFileSync(config.mysql.password_file).toString() +}); +const dir = __dirname + "/"; +const cookiesecret = readFileSync("cookiesecret.txt").toString(); +/** + * quick function to convert data to base64 + * @param {any} data data to encode in base64 + * @return {string} base64 encoded data + */ +function b64(data) { + let buff = Buffer.from(data); + return buff.toString('base64'); +} +/** + * custom, bad random number generator + * @param {number} seed seed for the number generator, defaults to current timestamp + * @constructor + */ +function RNG(seed) { + if (!seed) + seed = Date.now(); + this.seed = seed; + this.random = function (min, max) { + if (!min) + min = 0; + if (!max) { + max = min; + min = 0; + } + this.seed += Math.log(Math.abs(Math.sin(this.seed)) * 100); + return Math.abs(Math.sin(this.seed)) * max + min; + }; + this.rand = function (min, max) { + return Math.floor(this.random(min, max)); + }; +} +/** + * waits x ms + * @param {number} ms amount of ms to sleep for + * @return {promise} promise that gets resolved after x ms + */ +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +const rand = new RNG(); +const genstring_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +const genstring_charactersLength = genstring_characters.length; +/** + * generates a semi-random string + * @param {number} length length of string to generate + * @return {string} semi-random string generated + */ +function genstring(length) { + let result = ""; + for (let i = 0; i < length; i++) { + result += genstring_characters.charAt(rand.rand(genstring_charactersLength)); + } + return result; +} +/** + * handles client errors, used by expressJS + */ +function clientErrorHandler(err, req, res, next) { + if (err) { + if (req.xhr) { + res.status(200).send({ error: 'Something failed!' }); + } + else { + console.log(1, err); + } + } + else { + next(); + } +} +/** + * utility function to get a key by its value in an object + * @param {object} object object to get key from + * @param {any} value value to get key from + * @return {any} key to the given value inside the object + */ +function getKeyByValue(object, value) { + return Object.keys(object).find(key => object[key] === value); +} +var API_CALLS = {}; +var API_CALLS_ACCOUNT = {}; +var USER_CALLS = {}; +var SESSIONS = {}; +var REVERSE_SESSIONS = {}; +var INDIVIDUAL_CALLS = {}; +/** + * clears current api call list (per IP) + * @return {undefined} returns nothing + */ +function clear_api_calls() { + API_CALLS = {}; +} +/** + * clears current api account call list (per account) + * @return {undefined} returns nothing + */ +function clear_account_api_calls() { + API_CALLS_ACCOUNT = {}; +} +/** + * clears current user file call list (per IP) + * @return {undefined} returns nothing + */ +function clear_user_calls() { + USER_CALLS = {}; +} +setInterval(clear_api_calls, config.rate_limits.api.reset_time); +setInterval(clear_account_api_calls, config.rate_limits.api.reset_time); +setInterval(clear_user_calls, config.rate_limits.user.reset_time); +function increaseIndividualCall(url, req) { + let conf = config["rate_limits"]["individual"][url]; + if (!conf) { + console.log(5, "uri not in individual ratelimiter", url); + return true; + } + if (!conf["enabled"]) + return true; + let ip = getIP(req); + if (INDIVIDUAL_CALLS[ip] == undefined) + INDIVIDUAL_CALLS[ip] = {}; + if (INDIVIDUAL_CALLS[ip][url] == undefined) + INDIVIDUAL_CALLS[ip][url] = 0; + if (INDIVIDUAL_CALLS[ip][url] == 0) { + setTimeout(function () { + INDIVIDUAL_CALLS[ip][url] = 0; + }, conf["reset_time"]); + } + INDIVIDUAL_CALLS[ip][url]++; + if (INDIVIDUAL_CALLS[ip][url] >= conf["max"]) { + console.log(5, "ratelimiting someone on", url, INDIVIDUAL_CALLS[ip][url], conf["max"]); + return false; + } + return true; +} +function increaseAccountAPICall(req, res) { + let cookie = req.cookies.AUTH_COOKIE; + if (!cookie) { + return true; + } + let unsigned = unsign.unsign(cookie, req, res); + if (!unsigned) { + return true; //if there's no account, why not just ignore it + } + unsigned = decodeURIComponent(unsigned); + if (!unsigned) + return false; + let values = unsigned.split(" "); + let username = values[0]; + if (API_CALLS_ACCOUNT[username] == undefined) + API_CALLS_ACCOUNT[username] = 0; + if (API_CALLS_ACCOUNT[username] >= config.rate_limits.api.max_per_account) { + res.status(429); + res.send("You are sending way too many api calls!"); + return false; + } + return true; +} +function increaseAPICall(req, res, next) { + let ip = getIP(req); + if (API_CALLS[ip] == undefined) + API_CALLS[ip] = 0; + if (API_CALLS[ip] >= config.rate_limits.api.max_without_session) { + if (REVERSE_SESSIONS[ip] && req.cookies.session !== REVERSE_SESSIONS[ip]) { //expected a session, but didn't get one + res.status(429); + res.send("You are sending way too many api calls!"); + return; + } + if (!req.cookies.session) { + let session; + do { + session = genstring(300); + } while (SESSIONS[session] != undefined); + SESSIONS[session] = ip; + REVERSE_SESSIONS[ip] = session; + setTimeout(function () { + SESSIONS[session] = undefined; + REVERSE_SESSIONS[ip] = undefined; + }, 50000); + res.cookie('session', session, { maxAge: 100000, httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS }); + console.log(3, "sending session to " + ip); + } + } + if (API_CALLS[ip] >= config.rate_limits.api.max_with_session) { + res.status(429); + res.send("You are sending too many api calls!"); + console.log(3, "rate limiting " + ip); + return false; + } + API_CALLS[ip]++; + if (!increaseAccountAPICall(req, res)) + return false; //can't forget account-based ratelimits + if (next) + next(); + return true; +} +function increaseUSERCall(req, res, next) { + let ip = getIP(req); + if (USER_CALLS[ip] == undefined) + USER_CALLS[ip] = 0; + if (USER_CALLS[ip] >= config.rate_limits.user.max) { + res.status(429); + res.send("You are sending too many requests!"); + console.log(2, "rate limiting " + ip); + return false; + } + USER_CALLS[ip]++; + if (next) + next(); + return true; +} +console.log(5, "loading routes"); +app.use(useragent.express()); +app.use(fileUpload()); + +app.use(bodyParser.default.json({ limit: "100mb" })); +app.use(bodyParser.default.urlencoded({ limit: "100mb", extended: true })); +app.use(clientErrorHandler); +app.use(cookieParser(cookiesecret)); +var blocked_headers = [ + 'HTTP_VIA', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_FORWARDED', + 'HTTP_CLIENT_IP', + 'HTTP_FORWARDED_FOR_IP', + 'VIA', + 'X_FORWARDED_FOR', + 'FORWARDED_FOR', + 'X_FORWARDED', + 'FORWARDED', + 'CLIENT_IP', + 'FORWARDED_FOR_IP', + 'HTTP_PROXY_CONNECTION' +]; +if (!config.disallow_proxies_by_headers) { + blocked_headers = []; +} +app.use(function (req, res, next) { + res.set("X-XSS-Protection", "1; mode=block"); + next(); +}); +if (DID_I_FINALLY_ADD_HTTPS) { + //auto redirect to https + app.use((req, res, next) => { + if (req.secure) { + //already secure + next(); + } + else { + //redirect to https + res.redirect('https://' + req.headers.host + req.url); + } + }); +} +app.use("/*", function (req, res, next) { + res.set("x-powered-by", "ipost"); + for (let i = 0; i < blocked_headers.length; i++) { + if (req.header(blocked_headers[i]) != undefined) { + res.json({ "error": "we don't allow proxies on our website." }); + return; + } + } + let fullurl = req.baseUrl + req.path; + if (fullurl != "/") { + fullurl = fullurl.substring(0, fullurl.length - 1); + } + if (!increaseIndividualCall(fullurl, req)) { + res.status(429); + res.json({ "error": "you are sending too many requests!" }); + return; + } + next(); +}); +router.get("/", function (req, res) { + if (!increaseUSERCall(req, res)) + return; + res.sendFile(dir + "views/index.html"); +}); +console.log(5, "finished loading user routes, starting with api routes"); +/* + +START /API/* + +*/ +var wss; +var commonfunctions = { + increaseAPICall, + increaseUSERCall, + increaseAccountAPICall, + increaseIndividualCall, + wss, + genstring +}; +optionssetup(router, con, commonfunctions); +allsetup(router, con, commonfunctions); +settingshandlersetup(router, con, commonfunctions); +postsetup(router, con, commonfunctions); +dmsPersonalMessagessetup(router, con, commonfunctions); +dmspostsetup(router, con, commonfunctions); +// const toLoad = [ +// "api/options.js", +// "api/all.js", +// "api/settingshandler.js", +// "api/post.js", +// "api/dms/PersonalMessages.js", +// "api/dms/post.js", +// ] +// for (let i = 0; i < toLoad.length; i++) { +// require("./routes/"+toLoad[i]).setup(router,con,commonfunctions) +// } +// let options = require("./routes/api/options.js") +// options.setup(router,con,commonfunctions) +// let apiALL = require("./routes/api/all.js") +// apiALL.setup(router,con,commonfunctions) +// let settingshandler = require("./routes/api/settingshandler.js") +// settingshandler.setup(router,con,commonfunctions) +router.get("/api/search", async function (req, res) { + res.set("Access-Control-Allow-Origin", ""); + let type = req.query.type; + let arg = encodeURIComponent(req.query.selector); + if (type == "user") { + let sql = `select User_Name,User_Bio,User_Avatar from ipost.users where User_Name like ? limit 10;`; + con.query(sql, [`%${arg}%`], function (err, result) { + if (err) + throw err; + if (result[0] && result[0].User_Name) { + res.json(result); + } + else { + res.json({ "error": "there is no such user!" }); + } + }); + } + else if (type == "post") { + let sql = `select post_user_name,post_text,post_time,post_special_text,post_id from ipost.posts where post_text like ? and (post_receiver_name is null or post_receiver_name = 'everyone') order by post_id desc limit 20;`; + con.query(sql, [`%${arg}%`], function (err, result) { + if (err) + throw err; + if (result[0]) { + res.json(result); + } + else { + res.json({ "error": "there is no such post!" }); + } + }); + } + else { + res.json({ "error": "invalid type passed along, expected `user` or `post`" }); + } +}); +router.post("/api/setavatar", function (req, res) { + res.set("Access-Control-Allow-Origin", ""); + if (!req.files || Object.keys(req.files).length === 0) { + return res.status(400).send('No files were uploaded. (req.files)'); + } + let avatar = req.files.avatar; + if (!avatar) { + return res.status(400).send('No files were uploaded. (req.files.)'); + } + let DOSbuf = Buffer.from('ffd8ffc1f151d800ff51d800ffdaffde', 'hex'); //causes DOS + if (avatar.data.includes(DOSbuf)) { + console.log(3, "DOS image was caught"); + return res.status(400).send('No files were uploaded. (req.files.)'); + } + //DOS introduced through jimp (uses jpeg-js) + const avatars = __dirname + '/avatars/'; + ensureExists(avatars, function (err) { + if (err) { + return res.status(500).json({ "error": "there's been an internal server error." }); + } + if (res.locals.avatar) { + unlinkSync(avatars + res.locals.avatar); + } + let filename = genstring(96) + ".png"; + while (existsSync(avatars + "/" + filename) || filename == ".png") { + console.log(5, "already have file: ", filename); + original_log("already have file: ", filename); + filename = genstring(96) + ".png"; + } + avatar.mv(avatars + "temp_" + filename, function (err) { + if (err) { + return res.status(500).json({ "error": "there's been an internal server error." }); + } + Jimp.read(avatars + "temp_" + filename).then(function (image) { + image.resize(100, 100); + image.write(avatars + filename); + let sql = `update ipost.users set User_Avatar=? where User_Name=?`; + con.query(sql, [filename, encodeURIComponent(res.locals.username)], function (err, result) { + if (err) + throw err; + res.json({ "success": "updated avatar" }); + unlinkSync(avatars + "temp_" + filename); + }); + }); + }); + }); +}); +router.get("/api/getuser", async function (req, res) { + res.json({ "username": res.locals.username, "bio": res.locals.bio, "avatar": res.locals.avatar }); +}); +router.get("/api/getalluserinformation", async function (req, res) { + res.set("Access-Control-Allow-Origin", ""); //we don't want that here + let unsigned = unsign.getunsigned(req, res); + if (!unsigned) + return; + unsigned = decodeURIComponent(unsigned); + let sql = `select * from ipost.users where User_Name=? and User_PW=?;`; + let values = unsigned.split(" "); + values[1] = SHA.SHA256(values[1], values[0], HASHES_DIFF); + con.query(sql, values, function (err, result) { + if (err) + throw err; + if (result[0] && result[0].User_Name && result[0].User_Name == values[0]) { + res.status(200); + res.json(result[0]); + } + else { + res.status(400); + res.json({ "error": "you cannot access the api without being logged in" }); + } + }); +}); +router.get("/api/getotheruser", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + let username = req.query.user; + let sql = `select User_Name,User_Bio,User_Avatar,User_PublicKey from ipost.users where User_Name=?;`; + con.query(sql, [username], function (err, result) { + if (err) + throw err; + if (result[0] && result[0].User_Name && result[0].User_Name == username) { + res.json({ "username": username, "bio": result[0].User_Bio, "avatar": result[0].User_Avatar, "publicKey": result[0].User_PublicKey }); + } + else { + res.json({ "error": "there is no such user!" }); + } + }); +}); +router.get("/api/getPosts/*", async function (req, res) { + res.set("Access-Control-Allow-Origin", ""); + res.redirect("/api/getPosts"); +}); +router.get("/api/getPosts", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + if (req.query.channel != undefined) { + let sql = `select post_user_name,post_text,post_time,post_special_text,post_id,post_from_bot,post_reply_id from ipost.posts where post_receiver_name = ? group by post_id order by post_id desc limit 30;`; + con.query(sql, [encodeURIComponent(req.query.channel)], function (err, result) { + if (err) + throw err; + res.json(result); + }); + } + else { //fallback + let sql = `select post_user_name,post_text,post_time,post_special_text,post_id,post_from_bot,post_reply_id from ipost.posts where (post_receiver_name is null or post_receiver_name = 'everyone') group by post_id order by post_id desc limit 30;`; + con.query(sql, [], function (err, result) { + if (err) + throw err; + res.json(result); + }); + } +}); +router.get("/api/getPostsLowerThan", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + if (req.query.channel != undefined) { + let sql = `select post_user_name,post_text,post_time,post_special_text,post_id,post_from_bot,post_reply_id from ipost.posts where ((post_receiver_name = ?) and (post_id < ?)) group by post_id order by post_id desc limit 30;`; + con.query(sql, [encodeURIComponent(req.query.channel), req.query.id], function (err, result) { + if (err) + throw err; + res.json(result); + }); + } + else { //fallback + let sql = `select post_user_name,post_text,post_time,post_special_text,post_id,post_from_bot,post_reply_id from ipost.posts where ((post_receiver_name is null or post_receiver_name = 'everyone') and (post_id < ?)) group by post_id order by post_id desc limit 30;`; + con.query(sql, [req.query.id], function (err, result) { + if (err) + throw err; + res.json(result); + }); + } +}); +router.get("/api/getPost", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + let arg = req.query.id; + let sql = `select post_user_name,post_text,post_time,post_special_text,post_id,post_from_bot,post_reply_id,post_receiver_name from ipost.posts where post_id=?;`; + con.query(sql, [arg], function (err, result) { + if (err) + throw err; + if (result[0]) { + res.set('Cache-Control', 'public, max-age=2592000'); //cache it for one month-ish + res.json(result[0]); + } + else { + res.json({ "error": "there is no such post!" }); + } + }); +}); +router.get("/api/getChannels", async function (req, res) { + res.set("Access-Control-Allow-Origin", "*"); + let sql = `select post_receiver_name from ipost.posts where post_is_private = '0' group by post_receiver_name;`; + con.query(sql, [], function (err, result) { + if (err) + throw err; + res.json(result); + }); +}); +router.post("/api/setBio", async function (req, res) { + res.set("Access-Control-Allow-Origin", ""); + let bio = req.body.Bio; + if (!bio) { + res.status(400); + res.json({ "error": "no bio set!" }); + return; + } + bio = encodeURIComponent(bio); + if (100 < bio.length) { + res.status(400); + res.json({ "error": "the bio is too long!" }); + return; + } + let sql = `update ipost.users set User_Bio=? where User_Name=?`; + con.query(sql, [bio, encodeURIComponent(res.locals.username)], function (err, result) { + if (err) + throw err; + res.json({ "success": "updated bio" }); + }); +}); +router.post("/api/changePW", async function (req, res) { + res.set("Access-Control-Allow-Origin", ""); + if ((typeof req.body.newPW) != "string") { + res.json({ "error": "incorrect password" }); + return; + } + if ((typeof req.body.currentPW) != "string") { + res.json({ "error": "incorrect password" }); + return; + } + if (req.body.newPW.length < 10) { + res.status(400); + res.json({ "error": "password is too short" }); + return; + } + let hashed_pw = SHA.SHA256(req.body.currentPW, res.locals.username, HASHES_DB); + let hashed_new_pw = SHA.SHA256(req.body.newPW, res.locals.username, HASHES_DB); + let sql = `select * from ipost.users where User_Name=? and User_PW=?;`; + let values = [res.locals.username, hashed_pw]; + con.query(sql, values, function (err, result) { + if (err) + throw err; + if (result[0] && result[0].User_Name && result[0].User_Name == res.locals.username) { + let sql = `update ipost.users set User_PW=? where User_Name=? and User_PW=?;`; + let values = [hashed_new_pw, res.locals.username, hashed_pw]; + con.query(sql, values, function (err, result) { + if (err) + throw err; + let ip = getIP(req); + let setTo = res.locals.username + " " + SHA.SHA256(req.body.newPW, res.locals.username, HASHES_COOKIE); + let cookiesigned = signature.sign(setTo, cookiesecret + ip); + res.cookie('AUTH_COOKIE', cookiesigned, { maxAge: Math.pow(10, 10), httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS }); + res.json({ "success": "successfully changed password" }); + }); + } + else { + res.json({ "error": "invalid password" }); + } + sent_res = true; + }); + setTimeout(function () { + if (!sent_res) + res.json({ "error": "timeout" }); + }, 3000); +}); +router.post("/api/changeUsername", async function (req, res) { + res.set("Access-Control-Allow-Origin", ""); + if ((typeof req.body.newUsername) != "string") { + res.status(400); + res.json({ "error": "incorrect username" }); + return; + } + if ((typeof req.body.currentPW) != "string") { + res.status(400); + res.json({ "error": "incorrect password" }); + return; + } + if (100 < req.body.newUsername.length) { + res.status(400); + res.json({ "error": "username is too long" }); + return; + } + if (req.body.newUsername == res.locals.username) { + res.status(400); + res.json({ "error": "username can't be the current one" }); + return; + } + let hashed_pw = SHA.SHA256(req.body.currentPW, res.locals.username, HASHES_DB); + let hashed_new_pw = SHA.SHA256(req.body.currentPW, req.body.newUsername, HASHES_DB); + let sql = `select * from ipost.users where User_Name=?;`; //check if pw is correct + let values = [res.locals.username]; + con.query(sql, values, function (err, result) { + if (err) + throw err; + if (result[0] && result[0].User_PW == hashed_pw) { + let sql = `select * from ipost.users where User_Name=?;`; //check if newUsername isn't already used + let values = [req.body.newUsername]; + con.query(sql, values, function (err, result) { + if (err) + throw err; + if (result[0]) { + res.json({ "error": "user with that username already exists" }); + return; + } + let sql = `update ipost.users set User_PW=?,User_Name=? where User_Name=? and User_PW=?;`; //change username in users + let values = [hashed_new_pw, req.body.newUsername, res.locals.username, hashed_pw]; + con.query(sql, values, function (err, result) { + if (err) + throw err; + let ip = getIP(req); + let setTo = req.body.newUsername + " " + SHA.SHA256(req.body.currentPW, req.body.newUsername, HASHES_COOKIE); + let cookiesigned = signature.sign(setTo, cookiesecret + ip); + res.cookie('AUTH_COOKIE', cookiesigned, { maxAge: Math.pow(10, 10), httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS }); + //updated username in the users table, but not yet on posts + let sql = `update ipost.posts set post_user_name=? where post_user_name=?;`; //change username of every past post sent + let values = [req.body.newUsername, res.locals.username, hashed_pw]; + con.query(sql, values, function (err, result) { + res.json({ "success": "successfully changed username" }); //done + }); + }); + }); + } + else { + res.json({ "error": "invalid password" }); + } + }); +}); +/* + +END /API/* + +*/ +router.get("/users/*", async function (req, res) { + if (!increaseUSERCall(req, res)) + return; + res.sendFile(dir + "views/otheruser.html"); +}); +router.get("/css/*", (request, response) => { + if (!increaseUSERCall(request, response)) + return; + if (existsSync(__dirname + request.originalUrl)) { + response.sendFile(__dirname + request.originalUrl); + } + else { + response.status(404).send("no file with that name found"); + } + return; +}); +router.get("/js/*", (request, response) => { + if (!increaseUSERCall(request, response)) + return; + if (existsSync(__dirname + request.originalUrl)) { + response.sendFile(__dirname + request.originalUrl); + } + else { + response.status(404).send("no file with that name found"); + } + return; +}); +router.get("/images/*", (request, response) => { + if (!increaseUSERCall(request, response)) + return; + if (existsSync(__dirname + request.originalUrl)) { + response.sendFile(__dirname + request.originalUrl); + } + else { + response.status(404).send("no file with that name found"); + } + return; +}); +router.get("/avatars/*", (request, response, next) => { + if (!increaseUSERCall(request, response)) + return; + response.set('Cache-Control', 'public, max-age=2592000'); //cache it for one month-ish + let originalUrl = request.originalUrl.split("?").shift(); + if (existsSync(dir + originalUrl + ".png")) { + return response.sendFile(dir + originalUrl + ".png"); + } + if (existsSync(dir + originalUrl)) { + return response.sendFile(dir + originalUrl); + } + response.status(404).send("No avatar with that name found"); +}); +router.get("/logout", async function (req, res) { + res.cookie("AUTH_COOKIE", "", { maxAge: 0, httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS }); + res.redirect("/"); +}); +router.get("/*", (request, response, next) => { + if (!increaseUSERCall(request, response)) + return; + let originalUrl = request.originalUrl.split("?").shift(); + if (existsSync(dir + "views/" + originalUrl + ".html")) { + return response.sendFile(dir + "views/" + originalUrl + ".html"); + } + if (existsSync(dir + "views" + originalUrl)) { + return response.sendFile(dir + "views" + originalUrl); + } + if (existsSync(dir + "views" + originalUrl + ".html")) { + return response.sendFile(dir + "views" + originalUrl + ".html"); + } + if (existsSync(dir + "views" + originalUrl)) { + return response.sendFile(dir + "views" + originalUrl); + } + response.status(404).send("No file with that name found"); +}); +router.post("/register", async function (req, res) { + for (let i = 0; i < 10; i++) { //don't want people spam registering + if (!increaseAPICall(req, res)) + return; + } + res.status(200); + if ((typeof req.body.user) != "string") { + res.json({ "error": "incorrect username" }); + return; + } + if ((typeof req.body.pass) != "string") { + res.json({ "error": "incorrect password" }); + return; + } + let username = req.body.user.toString(); + username = username.replace(/\s/gi, ""); + let password = req.body.pass.toString(); + if (!username) { + res.status(400); + res.redirect("/register?success=false&reason=username"); + return; + } + if (username == "") { + res.status(400); + res.redirect("/register?success=false&reason=username"); + return; + } + if (password.length < 10) { + res.status(400); + res.send("password is too short"); + return; + } + if (username.length > 25) { + res.status(400); + res.send("username is too long"); + return; + } + if (username.search("@") != -1) { + res.status(400); + res.send("username can't contain @-characters"); + return; + } + if (!password) { + res.status(400); + res.redirect("/register?success=false&reason=password"); + return; + } + let userexistssql = `SELECT User_Name from ipost.users where User_Name = ?`; + con.query(userexistssql, [encodeURIComponent(username)], function (error, result) { + if (result && result[0] && result[0].User_Name) { + res.status(400); + res.redirect("/register?success=false&reason=already_exists"); + return; + } + let less_hashed_pw = SHA.SHA256(password, username, HASHES_DIFF); + let hashed_pw = SHA.SHA256(less_hashed_pw, username, HASHES_COOKIE); + let ip = getIP(req); + let setTo = username + " " + SHA.SHA256(password, username, HASHES_COOKIE); + let cookiesigned = signature.sign(setTo, cookiesecret + ip); + ip = SHA.SHA256(ip, setTo, HASHES_DB); + const default_settings = {}; + let values = [encodeURIComponent(username), hashed_pw, Date.now(), ip, ip, JSON.stringify(default_settings)]; + let sql = `INSERT INTO ipost.users (User_Name, User_PW, User_CreationStamp, User_CreationIP, User_LastIP, User_Settings) VALUES (?, ?, ?, ?, ?, ?, ?, ?);`; + con.query(sql, values, function (err, result) { + if (err) + throw err; + res.cookie('AUTH_COOKIE', cookiesigned, { maxAge: Math.pow(10, 10), httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS }); + res.redirect("/user?success=true"); + }); + }); +}); +router.post("/login", async function (req, res) { + if (!increaseAPICall(req, res)) + return; + if (!increaseAPICall(req, res)) + return; + //login is counted twice (think of bruteforces man) + if ((typeof req.body.user) != "string") { + res.json({ "error": "incorrect username" }); + return; + } + if ((typeof req.body.pass) != "string") { + res.json({ "error": "incorrect password" }); + return; + } + if (!req.body.user) { + res.status(400); + res.send("no username given"); + return; + } + if (!req.body.pass) { + res.status(400); + res.send("no password given"); + return; + } + let username = req.body.user.toString(); + username = username.replace(" ", ""); + let password = req.body.pass.toString(); + if (!username) { + res.status(400); + res.send("no username given"); + return; + } + if (username.length > 25) { + res.status(400); + res.send("username is too long"); + return; + } + if (password.length < 10) { + res.status(400); + res.send("password is too short"); + return; + } + if (!password) { + res.status(400); + res.send("no password given"); + return; + } + let less_hashed_pw = SHA.SHA256(password, username, HASHES_DIFF); + let hashed_pw = SHA.SHA256(less_hashed_pw, username, HASHES_COOKIE); + let userexistssql = `SELECT * from ipost.users where User_Name = ? and User_PW = ?;`; + con.query(userexistssql, [encodeURIComponent(username), hashed_pw], function (error, result) { + if (result && result[0]) { + let ip = getIP(req); + let setTo = username + " " + SHA.SHA256(password, username, HASHES_COOKIE); + let cookiesigned = signature.sign(setTo, cookiesecret + ip); + res.cookie('AUTH_COOKIE', cookiesigned, { maxAge: Math.pow(10, 10), httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS }); + ip = SHA.SHA256(ip, setTo, HASHES_DB); + if (result[0].User_LastIP != ip) { + let sql = `update ipost.users set User_LastIP = ? where User_Name = ?;`; + con.query(sql, [ip, encodeURIComponent(username)], function (error, result) { + if (error) + throw error; + }); + } + res.redirect("/user?success=true"); + } + else { + res.redirect("/login?success=false?reason=noUser"); + } + }); +}); +console.log(5, "finished loading routes"); +app.use(router); +const httpServer = http.createServer(app); +httpServer.listen(config["ports"]["http"], function () { + console.log(5, "HTTP Server is listening"); +}); +const privateKey = readFileSync(config["ssl"]["privateKey"]).toString(); +const certificate = readFileSync(config["ssl"]["certificate"]).toString(); +const credentials = { key: privateKey, cert: certificate }; +var httpsServer; +if (DID_I_FINALLY_ADD_HTTPS) { + httpsServer = https.createServer(credentials, app); + httpsServer.listen(config["ports"]["https"], function () { + console.log(5, "HTTPS Server is listening"); + }); +} +else { + httpsServer = httpServer; +} +wss = new WebSocket({ + server: httpsServer, + perMessageDeflate: { + zlibDeflateOptions: { + chunkSize: 1024, + memLevel: 7, + level: 3 + }, + zlibInflateOptions: { + chunkSize: 10 * 1024 + }, + clientNoContextTakeover: true, + serverNoContextTakeover: true, + serverMaxWindowBits: 10, + concurrencyLimit: 10, + threshold: 1024 * 16 + } +}); +wss.on("connection", function connection(ws) { + ws.channel = "everyone"; + ws.on("message", function incoming(message) { + message = JSON.parse(message); + if (message.id == "switchChannel") { + ws.channel = decodeURIComponent(message.data); + } + }); +}); +commonfunctions.wss = wss; +console.log(5, "starting up all services");