Bee3HiveAPI / index.js
plsgivemeachane
Error checking 10
1fc7456
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"));