const express = require("express"); const sql = require("mssql"); const bcrypt = require("bcrypt"); const cors = require("cors"); const jwt = require("jsonwebtoken") const dotenv = require("dotenv") const serverPort = 3001; const dev = process.argv.length > 2 && process.argv[2] == "-dev"; const app = express(); app.use(express.json()); app.use(express.static("public")) app.use(cors({ origin: [ "https://project.rochesterx.dev", "https://localhost:3001" ] })); let dbConfig = null; dbConfig = { user: "server", password: "TorusField1*", server: "mssql.rochesterx.dev", database: "ProjectTest", options: { trustServerCertificate: true } }; let pool = null; setupPool(); dotenv.config(); const JWT_SECRET = process.env.JWT_SECRET; if (!JWT_SECRET) { throw new Error("JWT_SECRET not set in environment."); } async function setupPool() { pool = await sql.connect(dbConfig); } app.post("/register", async (req, res) => { const { username, password, role } = req.body; try { const hash = await bcrypt.hash(password, 10); await pool.request() .input("username", sql.VarChar, username) .input("hash", sql.VarChar, hash) .input("role", sql.VarChar, role) .query("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt) VALUES (@username, @hash, @role, SYSUTCDATETIME())"); res.send({ success: true, message: "User registered" }) } catch (err) { if (err.message.includes("Violation of UNIQUE KEY constraint")) { res.status(500).send({ success: false, message: `Username "${username}" is already taken.` }); } res.status(500).send({ success: false, message: err.message }); } }); app.post("/login", async (req, res) => { const { username, password } = req.body; try { const result = await pool.request() .input("username", sql.VarChar, username) .query("SELECT * FROM Users WHERE Username = @username"); if (result.recordset.length == 0) return res.status(400).send({ message: "User not found" }); const hash = result.recordset[0].PasswordHash; const isDeleted = result.recordset[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(result.recordset[0], JWT_SECRET, { expiresIn: "1h" }); res.send({ success: true, message: "Login successful", token }); await pool.request() .input("username", sql.VarChar, username) .query("UPDATE Users SET LastLogin = SYSUTCDATETIME() WHERE 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("/getUsers", authenticate, async (req, res) => { const { query, category } = req.body; const allowed = ["Id", "Username", "FirstName", "LastName", "DOB", "Role", "LastLogin", "CreatedAt"]; if (!allowed.includes(category)) { res.status(500).json({ message: `Illegal category "${category}"`}); } const result = await pool.request() .input("query", sql.VarChar, query) .query(`SELECT * FROM Users WHERE ${category} LIKE '%' + @query + '%'`); res.status(200).json({ query: query, matches: result.recordset }); }); app.post("/getInfo", authenticate, async (req, res) => { const userData = req.user; res.status(200).json(userData); }); app.post("/getCourses", authenticate, async (req, res) => { const result = await pool.request() .query("SELECT * FROM Courses"); const courses = result.recordset; res.status(200).json(courses); }); app.post("/createSection", authenticate, async (req, res) => { let { subject, number, days, startDate, endDate, startTime, endTime, term } = req.body; startTime = new Date("1970-01-01T" + startTime); endTime = new Date("1970-01-01T" + endTime); console.log(req.user); try { await pool.request() .input("subject", sql.VarChar(3), subject) .input("number", sql.Int, number) .input("professor", sql.Int, req.user.Id) .input("term", sql.VarChar(10), term) .input("startDate", sql.Date, startDate) .input("endDate", sql.Date, endDate) .input("startTime", sql.Time(0), startTime) .input("endTime", sql.Time(0), endTime) .input("days", sql.Int, days) .query(` INSERT INTO Sections ( Subject, Number, ProfessorID, Term, Days, StartDate, EndDate, StartTime, EndTime ) VALUES ( @subject, @number, @professor, @term, @days, @startDate, @endDate, @startTime, @endTime ) `); } catch (err) { if (err.message.includes("Violation of UNIQUE KEY constraint")) { res.status(500).send({ success: false, message: `Username "${username}" is already taken.` }); } res.status(500).send({ success: false, message: err.message }); } res.status(200).send({ success: true, message: "Section created successfully."}); }) app.post("/setInfo", authenticate, async (req, res) => { const { firstName, lastName, dob } = req.body; try { await pool.request() .input("username", sql.VarChar, req.user.Username) .input("firstName", sql.NVarChar(50), firstName) .input("lastName", sql.NVarChar(50), lastName) .input("dob", sql.Date, dob) .query(` UPDATE Users SET FirstName = @firstName, LastName = @lastName, DOB = @dob WHERE Username = @username `); } catch (error) { console.log(error); res.status(500).json({ message: "Update request failed" }) } res.status(200).json({ message: "Information updated successfully" }); }); 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}`); await pool.request() .input("username", sql.VarChar, username) .input("actor", sql.VarChar, actor) .query("UPDATE Users SET IsDeleted = 1, DeletedAt = SYSUTCDATETIME(), DeletedBy = @actor WHERE Username = @username"); 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"]; 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"); res.status(401).json({ message: "You are not logged in", error: error, logout: true }); return; } next(); } app.listen(serverPort, "0.0.0.0", () => console.log(`Running ${dev ? "dev " : ""}server on port ${serverPort}`));