diff --git a/extra_modules/ensureExists.js b/extra_modules/ensureExists.js
new file mode 100644
index 0000000..a1a7d82
--- /dev/null
+++ b/extra_modules/ensureExists.js
@@ -0,0 +1,29 @@
+import {mkdir} from "fs"
+
+/**
+ * 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
+ });
+}
+
+export {
+ ensureExists
+} ;
\ No newline at end of file
diff --git a/extra_modules/navbar.html b/extra_modules/navbar.html
index 60c89d0..2fe1d34 100644
--- a/extra_modules/navbar.html
+++ b/extra_modules/navbar.html
@@ -3,5 +3,5 @@
Profile
Posts
DMs
-
+
\ No newline at end of file
diff --git a/js/posts.js b/js/posts.js
index 7f97b61..fc69e02 100644
--- a/js/posts.js
+++ b/js/posts.js
@@ -211,6 +211,7 @@ async function createPost(username,text,time,specialtext,postid,isbot,reply_id,a
avatar.width=25;
avatar.height=25;
+ avatar.alt = "user avatar"
avatar.classList.add("avatar")
if(avatar_src)avatar.src = "/avatars/"+avatar_src
else {
@@ -298,7 +299,12 @@ async function createPost(username,text,time,specialtext,postid,isbot,reply_id,a
file2_img.width = 50
file3_img.width = 50
file4_img.width = 50
-
+
+ file0_img.alt = "user uploaded file"
+ file1_img.alt = "user uploaded file"
+ file2_img.alt = "user uploaded file"
+ file3_img.alt = "user uploaded file"
+ file4_img.alt = "user uploaded file"
if(file0){
filesP.appendChild(file0_img)
diff --git a/routes/api/getFileIcon.js b/routes/api/getFileIcon.js
new file mode 100644
index 0000000..6ee954b
--- /dev/null
+++ b/routes/api/getFileIcon.js
@@ -0,0 +1,45 @@
+import sharp from "sharp"
+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 = `
+
+
+ ${text}
+
+ `;
+
+ return await img
+ .composite([
+ {
+ input: Buffer.from(svgImage),
+ top: 0,
+ left: 0,
+ },
+ ]).webp({effort:6}).toBuffer()
+ } catch (error) {
+ console.log(error);
+ }
+}
+
+export const setup = function (router, con, server) {
+ 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)
+ })
+ })
+}
\ No newline at end of file
diff --git a/routes/api/getPosts.js b/routes/api/getPosts.js
new file mode 100644
index 0000000..514808f
--- /dev/null
+++ b/routes/api/getPosts.js
@@ -0,0 +1,60 @@
+export const setup = function (router, con, server) {
+ 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!" });
+ }
+ });
+ });
+}
\ No newline at end of file
diff --git a/routes/api/post.js b/routes/api/post.js
index ad1ddc8..316c67a 100644
--- a/routes/api/post.js
+++ b/routes/api/post.js
@@ -120,7 +120,7 @@ export const setup = function (router, con, server) {
console.error(error)
return;
}
- writeFile(`${__dirname}/user_uploads/previews/${file_name}.webp`,await sharp(file.data).resize(50,50,{fit: "inside"}).webp({mixed:true,effort:6}).toBuffer(),(error2)=>{
+ writeFile(`${__dirname}/user_uploads/previews/${file_name}.webp`,await sharp(file.data).resize(100,100,{fit: "inside"}).webp({mixed:true,effort:6}).toBuffer(),(error2)=>{
if(error2)console.error(error2)
})
})
diff --git a/routes/api/search.js b/routes/api/search.js
new file mode 100644
index 0000000..dc683f7
--- /dev/null
+++ b/routes/api/search.js
@@ -0,0 +1,38 @@
+export const setup = function (router, con, server) {
+ 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) {
+ result["message"] = "search has been deprecated as of 11/30/2022"
+ 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]) {
+ result["message"] = "search has been deprecated as of 11/30/2022"
+ res.json(result);
+ }
+ else {
+ res.json({ "error": "there is no such post!", "message": "search has been deprecated as of 11/30/2022"});
+ }
+ });
+ }
+ else {
+ res.json({ "error": "invalid type passed along, expected `user` or `post`", "message": "search has been deprecated as of 11/30/2022"});
+ }
+ });
+}
\ No newline at end of file
diff --git a/routes/api/userRoutes.js b/routes/api/userRoutes.js
new file mode 100644
index 0000000..5c29119
--- /dev/null
+++ b/routes/api/userRoutes.js
@@ -0,0 +1,216 @@
+import sharp from "sharp"
+import { ensureExists } from "../../extra_modules/ensureExists.js"
+import {SHA256} from "../../extra_modules/SHA.js";
+import getIP from "../../extra_modules/getip.js";
+import {getunsigned} from "../../extra_modules/unsign.js";
+
+export const setup = function (router, con, server) {
+ const config = server.config
+ const HASHES_DB = config.cookies.server_hashes;
+ const HASHES_COOKIE = config.cookies.client_hashes;
+ const HASHES_DIFF = HASHES_DB - HASHES_COOKIE;
+ 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.)');
+ }
+ 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(95) + ".webp";
+ while (existsSync(avatars + "/" + filename) || filename == ".webp") { //generate new filename until it's unique
+ filename = genstring(95) + ".webp";
+ }
+ sharp(avatar.data).resize({ //resize avatar to 100x100 and convert it to a webp, then store it
+ width: 100,
+ height: 100
+ }).webp({
+ effort: 6,
+ mixed: true
+ }).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) {
+ 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 = 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] = 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.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) {
+ if (err)
+ throw err;
+ res.json({ "success": "updated bio" });
+ });
+ });
+ router.post("/api/changePW", (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 = SHA256(req.body.currentPW, res.locals.username, HASHES_DB);
+ let hashed_new_pw = 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, (err2) => {
+ if (err2)
+ throw err2;
+ let ip = getIP(req);
+ let setTo = `${res.locals.username} ${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" });
+ }
+ });
+ });
+ 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 = SHA256(req.body.currentPW, res.locals.username, HASHES_DB);
+ let hashed_new_pw = 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) {
+ if (err)
+ throw err;
+ let ip = getIP(req);
+ let setTo = `${req.body.newUsername} ${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
+ //TODO: update username on dms
+ 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, () => {
+ res.json({ "success": "successfully changed username" }); //done
+ });
+ });
+ });
+ }
+ else {
+ res.json({ "error": "invalid password" });
+ }
+ });
+ });
+}
\ No newline at end of file
diff --git a/routes/serve_static_files.js b/routes/serve_static_files.js
new file mode 100644
index 0000000..4566a5e
--- /dev/null
+++ b/routes/serve_static_files.js
@@ -0,0 +1,75 @@
+import {existsSync} from "fs"
+
+export const setup = function (router, con, server) {
+ const increaseUSERCall = server.increaseUSERCall
+ const __dirname = server.dirname
+ const dir = __dirname + "/"
+
+ 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) => {
+ 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)) {
+ return response.sendFile(dir + originalUrl);
+ }
+ response.status(404).send("No avatar with that name found");
+ });
+}
\ No newline at end of file
diff --git a/routes/user_auth.js b/routes/user_auth.js
new file mode 100644
index 0000000..bead980
--- /dev/null
+++ b/routes/user_auth.js
@@ -0,0 +1,164 @@
+import {SHA256} from "../extra_modules/SHA.js";
+import * as signature from "cookie-signature";
+import getIP from "../extra_modules/getip.js";
+import {readFileSync} from "fs"
+
+const cookiesecret = readFileSync("cookiesecret.txt").toString();
+
+export const setup = function (router, con, server) {
+ const config = server.config
+ const DID_I_FINALLY_ADD_HTTPS = server.DID_I_FINALLY_ADD_HTTPS
+ const increaseAPICall = server.increaseAPICall
+ const HASHES_DB = config.cookies.server_hashes;
+ const HASHES_COOKIE = config.cookies.client_hashes;
+ const HASHES_DIFF = HASHES_DB - HASHES_COOKIE;
+
+ 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 = SHA256(password, username, HASHES_DIFF);
+ let hashed_pw = SHA256(less_hashed_pw, username, HASHES_COOKIE);
+ let ip = getIP(req);
+ let setTo = `${username} ${SHA256(password, username, HASHES_COOKIE)}`
+ let cookiesigned = signature.sign(setTo, cookiesecret + ip);
+ ip = 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) {
+ 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 ((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;
+ }
+
+ const no_ip_lock = username.endsWith("@unsafe")
+ username = username.replace("@unsafe","")
+
+ let less_hashed_pw = SHA256(password, username, HASHES_DIFF);
+ let hashed_pw = 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} ${SHA256(password, username, HASHES_COOKIE)}`
+ let cookiesigned = signature.sign(setTo, cookiesecret + (!no_ip_lock ? ip : ""));
+ res.cookie('AUTH_COOKIE', cookiesigned, { maxAge: Math.pow(10, 10), httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS });
+ ip = 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) {
+ if (error)
+ throw error;
+ });
+ }
+ res.redirect("/user?success=true");
+ }
+ else {
+ console.log(5,"login failed, username: ", username);
+ res.redirect("/login?success=false?reason=noUser");
+ }
+ });
+ });
+}
\ No newline at end of file
diff --git a/routes/userfiles.js b/routes/userfiles.js
new file mode 100644
index 0000000..84eeab7
--- /dev/null
+++ b/routes/userfiles.js
@@ -0,0 +1,182 @@
+import ejs from "ejs"
+import LRU from "lru-cache"
+import {minify as min_js} from "uglify-js"
+import Clean from 'clean-css';
+import Minifier from 'html-minifier-terser';
+import { web_version } from "unsafe_encrypt";
+import {existsSync, readFileSync, readFile} from "fs"
+
+export const setup = function (router, con, server) {
+
+ const increaseUSERCall = server.increaseUSERCall
+ const dir = server.dirname + "/"
+
+ ejs.cache = new LRU({max:20})
+
+ const load_var_cache = new LRU({
+ max: 20,
+ maxSize: 10000,
+ sizeCalculation: (value) => {
+ return value.length
+ },
+ 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)
+ load_var_cache.set(fina,"")
+ return "";
+ }
+ let out = readFileSync(fina)
+ if(fina.endsWith(".js")) {
+ out = min_js(out.toString()).code
+ }
+ else if(fina.endsWith(".css")) {
+ const {
+ styles,
+ } = new Clean({}).minify(out.toString());
+ out = styles
+ }
+
+ load_var_cache.set(fina,out)
+
+ return out
+ }
+
+ 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)
+ });
+ })
+ }
+
+ 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,
+ encryptJS: min_js(web_version().toString()).code,
+ cookiebanner: ``,
+ newrelic: load_var("./extra_modules/newrelic_monitor.html"),
+ getPID: server.global_page_variables.getPID,
+ getDMPID: server.global_page_variables.getDMPID
+ }
+
+
+
+ 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
+ //send .txt files as plaintext to help browsers interpret it correctly
+ if(originalUrl.endsWith(".txt")) {
+ response.set('Content-Type', 'text/plain');
+ readFile(path,(err,data)=> {
+ if(err)return
+ response.send(data)
+ })
+ return
+ }
+ }
+ if (existsSync(dir + "views/" + originalUrl + "index.html")) {
+ path = dir + "views/" + originalUrl + "index.html"
+ }
+ if (existsSync(dir + "views/" + originalUrl + ".html")) {
+ path = dir + "views/" + originalUrl + ".html"
+ }
+ if (existsSync(dir + "views" + originalUrl + ".html")) {
+ path = 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)
+ }
+ }
+
+ /**
+ * Handle default URI as /index (interpreted redirect: "localhost" -> "localhost/index" )
+ */
+ router.get("/", function (req, res) {
+ handleUserFiles(req,res,"/index")
+ });
+
+ router.get("/*", handleUserFiles);
+}
\ No newline at end of file
diff --git a/server.js b/server.js
index 54718ff..982e270 100644
--- a/server.js
+++ b/server.js
@@ -1,19 +1,16 @@
import "newrelic"
import http from "http";
-import * as express from "express";
+import express,{Router} 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 {SHA256} 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 { readFileSync, appendFile } from "fs";
import { format } from "util";
import { setup as optionssetup } from "./routes/api/options.js";
import { setup as allsetup } from "./routes/api/all.js";
@@ -21,6 +18,15 @@ 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 { setup as fileiconsetup } from "./routes/api/getFileIcon.js";
+import { setup as searchsetup } from "./routes/api/search.js";
+import { setup as getpostssetup } from "./routes/api/getPosts.js";
+import { setup as userroutessetup } from "./routes/api/userRoutes.js";
+import { setup as servefilessetup} from "./routes/serve_static_files.js"
+import { setup as userfilessetup} from "./routes/userfiles.js"
+import { setup as userauthsetup} from "./routes/user_auth.js"
+
+import { ensureExists } from "./extra_modules/ensureExists.js"
import * as compress from "compression"
const compression = compress.default
@@ -30,69 +36,14 @@ 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 = `
-
-
- ${text}
-
- `;
-
- return await img
- .composite([
- {
- input: Buffer.from(svgImage),
- top: 0,
- left: 0,
- },
- ]).webp({effort:6}).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
+ * @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;
@@ -121,43 +72,8 @@ 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 router = Router();
+const app = express();
const DID_I_FINALLY_ADD_HTTPS = true;
const con = mysql.createPool({
connectionLimit: config.mysql.connections,
@@ -165,7 +81,6 @@ const con = mysql.createPool({
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
@@ -222,27 +137,11 @@ function genstring(length) {
}
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
+ * @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);
@@ -390,7 +289,6 @@ 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));
app.use(compression())
var blocked_headers = [
@@ -450,6 +348,9 @@ app.use("/*", function (req, res, next) {
next();
});
console.log(5, "finished loading user routes, starting with api routes");
+
+const setuproute = handler => handler(router,con,commonfunctions)
+
/*
START /API/*
@@ -464,204 +365,23 @@ var commonfunctions = {
wss,
genstring,
ensureExists,
- "dirname": __dirname
+ "dirname": __dirname,
+ config,
+ DID_I_FINALLY_ADD_HTTPS
};
-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)
- })
-})
+setuproute(optionssetup)
+setuproute(allsetup)
+setuproute(settingshandlersetup)
+const get_pid = setuproute(postsetup);
+setuproute(dmsPersonalMessagessetup)
+const get_dmpid = setuproute(dmspostsetup);
+setuproute(fileiconsetup)
+setuproute(searchsetup)
+setuproute(getpostssetup)
+setuproute(userroutessetup)
+
-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) {
- 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] = 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;`;
@@ -671,541 +391,28 @@ router.get("/api/getChannels", function (_req, res) {
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) {
- if (err)
- throw err;
- res.json({ "success": "updated bio" });
- });
-});
-router.post("/api/changePW", (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 = SHA256(req.body.currentPW, res.locals.username, HASHES_DB);
- let hashed_new_pw = 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, (err2) => {
- if (err2)
- throw err2;
- let ip = getIP(req);
- let setTo = `${res.locals.username} ${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" });
- }
- });
-});
-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 = SHA256(req.body.currentPW, res.locals.username, HASHES_DB);
- let hashed_new_pw = 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) {
- if (err)
- throw err;
- let ip = getIP(req);
- let setTo = `${req.body.newUsername} ${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, () => {
- 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;
-});
+setuproute(servefilessetup)
-router.get("/avatars/*", (request, response) => {
- 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,
- sizeCalculation: (value) => {
- return value.length
- },
- ttl: 1000 * 60,
- allowStale: true,
- updateAgeOnGet: true,
- updateAgeOnHas: true
-})
-
-import {minify as min_js} from "uglify-js"
-import Clean from 'clean-css';
-import Minifier from 'html-minifier-terser';
-
-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)
- load_var_cache.set(fina,"")
- return "";
- }
- let out = readFileSync(fina)
- if(fina.endsWith(".js")) {
- out = min_js(out.toString()).code
- }
- else if(fina.endsWith(".css")) {
- const {
- styles,
- } = new Clean({}).minify(out.toString());
- out = styles
- }
-
- load_var_cache.set(fina,out)
-
- return out
-}
-
-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")
}
+commonfunctions.global_page_variables = global_page_variables
+setuproute(userfilessetup) //needs getPID and getDMPID
- function handleUserFiles(request, response, overrideurl) {
- if (!increaseUSERCall(request, response))return;
- if(typeof overrideurl != "string")overrideurl = undefined;
+setuproute(userauthsetup) //login & register
- let originalUrl = overrideurl || request.originalUrl.split("?").shift();
-
- let path = ""
- //console.log("handling user file")
- if (existsSync(dir + "views" + originalUrl)) {
- //console.log("exists without additional path");
- path = dir + "views" + originalUrl
- if(originalUrl.endsWith(".txt")) {
- response.set('Content-Type', 'text/plain');
- //console.log("sending txt file")
- readFile(path,(err,data)=> {
- if(err)return
- response.send(data)
- })
- return
- }
- //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 = SHA256(password, username, HASHES_DIFF);
- let hashed_pw = SHA256(less_hashed_pw, username, HASHES_COOKIE);
- let ip = getIP(req);
- let setTo = `${username} ${SHA256(password, username, HASHES_COOKIE)}`
- let cookiesigned = signature.sign(setTo, cookiesecret + ip);
- ip = 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) {
- 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;
- }
-
- const no_ip_lock = username.endsWith("@unsafe")
- username = username.replace("@unsafe","")
-
- let less_hashed_pw = SHA256(password, username, HASHES_DIFF);
- let hashed_pw = 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} ${SHA256(password, username, HASHES_COOKIE)}`
- let cookiesigned = signature.sign(setTo, cookiesecret + (!no_ip_lock ? ip : ""));
- res.cookie('AUTH_COOKIE', cookiesigned, { maxAge: Math.pow(10, 10), httpOnly: true, secure: DID_I_FINALLY_ADD_HTTPS });
- ip = 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) {
- 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);