feat: Added authentication system and configManager bases

fix: Fixed some errors related to users
This commit is contained in:
Gu://em_ 2025-03-31 17:00:28 +02:00
parent 491f16b8f9
commit 575efff1cb
14 changed files with 681 additions and 21 deletions

12
src/controllers/auth.js Normal file
View file

@ -0,0 +1,12 @@
const handleError = require("../middleware/errors");
const authService = require("../services/authService");
async function login(req, res) {
try {
const token = await authService.login(req.body.username, req.body.password);
res.json({ token });
} catch (err) {
handleError(err, req, res, null);
}
}

View file

@ -25,12 +25,12 @@ async function initDatabase(config) {
// Create mods table
db.exec("CREATE TABLE IF NOT EXISTS mods ( \
Name tinytext PRIMARY KEY, \
DisplayName tinytext, \
Author tinytext,\
Versions longtext,\
OtherInfos longtext \
);");
Username tinytext PRIMARY KEY, \
DisplayName tinytext, \
Author tinytext,\
Versions longtext,\
OtherInfos longtext \
);");
// Insert example mod
if (!(await db.exists("mods", "Name", "example"))) {
@ -38,6 +38,25 @@ async function initDatabase(config) {
db.exec(`INSERT INTO mods (Name, DisplayName, Author, Versions, OtherInfos) \
VALUES ('example', 'Example mod', '${config.users.admin.username}', '', '');`);
}
db.exec("DROP TABLE users");
// Create users table
db.exec("CREATE TABLE IF NOT EXISTS users ( \
Username tinytext PRIMARY KEY, \
DisplayName tinytext, \
Email tinytext,\
Password tinytext,\
ProfilePicture longtext,\
Preferences longtext, \
Favorites longtext \
);");
// Insert default admin account
if (!(await db.exists("users", "Username", config.users.admin.username))) {
console.debug("Creating default admin user");
db.exec(`INSERT INTO users (Username, DisplayName, Email, Password, ProfilePicture, Preferences, Favorites) \
VALUES ('${config.users.admin.username}', 'Admin', '', '${config.users.admin.password}', '', '', '' );`);
}
}

23
src/middleware/auth.js Normal file
View file

@ -0,0 +1,23 @@
const authService = require("../services/authService");
const AppError = require("../utils/appError");
function authenticateToken(req, res, next) {
const auth_header = req.headers["authorization"];
const token = auth_header && auth_header.split(' ')[1];
if (token == null) {
throw new AppError(401, "Unauthorized: missing or bad authorization header");
}
try {
req.user = authService.verifyToken(token);
next();
} catch (err) {
throw new AppError(403, "Forbidden: Error verifying the authorization token");
}
}
module.exports = { authenticateToken }

View file

@ -9,7 +9,23 @@ async function getAllUsers() {
async function getUserByName(name) {
try {
console.debug("Searching for", name);
const res = await db.query("SELECT * FROM users WHERE Name = ?;", [name]);
const res = await db.query("SELECT * FROM users WHERE Username = ?;", [name]);
if (res && res.length > 0) {
return res[0];
} else {
return null;
}
} catch (err) {
console.error("Error in getUserByName:", err);
throw err;
}
}
async function getUserByEmail(email) {
try {
console.debug("Searching for", email);
const res = await db.query("SELECT * FROM users WHERE Email = ?;", [email]);
if (res && res.length > 0) {
return res[0];
} else {
@ -23,20 +39,20 @@ async function getUserByName(name) {
}
async function exists(name) {
return db.exists("users", "Name", name);
return db.exists("users", "Username", name);
}
async function createUser(user_data) {
const { name, email, password, displayName, profilePicture, favorites, preferences } = user_data;
await db.prepare("INSERT INTO users (Name, Email, Password, DisplayName, ProfilePicture, Favorites, Preferences) \
await db.prepare("INSERT INTO users (Username, Email, Password, DisplayName, ProfilePicture, Favorites, Preferences) \
VALUES (?, ?, ?, ?)", [name, email, password, displayName, profilePicture, favorites, preferences]);
return;
}
async function deleteUser(name) {
db.prepare("DELETE FROM users WHERE Name = ?", [name]);
db.prepare("DELETE FROM users WHERE Username = ?", [name]);
return;
}

11
src/routes/login.js Normal file
View file

@ -0,0 +1,11 @@
const express = require("express");
const controller = require("../controllers/auth");
const router = express.Router();
// Login
router.post("/", async (req, res) => {
controller.login(req, res);
});
module.exports = router;

View file

@ -13,7 +13,7 @@ router.get("/", async (req,res) => {
router.post("/", async (req, res) => {
console.debug("Creating mod ", req.body.name);
controller.createMod(req, res);
})
});
// Get mod infos
router.get("/:name", async (req,res) => {

View file

@ -0,0 +1,66 @@
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const userModel = require("../models/user");
const AppError = require("../utils/appError");
const configManager = require("../utils/configManager");
const validate = require("../utils/validate");
const JWT_Secret = configManager.getJWTSecret();
async function login(identifier, password) {
// Check for null
if (!username || !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);
} else
if (validate.isID(identifier)) { // if matches username
user = await userModel.getUserByName(username);
} else {
throw new AppError(401, "Unauthorized", "Invalid credentials");
}
// Check if user exists
if (!user || user.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) {
throw new AppError(500, "Internal server error", "Found multiple users with this name or email, please contact administration");
}
// Check if passwords match
const passwords_match = await bcrypt.compare(password, user[0].password);
if (!passwords_match) {
// throw new AppError(401, "Unauthorized: Invalid password");
throw new AppError(401, "Unauthorized", "Invalid credentials");
}
return jwt.sign({ username: user[0].username, role: user[0].role }, await JWT_Secret);
}
function verifyToken(token) {
return new Promise( (resolve, reject) => {
jwt.verify( token, JWT_Secret, (err, user) => {
if (err) {
reject(err);
} else {
resolve(user);
}
});
});
}
// function authorizeRole(user, roles) {
// if (!user || !roles.includes(user.role)) {
// throw new AppError(401, "Unauthorized: You don't have the necessary permissions to access this resource");
// }
// }
module.exports = { login, verifyToken };

View file

@ -1,12 +1,16 @@
class AppError extends Error {
constructor(statusCode, message) {
constructor(statusCode, status = "", message) {
super(message);
this.statusCode = statusCode;
if (statusCode.toString().startsWith("4")) {
this.status = "Fail";
// Get status
if (status === "") {
if (statusCode.toString().startsWith("4")) {
this.status = "Fail";
} else {
this.status = "Error";
}
} else {
this.status = "Error";
this.status = status;
}
}
}

View file

@ -0,0 +1,24 @@
const fs = require("fs");
const path = require("path");
// Var decalaration
let config;
const config_folder = "config";
const config_file_name = "config.json"
// Load config
config = JSON.parse(fs.readFileSync(path.join(config_folder, config_file_name)));
async function getConfig() {
return config;
}
async function getJWTSecret() {
return config.JWT_Secret || process.env.JWT_Secret;
}
// Default values
// Exports
module.exports = { getJWTSecret };

View file

@ -1,4 +1,5 @@
const mod_model = require("../models/mod");
const user_model = require("../models/user");
const AppError = require("./appError");
async function validateModData(mod_data) {
@ -22,7 +23,7 @@ async function validateModData(mod_data) {
if (!not_null) {
console.debug("Item is missing expected fields:", mod_data);
throw new AppError(400, "Bad request: missing expected fields");
throw new AppError(400, "Bad request", "Missing expected fields");
}
// Check fields format (check if sanitized)
@ -34,16 +35,64 @@ async function validateModData(mod_data) {
const is_valid = is_valid_name && is_valid_displayName;
if (!is_valid) {
console.debug("Fields are not following the expected formats");
throw new AppError(400, "Bad request: The provided fields don't match the expected format");
throw new AppError(400, "Bad request", "The provided fields don't match the expected format");
}
// Check if mod already exists
const exists = await mod_model.exists(mod_data.name);
if (exists) {
console.debug("Error: Item already exists");
throw new AppError(403, "Forbidden: Content with this name already exists");
throw new AppError(403, "Forbidden", "Content with this name already exists");
}
}
module.exports = { validateModData }
async function validateUserData(user_data) {
throw new AppError(501, "Not implemented");
//TODO
// Check fields existence
// ...
if (!not_null) {
console.debug("Missing expected fields:", mod_data);
throw new AppError(400, "Bad request: Missing expected fields");
}
// Check fields format (check if sanitized)
const is_valid_username = /^[a-zA-Z0-9_]+$/.test(user_data.username);
// const is_valid_email = ...
// ...
const is_valid = is_valid_username && is_valid_email;
if (!is_valid) {
console.debug("Fields are not following the expected formats");
throw new AppError(400, "Bad request: The provided fields don't match the expected format");
}
// Check if user already exists
const exists = await user_model.exists(user_data.username);
if (exists) {
console.debug("Error: User already exists");
throw new AppError(403, "Forbidden: User with this name already exists");
}
}
async function validateCretendials(identifier, password) {
throw new AppError(501, "Not implemented");
}
async function isEmail(text) {
const email_regex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
return email_regex.test(text);
}
async function isID(text) {
const id_regex = /[a-zA-Z0-9_]+/;
return id_regex.test(text);
}
module.exports = { validateModData, validateUserData, isEmail, isID };