From 106646f306776be98edfd1ac190f5dcf3baa2a5f Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sun, 4 May 2025 23:14:00 +0200 Subject: [PATCH] A LOT of fixes --- config/config.json | 5 +++- src/controllers/auth.js | 9 ++++-- src/database/index.js | 2 +- src/middleware/auth.js | 22 +++++++------- src/middleware/errors.js | 11 +++++++ src/models/mod.js | 17 +++++++---- src/models/user.js | 9 +++--- src/routes/users.js | 6 ---- src/services/authService.js | 57 ++++++++++++++++++++++++++++--------- src/services/modService.js | 6 ++-- src/services/userService.js | 31 ++++++++++---------- src/utils/configManager.js | 7 +++-- src/utils/sanitize.js | 7 +++-- 13 files changed, 120 insertions(+), 69 deletions(-) diff --git a/config/config.json b/config/config.json index 0b7f9c1..a060d02 100644 --- a/config/config.json +++ b/config/config.json @@ -12,5 +12,8 @@ "type": "sqlite" }, - "JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY" + "auth": { + "JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY", + "tokenExpiry": "1h" + } } \ No newline at end of file diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 0a55dde..7bd6571 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -3,10 +3,15 @@ const authService = require("../services/authService"); async function login(req, res) { try { - const token = await authService.login(req.body.username, req.body.password); + const username = req.body.username; + const email = req.body.email; + const password = req.body.password + const token = await authService.login(username, email, password); res.json({ token }); } catch (err) { handleError(err, res); } -} \ No newline at end of file +} + +module.exports = { login }; \ No newline at end of file diff --git a/src/database/index.js b/src/database/index.js index 8c32752..1399795 100644 --- a/src/database/index.js +++ b/src/database/index.js @@ -66,7 +66,7 @@ async function initDatabase() { creation_date TINYTEXT NOT NULL, downloads_count INT NOT NULL, - FOREIGN KEY (mod) REFERENCES Users(username) + FOREIGN KEY (mod) REFERENCES Mods(name) );`); // Mods tags diff --git a/src/middleware/auth.js b/src/middleware/auth.js index ba0d1c8..d43e385 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -29,11 +29,11 @@ async function authorizeModModification(req) { // Auth token await authenticateToken(req); // Get mod infos - if (!req.params || req.params.id) { + if (!req.params || !req.params.name) { throw new AppError(400, "No mod name was scpecified", "Bad request"); } - const mod_name = req.params.id; - const mod = getModByName(mod_name); + const mod_name = req.params.name; + const mod = await getModByName(mod_name); if (!mod) { throw new AppError(404, "No mod was found with this name", "Not found"); } @@ -48,11 +48,11 @@ async function authorizeModpackModification(req) { // Auth token await authenticateToken(req); // Get mod infos - if (!req.params || req.params.id) { + if (!req.params || !req.params.name) { throw new AppError(400, "No mod name was scpecified", "Bad request"); } - const modpack_name = req.params.id; - const modpack = getModpackByName(modpack_name); + const modpack_name = req.params.name; + const modpack = await getModpackByName(modpack_name); if (!modpack) { throw new AppError(404, "No mod was found with this name", "Not found"); } @@ -67,17 +67,17 @@ async function authorizeUserModification(req) { // Auth token await authenticateToken(req); // Get mod infos - if (!req.params || req.params.id) { + if (!req.params || !req.params.name) { throw new AppError(400, "No mod name was scpecified", "Bad request"); } - const user_name = req.params.id; - const user = getUserByName(user_name); + const user_name = req.params.name; + const user = await getUserByName(user_name); if (!user) { - throw new AppError(404, "No mod was found with this name", "Not found"); + throw new AppError(404, "No user was found with this name", "Not found"); } // Authorize if ( user.username != req.token_infos.username) { - throw new AppError(401, "Mod author differs from current user", "Unauthorized"); + throw new AppError(401, "User to modify differs from current user", "Unauthorized"); } } diff --git a/src/middleware/errors.js b/src/middleware/errors.js index 34ecb1c..f51e57a 100644 --- a/src/middleware/errors.js +++ b/src/middleware/errors.js @@ -4,6 +4,16 @@ const handleError = (err, res) => { // Send error infos if (err instanceof AppError) { + + // Log + if (err.statusCode == 500) { + console.error("Error:", err.message); + if (err.debugMsg) { + console.debug(" >", err.debugMsg); + } + } + + // Response return res.status(err.statusCode).json({ status: err.status, message: err.message @@ -11,6 +21,7 @@ const handleError = (err, res) => { } // Default error + console.error("Error:", err.message); res.status(500).json({ message: 'Internal server error', status: 500 diff --git a/src/models/mod.js b/src/models/mod.js index 98ee457..f7abfd9 100644 --- a/src/models/mod.js +++ b/src/models/mod.js @@ -62,22 +62,27 @@ async function createMod(name, display_name, author, description, mod_infos) { [name, display_name, author, description]); // ModInfos table - await db.prepare(`INSERT INTO ModInfos (mod, full_description, license, links, creation_date) - VALUES (?, ?, ?, ?, ?)`, - [name, full_description, license.type, links.toString(), creation_date]); + await db.prepare(`INSERT INTO ModInfos (mod, full_description, license, links, creation_date, downloads_count) + VALUES (?, ?, ?, ?, ?, ?)`, + [name, full_description, license.type, links.toString(), creation_date, 0]); // Tags - const tags_proc = addTags(name, tags, []); + if (tags) { + const tags_proc = addTags(name, tags, []); + } + // License - if (license_type == "custom") { + if (license.type == "custom") { await db.prepare(`UPDATE ModInfos SET custom_license = ? WHERE mod = ?`, [license.content, name]); } // Await - await tags_proc; + if (tags) { + await tags_proc; + } return; } diff --git a/src/models/user.js b/src/models/user.js index 2791e28..fc66eda 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -5,8 +5,8 @@ const db = getDatabase(); // --- Get --- -async function getAllUsers(name) { - return db.query("SELECT username, display_name, email, profile_picture FROM Users WHERE username = ?", [name]); +async function getAllUsers() { + return db.query("SELECT username, display_name, email, profile_picture FROM Users"); } async function getUserByName(name) { @@ -35,8 +35,9 @@ async function exists(name) { async function createUser( username, email, password, displayName, profilePicture, settings ) { // Create user - await db.prepare("INSERT INTO Users (username, email, password, display_name, role) \ - VALUES (?, ?, ?, ?, ?)", [username, email, password, displayName, "user"]); + await db.prepare(`INSERT INTO Users (username, email, password, display_name, role ) + VALUES (?, ?, ?, ?, ? )`, + [username, email, password, displayName, "user"]); // Handle nullable fields if (profilePicture) { diff --git a/src/routes/users.js b/src/routes/users.js index 16a0826..390451f 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -5,27 +5,21 @@ const router = express.Router(); // List users router.get("/", async (req,res) => { - console.debug("Accessing users list"); controller.listUsers(req,res); }); // Create a user router.post("/", async (req, res) => { - console.debug("Creating user ", req.body.name); controller.createUser(req, res); }) // Get user infos router.get("/:name", async (req,res) => { - const name = req.params.name; - console.debug("Accessing user " + name) controller.getUserByName(req, res); }); // Delete user router.delete("/:name", async (req,res) => { - const name = req.params.name; - console.debug("Deleting user " + name) controller.deleteUser(req, res); }); diff --git a/src/services/authService.js b/src/services/authService.js index bbf25cc..e9dee2b 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -1,48 +1,79 @@ -const bcrypt = require("bcrypt"); -const jwt = require("jsonwebtoken"); const userModel = require("../models/user"); const AppError = require("../utils/appError"); +const cryptoUtils = require("../utils/crypto"); const configManager = require("../utils/configManager"); const validate = require("../utils/validate_legacy"); const JWT_Secret = configManager.getJWTSecret(); -async function login(identifier, password) { +async function login(username, email, password) { // Check for null - if (!username || !password) { + if (!(username || email) || !password) { throw new AppError(400, "Bad request", "missing credentials"); } // Get user data - let user; - if (validate.isEmail(identifier)) { // If matches email - user = await userModel.getUserByEmail(username); + let user_get; + if (email) { // If matches email + user_get = await userModel.getUserByEmail(email); } else - if (validate.isID(identifier)) { // if matches username - user = await userModel.getUserByName(username); + if (username) { // if matches username + user_get = await userModel.getUserByName(username); } else { + console.debug("Failed finding user, weird...") throw new AppError(401, "Unauthorized", "Invalid credentials"); } // Check if user exists - if (!user || user.length == 0) { + if (!user_get || user_get.length == 0) { // throw new AppError(401, "Unauthorized: No user with this name"); throw new AppError(401, "Unauthorized", "Invalid credentials"); } // Just in case - if (user.length > 1) { + if (user_get.length > 1) { throw new AppError(500, "Internal server error", "Found multiple users with this name or email, please contact administration"); } + const user = user_get[0]; + + // Get user password + const saved_password_get = await userModel.getUserPassword(user.username); + // Check if retrieved password sucessfully + if (!saved_password_get || saved_password_get.length == 0) { + throw new AppError(500, "Unable to retrieve user password"); + } + saved_password = saved_password_get[0].password; + // Check if retrieved password sucessfully again + if (!saved_password) { + throw new AppError(500, "Unable to retrieve user password"); + } + // Check if passwords match - const passwords_match = await bcrypt.compare(password, user[0].password); + const passwords_match = await cryptoUtils.passwordsMatch(password, saved_password) if (!passwords_match) { // throw new AppError(401, "Unauthorized: Invalid password"); + console.debug(password, "differs from", saved_password); throw new AppError(401, "Unauthorized", "Invalid credentials"); } - return jwt.sign({ username: user[0].username, role: user[0].role }, await JWT_Secret); + const payload = { type: "user", + username: user.username, + email: user.email, + role: user.role }; + + const token = await cryptoUtils.signToken(payload); + return token; + + // // Check if passwords match + // const passwords_match = await bcrypt.compare(password, user[0].password); + // if (!passwords_match) { + // // throw new AppError(401, "Unauthorized: Invalid password"); + // console.debug("Password doesn't match") + // throw new AppError(401, "Unauthorized", "Invalid credentials"); + // } + + // return jwt.sign({ username: user[0].username, role: user[0].role }, await JWT_Secret); } // function authorizeRole(user, roles) { diff --git a/src/services/modService.js b/src/services/modService.js index 19b04dd..4cd8862 100644 --- a/src/services/modService.js +++ b/src/services/modService.js @@ -51,10 +51,10 @@ async function createMod(mod_data, author) { mod_infos.full_description = await mdToHtml(mod_infos.full_description); // Convert await sanitizeModData(mod_data); // Sanitize //TODO - // mod_infos.creation_date = ... + mod_infos.creation_date = 0 // Write changes to database - model.createMod(name, display_name, author, description, mod_data); + await model.createMod(name, display_name, author, description, mod_infos); // Return return getModByName(name); @@ -117,7 +117,7 @@ async function deleteMod(name) { // Authorize // TODO move outside of this function - if (mod.author != mod.user) { + if (mod.author != mod.user) { throw new AppError(403, "You don't have the necessary permissions to execute this action", "Forbidden"); } diff --git a/src/services/userService.js b/src/services/userService.js index 36fb406..8020e25 100644 --- a/src/services/userService.js +++ b/src/services/userService.js @@ -1,30 +1,32 @@ const model = require("../models/user"); const AppError = require("../utils/appError"); +const cryptoUtils = require("../utils/crypto"); const { validateUserData } = require("../utils/validate_legacy"); const { sanitizeUserData } = require("../utils/sanitize"); async function getAllUsers() { - return model.getAllUsers(); + return await model.getAllUsers(); } async function getUserByName(name) { - return model.getUserByName(name); + const res = await model.getUserByName(name); + return res[0]; } async function createUser(user_data) { // Check body validity - await validateUserData(user_data); - - // Check authenticity - //TODO no auth provider + // TODO // Sanitize - await sanitizeUserData(user_data); + // TODO - console.debug("Passed validity checks"); - model.createUser(user_data); - return; + // Gather data + const { username, email, password, display_name, profile_picture, settings } = user_data + const password_hash = await cryptoUtils.hashPassword(password); + + await model.createUser(username, email, password_hash, display_name, null, null); + return model.getUserByName(username); } async function deleteUser(name, token_user) { @@ -35,13 +37,10 @@ async function deleteUser(name, token_user) { throw new AppError(404, "Cannot find user with this name", "Not found"); } - // Check authenticity - if (name != token_user) { - throw new AppError(401, "", "Unauthorized"); - } + const res = await model.getUserByName(name); + await model.deleteUser(name); - model.deleteUser(name); - return; + return res; } module.exports = { getAllUsers, getUserByName, createUser, deleteUser }; \ No newline at end of file diff --git a/src/utils/configManager.js b/src/utils/configManager.js index 5862b59..5017a6f 100644 --- a/src/utils/configManager.js +++ b/src/utils/configManager.js @@ -31,7 +31,8 @@ const default_config = { }, "auth" : { - "JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY" + "JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY", + "tokenExpiry": "1h" } } @@ -48,7 +49,7 @@ function loadConfig() { user_config = JSON.parse(fs.readFileSync(path.resolve(path.join(config_folder, config_file_name)))); // Warns - if (!user_config.auth || !user_config.auth.jwtSecret) { + if (!user_config.auth || !user_config.auth.JWT_secret) { console.warn("WARNING: No JWT secret provided, using the default one. Please note that using the default secret is a major security risk.") } @@ -75,7 +76,7 @@ async function getConfig() { } async function getJWTSecret() { - return config.auth.JWT_Secret || process.env.JWT_Secret; + return config.auth.JWT_secret || process.env.JWT_secret; } async function getVersion() { diff --git a/src/utils/sanitize.js b/src/utils/sanitize.js index f6b99d1..be3885b 100644 --- a/src/utils/sanitize.js +++ b/src/utils/sanitize.js @@ -10,9 +10,10 @@ async function sanitizeText(text) { } async function sanitizeModData(mod_data) { - mod_data.displayName = await sanitizeText(mod_data.displayName); - mod_data.otherInfos.description = await sanitizeText(mod_data.otherInfos.description); - mod_data.otherInfos.changelogs = await sanitizeText(mod_data.otherInfos.changelogs); + console.warn("Skipping sanitanization (not implemented)"); + // mod_data.displayName = await sanitizeText(mod_data.displayName); + // mod_data.otherInfos.description = await sanitizeText(mod_data.otherInfos.description); + // mod_data.otherInfos.changelogs = await sanitizeText(mod_data.otherInfos.changelogs); } module.exports = { sanitizeText, sanitizeModData }; \ No newline at end of file