const express = require("express"); const sqlite = require("better-sqlite3"); const bcrypt = require("bcrypt"); const cors = require("cors"); const jwt = require("jsonwebtoken") const dotenv = require("dotenv"); const Database = require("better-sqlite3"); const serverPort = 3006; const dev = process.argv.length > 2 && process.argv[2] == "-dev"; const app = express(); app.use(express.json()); app.use((req, res, next) =>{ if (req.path.endsWith(".html")) { res.redirect(req.path.replace(".html", "")); return; } next(); }) app.use(express.static("public")) app.use(cors({ origin: [ "https://project.rochesterx.dev", "https://localhost:3001" ] })); const db = new Database("project.db"); db.pragma("journal_mode = WAL") dotenv.config(); const JWT_SECRET = process.env.JWT_SECRET; if (!JWT_SECRET) { throw new Error("JWT_SECRET not set in environment."); } app.post("/register", async (req, res) => { const { username, password, role, salt } = req.body; try { const hash = await bcrypt.hash(password, 10); const inputs = { username: username, hash: hash, role: role, salt: salt } const query = db.prepare("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt, Salt) VALUES (@username, @hash, @role, datetime('now'), @salt)"); query.run(inputs); res.send({ success: true, message: "User registered" }) } catch (err) { if (err.message.includes("UNIQUE")) { res.status(500).send({ success: false, message: `Username "${username}" is already taken.` }); return; } res.status(500).send({ success: false, message: err.message }); } }); app.post("/login", async (req, res) => { const { username, password } = req.body; //try { const query = db.prepare("SELECT * FROM Users WHERE Username = @username"); const results = query.all({ username: username }); if (results.length == 0) return res.status(400).send({ message: "User not found" }); const hash = results[0].PasswordHash; const isDeleted = results[0].IsDeleted; if (isDeleted === true) { return res.status(400).json({ message: "User not found (deleted)" }) } const match = await bcrypt.compare(password, hash); if (match){ const token = jwt.sign(results[0], JWT_SECRET, { expiresIn: "1h" }); res.send({ success: true, message: "Login successful", token, salt: results[0].Salt }); const update = db.prepare("UPDATE Users SET LastLogin = datetime('now') WHERE Username = @username"); update.run({ username: username }); console.log("Issued token: " + JSON.stringify(token)) } else res.status(401).send({ success: false, message: "Invalid credentials" }); //} catch (err) { // res.status(500).send({ success: false, message: err.message }); //} }); app.post("/getSalt", async (req, res) => { const { username } = req.body; try { const query = db.prepare("SELECT * FROM Users WHERE Username = @username"); const results = query.all({ username: username }); if (results.length == 0) return res.status(400).send({ message: "User not found" }); const isDeleted = results[0].IsDeleted; if (isDeleted === true) { return res.status(400).json({ message: "User not found (deleted)" }) } res.send({ salt: results[0].Salt }); } catch (err) { res.status(500).send({ success: false, message: err.message }); } }); app.post("/setPassword", authenticate, async (req, res) => { try { const { name, iv, password } = req.body; const inputs = { name: name, userID: req.user.Id, iv: iv, password: password } const query = db.prepare("INSERT INTO Passwords (Name, UserID, Password, IV) VALUES (@name, @userID, @password, @iv)"); query.run(inputs); res.status(200).json({ success: true, message: "Success" }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); app.post("/updatePassword", authenticate, async (req, res) => { try { const { name, iv, password } = req.body; const inputs = { name: name, userID: req.user.Id, iv: iv, password: password } const query = db.prepare("UPDATE Passwords SET Password = @password, IV = @iv WHERE UserID = @userID AND Name = @name"); query.run(inputs); res.status(200).json({ success: true, message: "Success" }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); app.post("/getPassword", authenticate, async (req, res) => { try { const { name } = req.body; const query = db.prepare("SELECT * FROM Passwords WHERE UserID = @userID AND Name = @name"); const results = query.all({ userID: req.user.Id, name: name}); if (results.length == 0) { res.status(500).json({ success: false, message: `No password with name ${name}` }); return; } if (results.length > 1) { res.status(500).json({ success: false, message: `More than one password with name ${name}` }); return; } res.status(200).json({ password: results[0].Password, iv: results[0].IV, success: true, message: "Success" }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); app.post("/getPasswords", authenticate, async (req, res) => { try { const query = db.prepare("SELECT * FROM Passwords WHERE UserID = @userID"); const results = query.all({ userID: req.user.Id}); if (results.length == 0) { res.status(500).json({ success: false, message: `No passwords` }); return; } res.status(200).json({ results: results, success: true, message: "Success" }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); app.post("/getWatchlist", authenticate, async (req, res) => { const { id } = req.body; const query = db.prepare(`SELECT Player.PlayerName, Player.Team, Player.Position, Player.PlayerID FROM Watch JOIN Player ON Watch.PlayerID = Player.PlayerID WHERE UserID = @userID ORDER BY Player.PlayerName`); const watchlist = query.all({ userID: req.user.Id, id: id }); res.status(200).json({ watchlist: watchlist }); }); app.post("/getInfo", authenticate, async (req, res) => { const userData = req.user; res.status(200).json(userData); }); app.post("/delete", authenticate, async (req, res) => { let { username, actor } = req.body; if (username === true) { username = req.user.Username; } if (actor === true) { actor = req.user.Username; } console.log(`Deleting user ${username}`); const query = ("UPDATE Users SET IsDeleted = 1, DeletedAt = datetime('now'), DeletedBy = @actor WHERE Username = @username"); query.run({ username: username, actor: actor }); console.log(`User ${username} deleted`); res.status(200).json({ message: `User "${username}" deleted.` }); }); async function authenticate(req, res, next) { try { const authenticationHeader = req.headers["authorization"]; console.log("authenticationheader: " + authenticationHeader); const token = authenticationHeader.split(" ")[1]; console.log(JSON.stringify(authenticationHeader)); const decoded = jwt.verify(token, JWT_SECRET); req.user = decoded; console.log(req.user); console.log(decoded.Username + " authenticated"); } catch (error) { console.log("Authentication header missing"); console.log(error); res.status(401).json({ message: "You are not logged in", error: error, logout: true }); return; } next(); } app.get("/player/:id", (req, res) => { res.sendFile(__dirname + "/public/player.html"); }) app.get("/search", (req, res) => { res.sendFile(__dirname + "/public/search.html"); }) app.get("/home", (req, res) => { res.sendFile(__dirname + "/public/home.html"); }) app.get("/info", (req, res) => { res.sendFile(__dirname + "/public/info.html"); }) app.get("/register", (req, res) => { res.sendFile(__dirname + "/public/register.html"); }) app.get("/login", (req, res) => { res.sendFile(__dirname + "/public/login.html"); }) app.listen(serverPort, "0.0.0.0", () => console.log(`Running ${dev ? "dev " : ""}server on port ${serverPort}`));