import "newrelic"
import http from "http";
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 * as ws from "ws";
import sharp from "sharp"
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, appendFile, unlinkSync, writeFileSync, readFile } 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)
async function addTextOnImage(text,buf) {
try {
let img = await sharp(buf)
const metadata = await img.metadata()
const width = metadata.width;
const height = metadata.height;
const svgImage = `
`;
return await img
.composite([
{
input: Buffer.from(svgImage),
top: 0,
left: 0,
},
]).toBuffer()
} catch (error) {
console.log(error);
}
}
/**
* 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 = ws.WebSocketServer;
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) {
//if(!url.startsWith("/avatars/")) //ignore avatars /* DEBUG: inidividual ratelimiters */
//console.log(5, "url not in individual ratelimiter", url); /* DEBUG: inidividual ratelimiters */
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(3, "ratelimiting someone on", url, INDIVIDUAL_CALLS[ip][url], conf["max"],ip);
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();
});
console.log(5, "finished loading user routes, starting with api routes");
/*
START /API/*
*/
var wss;
var commonfunctions = {
increaseAPICall,
increaseUSERCall,
increaseAccountAPICall,
increaseIndividualCall,
wss,
genstring,
ensureExists,
"dirname": __dirname
};
optionssetup(router, con, commonfunctions);
allsetup(router, con, commonfunctions);
settingshandlersetup(router, con, commonfunctions);
const get_pid = postsetup(router, con, commonfunctions);
dmsPersonalMessagessetup(router, con, commonfunctions);
const get_dmpid = dmspostsetup(router, con, commonfunctions);
router.get("/api/getFileIcon/*",async function(req,res){
let path = req.path.split("/api/getFileIcon/")[1]
if(path.length > 4) {
res.status(410).json({"error":"file ending is too long"})
return;
}
addTextOnImage(path,await sharp("./images/empty_file.png").toBuffer()).then(buf => {
res.set("content-type","image/png")
res.send(buf)
})
})
router.get("/api/search", 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(410).send('No files were uploaded. (req.files)');
}
let avatar = req.files.avatar;
if (!avatar) {
return res.status(411).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(412).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) {
try {
unlinkSync(avatars + res.locals.avatar);
} catch(ignored){}
}
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";
}
sharp(avatar.data).resize({
width: 100,
height: 100
}).toBuffer().then(function(data){
writeFileSync(avatars + filename,data)
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" });
});
})
});
});
router.get("/api/getuser", function (req, res) {
res.json({ "username": res.locals.username, "bio": res.locals.bio, "avatar": res.locals.avatar });
});
router.get("/api/getalluserinformation", 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(402);
res.json({ "error": "you cannot access the api without being logged in" });
}
});
});
router.get("/api/getotheruser", function (req, res) {
res.set("Access-Control-Allow-Origin", "*");
let username = req.query.user;
let sql = `select User_Name,User_Bio,User_Avatar 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/*", function (req, res) {
res.set("Access-Control-Allow-Origin", "");
res.redirect("/api/getPosts");
});
router.get("/api/getPosts", 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,User_Avatar,file_0,file_1,file_2,file_3,file_4 from ipost.posts inner join ipost.users on (User_Name = post_user_name) 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,file_0,file_1,file_2,file_3,file_4 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", 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,file_0,file_1,file_2,file_3,file_4 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,file_0,file_1,file_2,file_3,file_4 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", 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,User_Avatar,file_0,file_1,file_2,file_3,file_4 from ipost.posts inner join ipost.users on (User_Name = post_user_name) 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", 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", function (req, res) {
res.set("Access-Control-Allow-Origin", "");
let bio = req.body.Bio;
if (!bio) {
res.status(410);
res.json({ "error": "no bio set!" });
return;
}
bio = encodeURIComponent(bio);
if (bio.length > 100) {
res.status(411);
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", 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(410);
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", function (req, res) {
res.set("Access-Control-Allow-Origin", "");
if ((typeof req.body.newUsername) != "string") {
res.status(410);
res.json({ "error": "incorrect username" });
return;
}
if ((typeof req.body.currentPW) != "string") {
res.status(411);
res.json({ "error": "incorrect password" });
return;
}
if (req.body.newUsername.length > 100) {
res.status(412);
res.json({ "error": "username is too long" });
return;
}
if (req.body.newUsername == res.locals.username) {
res.status(413);
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/*", 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.set('Cache-Control', 'public, max-age=2592000'); //cache it for one month-ish
response.sendFile(__dirname + request.originalUrl);
}
else if(existsSync(__dirname + request.originalUrl.toLowerCase())){
response.set('Cache-Control', 'public, max-age=2592000'); //cache it for one month-ish
response.sendFile(__dirname + request.originalUrl.toLowerCase());
}
else {
response.status(404).send("no file with that name found");
}
return;
});
router.get("/user_uploads/*", (request, response) => {
if (!increaseUSERCall(request, response))
return;
if (existsSync(__dirname + request.originalUrl)) {
response.set('Cache-Control', 'public, max-age=2592000'); //cache it for one month-ish
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", function (req, res) {
res.cookie("AUTH_COOKIE", "", { maxAge: 0, httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS });
res.redirect("/");
});
import ejs from "ejs"
import LRU from "lru-cache"
ejs.cache = new LRU({max:20})
const load_var_cache = new LRU({
max: 20,
maxSize: 10000,
ttl: 1000 * 60,
allowStale: true,
updateAgeOnGet: true,
updateAgeOnHas: true
})
function load_var(fina) {
if(load_var_cache.get(fina))return load_var_cache.get(fina)
if(!existsSync(fina)) {
console.log(1,"tried loading non-existent file",fina)
return "";
}
let out = readFileSync(fina)
if(fina.endsWith(".js")) {
return min_js(out.toString()).code
}
if(fina.endsWith(".css")) {
const {
styles,
errors,
} = new Clean({}).minify(out.toString());
return styles
}
load_var_cache.set(fina,out)
return out
}
import {minify as min_js} from "uglify-js"
import Clean from 'clean-css';
import Minifier from 'html-minifier-terser';
function get_channels(){
return new Promise(function(resolve, reject) {
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)reject(err)
let out = []
for(let channel of result){
if(channel.post_receiver_name == "")continue;
out[out.length] = channel.post_receiver_name
}
resolve(out)
});
})
}
import { web_version } from "unsafe_encrypt";
let global_page_variables = {
globalcss: load_var("./css/global.css"),
httppostjs: load_var("./js/httppost.js"),
navbar: load_var("./extra_modules/navbar.html"),
markdownjs: load_var("./js/markdown.js"),
htmlescapejs: load_var("./js/htmlescape.js"),
warnmessagejs: load_var("./js/warn_message.js"),
loadfile: load_var,
getChannels: get_channels,
getPID: get_pid,
getDMPID: get_dmpid,
encryptJS: min_js(web_version().toString()).code,
cookiebanner: ``,
newrelic: load_var("./extra_modules/newrelic_monitor.html")
}
function handleUserFiles(request, response, overrideurl) {
if (!increaseUSERCall(request, response))return;
if(typeof overrideurl != "string")overrideurl = undefined;
let originalUrl = overrideurl || request.originalUrl.split("?").shift();
let path = ""
if (existsSync(dir + "views" + originalUrl)) {
path = dir + "views" + originalUrl
//return response.sendFile(dir + "views" + originalUrl);
}
if (existsSync(dir + "views/" + originalUrl + "index.html")) {
path = dir + "views/" + originalUrl + "index.html"
}
if (existsSync(dir + "views/" + originalUrl + ".html")) {
path = dir + "views/" + originalUrl + ".html"
//return response.sendFile(dir + "views/" + originalUrl + ".html");
}
if (existsSync(dir + "views" + originalUrl + ".html")) {
path = dir + "views" + originalUrl + ".html"
//return response.sendFile(dir + "views" + originalUrl + ".html");
}
if(path != "" && originalUrl != "/favicon.ico" && originalUrl != "/api/documentation/") {
global_page_variables.user = { "username": response.locals.username, "bio": response.locals.bio, "avatar": response.locals.avatar }
ejs.renderFile(path,global_page_variables,{async: true},async function(err,str){
str = await str
err = await err
if(err) {
console.log(1,err)
response.status(500)
response.send("error")
//TODO: make error page
return
}
try {
str = await Minifier.minify(str,{
removeComments: true,
removeCommentsFromCDATA: true,
removeCDATASectionsFromCDATA: true,
collapseWhitespace: true,
collapseBooleanAttributes: true,
removeAttributeQuotes: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true
})
} catch(ignored){
console.log(2,"error minifying",originalUrl);
}
try {
response.send(str)
} catch(err) {
console.error(err)
}
})
return;
}
if(originalUrl == "/favicon.ico") {
response.set('Cache-Control', 'public, max-age=2592000');
response.sendFile(dir + "/views/favicon.ico")
return
}
if(originalUrl == "/api/documentation/") {
readFile(path,function(err,res){
response.send(res.toString())
})
return
}
console.log(5,"no file found",originalUrl);
try {
response.status(404).send("No file with that name found");
} catch(err) {
console.error(err)
}
}
router.get("/", function (req, res) {
// if (!increaseUSERCall(req, res))
// return;
handleUserFiles(req,res,"/index")
//res.sendFile(dir + "views/index.html");
});
router.get("/*", handleUserFiles);
router.post("/register", 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.status(416);
res.json({ "error": "incorrect username" });
return;
}
if ((typeof req.body.pass) != "string") {
res.status(417);
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(410);
res.redirect("/register?success=false&reason=username");
return;
}
if (username == "") {
res.status(411);
res.redirect("/register?success=false&reason=username");
return;
}
if (password.length < 10) {
res.status(412);
res.send("password is too short");
return;
}
if (username.length > 25) {
res.status(413);
res.send("username is too long");
return;
}
if (username.search("@") != -1) {
res.status(414);
res.send("username can't contain @-characters");
return;
}
if (!password) {
res.status(415);
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(418);
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", 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.status(416);
res.json({ "error": "incorrect username" });
return;
}
if ((typeof req.body.pass) != "string") {
res.status(417);
res.json({ "error": "incorrect password" });
return;
}
if (!req.body.user) {
res.status(410);
res.send("no username given");
return;
}
if (!req.body.pass) {
res.status(411);
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(412);
res.send("no username given");
return;
}
if (username.length > 25) {
res.status(413);
res.send("username is too long");
return;
}
if (password.length < 10) {
res.status(414);
res.send("password is too short");
return;
}
if (!password) {
res.status(415);
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 {
console.log(5,"login failed, username: ", username);
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;
import spdy from "spdy"
if (DID_I_FINALLY_ADD_HTTPS) {
httpsServer = spdy.createServer(credentials,app)
//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";
console.log(5,"new connection");
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");