diff --git a/.env b/.env index 8df116a..993214c 100644 --- a/.env +++ b/.env @@ -1,9 +1,7 @@ -SERVER_PORT = 8012 -API_PORT = 2323 +SERVER_PORT = 8080 DATABASE = "" # Available option: MySQL, MongoDB DB_HOST = "" # Enter the database IP here. -DB_PORT = 27017 # Database port. (MySQL: 3306, MongoDB: 27017) DB_USER = "" # Database username. DB_PASSWORD = "" # Database password. DB_NAME = "" # Database name. \ No newline at end of file diff --git a/server.ts b/server.ts index c5dd5d0..2e90424 100644 --- a/server.ts +++ b/server.ts @@ -10,29 +10,30 @@ const socketIo = require("socket.io"); const cors = require("cors"); // Database -const mysql = require("mysql2"); +const mysql = require("mysql2/promise"); const mongoose = require("mongoose"); // Initialization const dev = process.env.NODE_ENV !== "production"; const nextApp = next({ dev }); -const handle = nextApp.getRequestHandler(); const nodeCrypto = require("node:crypto"); +const handle = nextApp.getRequestHandler(); -const api = express(); +const app = express(); -let pool, Account; +let pool; +let Account; -api.use( +app.use( cors({ origin: "*", methods: ["GET", "POST"], optionsSuccessStatus: 200, }) ); -api.use(express.json()); +app.use(express.json()); -switch ((process.env.DATABASE).toLowerCase()) { +switch (process.env.DATABASE.toLowerCase()) { case "mongodb": console.log("Using MongoDB."); async function ConnectMongoDB() { @@ -63,6 +64,7 @@ switch ((process.env.DATABASE).toLowerCase()) { passwordHash: String, token: String, isAdmin: Boolean, + isBanned: Boolean, }); const AccountModel = mongoose.model("Account", accountSchema); @@ -70,7 +72,7 @@ switch ((process.env.DATABASE).toLowerCase()) { Account = AccountModel; } - api.post("/api/login", async (req, res) => { + app.post("/api/login", async (req, res) => { const data = req.body; let username = data.username; @@ -98,6 +100,11 @@ switch ((process.env.DATABASE).toLowerCase()) { if (account) { if (passwordHash === account.passwordHash) { + if (account.isBanned) { + res.status(403).send("You are banned from the server."); + return; + } + if (account.token === null) { let token = nodeCrypto.randomBytes(16).toString("hex"); account.token = token; @@ -114,7 +121,7 @@ switch ((process.env.DATABASE).toLowerCase()) { } }); - api.post("/api/register", async (req, res) => { + app.post("/api/register", async (req, res) => { const data = req.body; let username = data.username; @@ -156,14 +163,36 @@ switch ((process.env.DATABASE).toLowerCase()) { break; case "mysql": - const pool = mysql.createPool({ - host: process.env.MYSQL_HOST, - user: process.env.MYSQL_USER, - password: process.env.MYSQL_PASSWORD, - database: process.env.MYSQL_DATABASE, + console.log("Using MySQL."); + + let connection; + + async function ConnectMySQL() { + connection = await mysql.createConnection({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + }); + + try { + await connection.connect(); + console.log("Connected to MySQL"); + } catch (error) { + console.error("Error connecting to MySQL:", error); + } + } + + ConnectMySQL(); + + pool = mysql.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, }); - api.post("/api/login", async (req, res) => { + app.post("/api/login", async (req, res) => { const data = req.body; let username = data.username; @@ -186,13 +215,6 @@ switch ((process.env.DATABASE).toLowerCase()) { nodeCrypto.hash("sha512", password) ); - const pool = mysql.createPool({ - host: process.env.MYSQL_HOST, - user: process.env.MYSQL_USER, - password: process.env.MYSQL_PASSWORD, - database: process.env.MYSQL_DATABASE, - }); - const [rows] = await pool.query( "SELECT * FROM account WHERE username = ?", [username] @@ -220,7 +242,7 @@ switch ((process.env.DATABASE).toLowerCase()) { } }); - api.post("/api/register", async (req, res) => { + app.post("/api/register", async (req, res) => { const data = req.body; let username = data.username; @@ -243,7 +265,7 @@ switch ((process.env.DATABASE).toLowerCase()) { nodeCrypto.hash("sha512", password) ); await pool.query( - "INSERT INTO account (username, passwordHash, token, isAdmin) VALUES (?, ?, NULL, FALSE)", + "INSERT INTO account (username, passwordHash, token, isAdmin, isBanned) VALUES (?, ?, NULL, FALSE, FALSE)", [username, passwordHash] ); res.status(200).send("Account created"); @@ -263,25 +285,43 @@ switch ((process.env.DATABASE).toLowerCase()) { break; } -api.listen(process.env.API_PORT || 2323, () => { - console.log( - `> API ready on http://localhost:${process.env.API_PORT || 2323}` - ); -}); - nextApp.prepare().then(() => { - const app = express(); - const server = http.createServer(app); const io = new socketIo.Server(server); const onConnection = (socket) => { + socket.on("checkToken", async (token) => { + let isValid = false; + try { + let account; + if (process.env.DATABASE === "mongodb") { + account = await Account.findOne({ token }); + } else { + var [rows] = await pool.query( + "SELECT * FROM account WHERE token = ?", + [token] + ); + account = rows[0]; + } + if (account) { + isValid = true; + socket.handshake.auth = { token: account.token }; + } + } catch (error) { + console.error("Error executing query:", error); + } + + if (!isValid) { + socket.disconnect(); + } else { + socket.emit("tokenCheck", { valid: isValid }); + } + }); + console.log(`User ${socket.handshake.address} connected`); io.emit("announcement", "An user has connected"); socket.on("message", async (message) => { - console.log("Received message:", message); // Log the received message - if (!message || !message.token || !message.message) { console.log("Invalid message received:", message); socket.emit("announcement", "Invalid message received"); @@ -310,7 +350,50 @@ nextApp.prepare().then(() => { var account = rows[0]; } if (account) { - io.emit("message", `${account.username}: ${content}`); + if (account.isAdmin) { + io.emit("adminMessage", `${account.username}: ${content}`); + + if (content.startsWith("?ban")) { + let username = content.split(" ")[1]; + if (username) { + if (process.env.DATABASE === "mongodb") { + let target = await Account.findOne({ username: username }); + if (target.isAdmin) { + socket.emit("announcement", "You cannot ban another admin"); + return; + } + await Account.updateOne({ username: username }, { isBanned: true }); + } else { + await pool.query("UPDATE account SET isBanned = TRUE WHERE username = ?", [username]); + } + + io.emit("announcement", `${username} has been banned by admin ${account.username}`); + + let target = io.sockets.sockets.find((socket) => socket.handshake.auth.token === username); + if (target) { + target.emit("error", JSON.stringify({ message: `You have been banned by Admin ${account.username}`, logout: true })); + } + } else { + socket.emit("announcement", "Invalid target, or arguments."); + } + } else if (content.startsWith("?unban")) { + let username = content.split(" ")[1]; + if (username) { + if (process.env.DATABASE === "mongodb") { + await Account.updateOne({ username: username }, { isBanned: false }); + } + else { + await pool.query("UPDATE account SET isBanned = FALSE WHERE username = ?", [username]); + } + io.emit("announcement", `${username} has been forgiven by admin ${account.username}`); + } + else { + socket.io.emit("announcement", "Invalid target, or arguments."); + } + } + } else { + io.emit("message", `${account.username}: ${content}`); + } } else { io.emit("error", JSON.stringify({ logout: true })); } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index bddc03e..1c6d63e 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -16,8 +16,8 @@ export default function Login() { e.preventDefault(); try { - const response = await fetch(window.location.origin.replace((process.env.SERVER_PORT || 80).toString(), (process.env.API_PORT || 2323).toString())+"/api/login", { - method: "POST", + const response = await fetch("/api/login", { + method: "POST", headers: { "content-type": "application/json", }, @@ -32,8 +32,10 @@ export default function Login() { setError("Invalid username or password"); } else if (response.status === 500) { setError("An error occurred while logging in"); + } else if (response.status === 403) { + setError("You are banned from the server."); } else { - setError("An error occurred"); + setError("An error occurred while logging in"); } } catch (error) { setError("An error occurred while logging in"); @@ -83,7 +85,12 @@ export default function Login() { Login {error &&

{error}

} - Does not have an account? Sign up + + Does not have an account? Sign up + diff --git a/src/app/page.tsx b/src/app/page.tsx index 7d41675..5510b60 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -10,10 +10,19 @@ export default function Home() { const [message, setMessage] = useState(""); useEffect(() => { - if (socket.connected) { - onConnect(); + if (!localStorage.getItem("token")) { + window.location.href = "./login"; + return; } + socket.emit("checkToken", localStorage.getItem("token"), (data) => { + if (data.error) { + alert(data.error); + localStorage.removeItem("token"); + window.location.href = "./login"; + } + }); + function onConnect() { setIsConnected(true); setTransport(socket.io.engine.transport.name); @@ -41,8 +50,21 @@ export default function Home() { ]); }); + socket.on("adminMessage", (receivedMessage) => { + let [username, ...messageParts] = receivedMessage.split(": "); + let message = messageParts.join(": "); + + setMessages((prevMessages) => [ + ...prevMessages, + `ADMIN ${username}: ${message}`, + ]); + }) + socket.on("error", (data) => { data = JSON.parse(data); + if (data.message) { + alert(data.message); + } if (data.logout) { localStorage.removeItem("token"); window.location.href = "./login"; @@ -59,10 +81,11 @@ export default function Home() { socket.on("disconnect", onDisconnect); return () => { - socket.off("connect", onConnect); - socket.off("disconnect", onDisconnect); + socket.off("connect"); + socket.off("disconnect"); socket.off("message"); - socket.off("announcement"); // Add this line + socket.off("adminMessage"); + socket.off("announcement"); socket.off("error"); }; }, [message]); @@ -71,7 +94,6 @@ export default function Home() { e.preventDefault(); if (message) { if (!localStorage.getItem("token")) { - alert("You must be logged in to send messages"); window.location.href = "./login"; return; } @@ -80,66 +102,36 @@ export default function Home() { token: localStorage.getItem("token"), message: message, }); - /*setMessages((prevMessages) => [...prevMessages, `You: ${message}`]); Currently useless, just spams us. Maybe we will deal with this later?*/ setMessage(""); } }; return ( -
+
-

+

{isConnected ? `Connected` : "Disconnected"}

{messages.map((message, index) => (
))}
setMessage(e.target.value)} - style={{ - width: "calc(100% - 80px)", - color: "black", - height: "35px", - borderRadius: "5px", - border: "none", - padding: "0 10px", - }} + className="w-full h-9 rounded-l text-black border-none px-2" + style={{ width: "calc(100% - 80px)" }} /> diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 0ecb4ca..5f3a9fa 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -10,36 +10,35 @@ export default function Register() { const handleRegister = async (e) => { e.preventDefault(); - + try { - const response = await fetch(window.location.origin.replace((process.env.SERVER_PORT || 80).toString(), (process.env.API_PORT || 2323).toString())+"/api/register", { + const response = await fetch("/api/register", { method: "POST", headers: { - "Content-type": "application/json" + "Content-type": "application/json", }, - body: JSON.stringify({ username: username, password: password }) + body: JSON.stringify({ username: username, password: password }), }); - if(response.status == 200) { + if (response.status == 200) { // make them relogin after register window.location.href = "/login"; } else { setError("An error occured."); } } catch (error) { - console.error('Failed to fetch:', error); + console.error("Failed to fetch:", error); setError("An error occured while trying to connect to the server."); } }; const checkUsername = async (username) => { - - if (!new RegExp("^(([A-Za-z0-9]){3,16})+$").test(username)){ + if (!new RegExp("^(([A-Za-z0-9]){3,16})+$").test(username)) { setError("Username must be alphanumberic."); } else { setError(""); } - } + }; return (