const express = require("express"); const bodyParser = require("body-parser"); const { sha } = require("sha256quanvn"); const { PrismaClient } = require("@prisma/client"); const multer = require("multer"); const fs = require("fs"); const statusMonitor = require("express-status-monitor")({ path: "/bee3hivestatuspagethatnoonewillrememberthisurl", title: "Bee3Hive API Status", }); const cors = require("cors"); const { withAccelerate } = require("@prisma/extension-accelerate"); const { error } = require("console"); const prisma = new PrismaClient().$extends(withAccelerate()) const app = express(); const SALT = process.env.PRIVATE_KEY; const services = ["https://quanvndzai-ipfs-server-1.hf.space"]; const queueFileUpload = {}; const info = (message) => { console.log(`<${new Date()}> [INFO] - ${message}`); }; const warn = (message) => { console.log(`<${new Date()}> [INFO] - ${message}`); }; //* Sussy bigint thing BigInt.prototype["toJSON"] = function () { return this.toString(); }; //* Safe Base64 Encode const safeb64 = (str) => { return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); }; /** * Returns a new array with all duplicates removed. * * @param {Array} a - The array to remove duplicates from. * @return {Array} - A new array with all duplicates removed. */ function uniq(a) { var seen = {}; return a.filter(function(item) { return seen.hasOwnProperty(item) ? false : (seen[item] = true); }); } /** * Generates a new JSON Web Token for the given user ID, and returns it as a string. * * The token is valid for 1 day, and is signed with the server's private key. * * @param {number} id - The user's ID. * @return {string} A new JSON Web Token, as a string. */ const generateJWT = async (id) => { const header = { alg: "SHA256QUANVN", typ: "JWT", }; const payload = { id: id, exp: Date.now() + 3600000 * 24 * 7, // 7 days }; await prisma.users.update({ where: { id: payload.id, }, data: { last_login: new Date(), }, }); const token = safeb64(JSON.stringify(header)) + "." + safeb64(JSON.stringify(payload)); const sig = sha(token + "." + SALT); info("Generated new token for user: " + id); // console.log("New Token generated: " + token + "." + sig) return token + "." + safeb64(sig); }; const generateFileID = () => { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let fileID = ""; for (let i = 0; i < 6; i++) { fileID += characters.charAt(Math.floor(Math.random() * characters.length)); } return fileID; }; async function checkServer() { // info("Checking servers..."); for(var i = services.length - 1; i >= 0; i--) { try { const resp = await fetch(services[i] + "/availible"); if(!resp.ok) { // This server is ded but can try again later lol info(await resp.text()); warn("Server " + services[i] + " fail to ping"); continue; } const data = await resp.text(); // info(data) if(data != "\"Ok!\"") { //This server is full warn("Server " + services[i] + " is full"); services.splice(i, 1); } } catch(e) { // This server is ded but can try again later lol warn("Server " + services[i] + " fail to fetch"); // services.splice(i, 1); } } } setInterval(checkServer, 60 * 1000) checkServer() // Configure Multer storage const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, "uploads/"); }, filename: (req, file, cb) => { // cb(null, generateFileID() + '-' + Date.now()); cb(null, generateFileID() + "-" + file.originalname); }, }); // Set file size limit to 10GB const upload = multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024 * 1024 }, // 10GB }); // console.log(file) const current_connections = []; app.use( cors({ origin: "*", }) ); app.use(bodyParser.json()); //utilizes the body-parser package app.use(bodyParser.urlencoded({extended: true})); app.use(statusMonitor); app.use("/upload_queue", express.static("uploads")); app.get( "/bee3hivestatuspagethatnoonewillrememberthisurl", statusMonitor.pageRoute ); app.get("/bee3hive/internal/files/:node_id", async (req, res) => { // TODO: Make database query for services const node_id = req.params.node_id; const record = await prisma.storage.findMany({ where: { handle: node_id, }, cacheStrategy: { ttl: 60, //* 1 min }, }) if(!record) { res.sendStatus(404); return; } res.send(record); }) // TODO: Make database query for services app.post("/auth/email/register", async (req, res) => { let { username, email, password } = req.body; if (!username || !email || !password) { info("Missing required fields registration"); res.sendStatus(400); return; } if (username.length < 6) { info("Username: " + username + " is too short!"); res.sendStatus(400); return; } username = username.trim(); password = password.trim(); email = email.trim(); // console.log(username, email, password) if ( ["admin", "administrator", "root", "user", "anonymous", "guest"].includes( username ) ) { warn("Username not allowed: " + username); res.sendStatus(401); return; } const record = await prisma.users.findUnique({ where: { email: email, }, }); if (record) { info("User already exists: " + username); res.status(400).send({ error: "Username already exists" }); return; } try { info( "Creating user: " + email + " username: " + username + " password: " + sha(password) + " " + new Date() ); const created = await prisma.users.create({ data: { username: username, email: email, password_hash: sha(password), last_login: new Date(), }, }); info("User created: " + email + " username: " + username); res.send({ token: await generateJWT(created.id) }); return; } catch (e) { warn("Error on creating users"); console.log(e); res.sendStatus(500); return; } }); app.post("/auth/email/login", async (req, res) => { const { email, password } = req.body; if (!email || !password) { info("Missing required fields login"); res.sendStatus(400); return; } const record = await prisma.users.findUnique({ where: { email: email, }, }); if (!record) { info("User not found: " + email); res.sendStatus(401); return; } if (record.password_hash != sha(password)) { warn("Wrong password attempt: " + email); res.sendStatus(401); return; } info("User logged in: " + email); res.send({ token: await generateJWT(record.id) }); }); app.use(async (req, res, next) => { if (!req.headers.authorization) { info("Authorization header not found"); res.sendStatus(401); return; } let token; try { token = req.headers.authorization.slice(7); // Bearer token } catch (e) { warn("Parsing token fail"); console.log(e); return; } if (!token) { info("Token not found"); res.sendStatus(401); return; } const [tokenHeader, tokenPayload, tokenSig] = token.split("."); if (!tokenHeader || !tokenPayload || !tokenSig) { info("Invalid token"); res.sendStatus(401); return; } // console.log(tokenHeader, tokenPayload, tokenSig) try { // console.lo const header = JSON.parse(Buffer.from(tokenHeader, "base64").toString()); const payload = JSON.parse(Buffer.from(tokenPayload, "base64").toString()); const token_sig = Buffer.from(tokenSig, "base64").toString(); const sig = sha(tokenHeader + "." + tokenPayload + "." + SALT); // console.log(header, payload, token_sig, sig) if (sig !== token_sig) { warn("Invalid signature"); res.sendStatus(401); return; } if (Date.now() > payload.exp) { warn("Token expired"); res.sendStatus(401); return; } const record = await prisma.users.findUnique({ where: { id: payload.id, }, }); if (!record) { warn("Record not found"); res.sendStatus(500); // How? return; } info("Authorized: " + record.id); req.user = record; next(); return; } catch (e) { console.log(e); res.sendStatus(401); return; } }); app.post("/auth/me", async (req, res) => { if (!Array.isArray(queueFileUpload[req.user.username])) { queueFileUpload[req.user.username] = []; } // User already authorize res.send(req.user); }); app.post("/auth/delete", async (req, res) => { // User already authorize const { password } = req.body; if (!password) { info("Missing password when deleting user"); res.sendStatus(400); return; } // Check password if (sha(password) != req.user.password_hash) { res.sendStatus(401); return; } // Delete User try { await prisma.users.delete({ where: { id: req.user.id, }, }); res.sendStatus(200); return; } catch (e) { warn("Error on deleting user"); console.log(e); res.sendStatus(500); return; } }); app.post("/file/upload", upload.single("file"), async (req, res) => { try { const tests = fs.readdirSync("uploads"); console.log(tests) console.log(req.file) const upload_path = req.file.path; const file_path = req.body.filePath; const full_file_path = req.user.username + (file_path ? "/" + file_path : ""); // Upload the file to another backend if(services.length == 0) { res.status(500).send("Service unavailable"); return; } const server = services[Math.floor(Math.random() * services.length)]; const body = { filename: file_path, directory: full_file_path, username: req.user.username, fileURI: req.file.filename, } // console.log(body) // Upload the file try { info("Uploading file to " + server); const resp = await fetch(server + "/fetch", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), }); if(!resp.ok) { warn(await resp.text()); res.sendStatus(500); return; } const json = await resp.json(); console.log(json) if(await prisma.storage.findUnique({ where: { dir: full_file_path } })) { // File already exists and same content warn("File already exists. No override: " + full_file_path); res.sendStatus(200); return; } if(await prisma.storage.findUnique({ where: { dir: full_file_path } })) { warn("File already exists. Override: " + full_file_path); await prisma.storage.update({ where:{ dir: full_file_path }, data: { handle: server, username: req.user.username, CID: json[0].Hash, size: +json[0].Size, dir: full_file_path, } }) } else { info("Adding record: " + full_file_path); //* Add record await prisma.storage.create({ data: { handle: server, username: req.user.username, CID: json[0].Hash, size: +json[0].Size, dir: full_file_path, } }) } } catch (e) { warn("Error on uploading file"); console.log(e); res.sendStatus(500); fs.unlinkSync(upload_path); return; } fs.unlinkSync(upload_path); info("File uploaded successfully: " + full_file_path); res.send("File uploaded successfully."); } catch (error) { // Fail to upload warn("File upload failed"); console.log(error); res.status(500).send("File upload failed."); } }); app.get("/file/list", async (req, res) => { const filePath = req.query.filePath; const recursive = req.query.recursive == "true"; const files = [] const setsDirectory = [] try{ const record = await prisma.storage.findMany({ where: { username: req.user.username, } }) for (const r of record) { const path = r.dir ? r.dir.split("/") : []; // remove first element too if(path.length > 1) { path.shift(); path.pop(); } else { path = []; } // console.log(path) let dir = ""; for (const p of path) { dir += p; setsDirectory.push({ type:"directory", dir }); } r.type = "file"; files.push(r) } } catch (e) { info("No file found") console.log(e) } res.send(files.concat(uniq(setsDirectory))); }) app.post("/file/delete", async (req, res) => { const filePath = req.body.filePath; const full_file_path = req.user.username + (filePath ? "/" + filePath : ""); const record = await prisma.storage.findUnique({ where: { dir: full_file_path, } }) if(!record) { warn("File not found: " + (full_file_path ? (full_file_path + "/") : "")) res.status(404).send("File not found."); return; } try { // Tell the server to unpin the file const resp = await fetch(record.handle + "/delete", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ CID: record.CID }), }) if(!resp.ok) { throw "Respone not ok"; } console.log(await resp.json()) await prisma.storage.delete({ where: { CID: record.CID } }) } catch (e) { console.log(e); res.sendStatus(500); return; } info("File deleted: " + (filePath ? (filePath + "/") : "")) res.send("File deleted successfully."); }) app.post("/file/getdownloadurl", async (req, res) => { const filePath = req.body.filePath; const full_file_path = req.user.username + (filePath ? "/" + filePath : ""); info("Get download url: " + full_file_path) const record = await prisma.storage.findUnique({ where: { dir: full_file_path } }) if(!record) { res.status(404).send("File not found") return; } try { const IPFS_GATEWAY = "https://ipfs.io/ipfs/"; res.send(IPFS_GATEWAY + record.CID); } catch(e) { console.log(e); res.sendStatus(500) } }) app.listen(7860, () => info("Server started on port 7860"));