feat: Added authentication system and configManager bases
fix: Fixed some errors related to users
This commit is contained in:
parent
491f16b8f9
commit
575efff1cb
14 changed files with 681 additions and 21 deletions
12
src/controllers/auth.js
Normal file
12
src/controllers/auth.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
23
src/middleware/auth.js
Normal 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 }
|
||||
|
|
@ -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
11
src/routes/login.js
Normal 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;
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
66
src/services/authService.js
Normal file
66
src/services/authService.js
Normal 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 };
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
@ -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 };
|
||||
Loading…
Add table
Add a link
Reference in a new issue