Compare commits

..

6 commits

Author SHA1 Message Date
Gu://em_ 0aa9e663ff Reworked database model from scratch 2025-04-25 19:51:56 +02:00
Gu://em_ 32a5c97c02 feat: Updated database model 2025-04-25 14:37:42 +02:00
Gu://em_ ce806ef426 feat: Authorization functions 2025-04-25 14:06:03 +02:00
Gu://em_ 6bb2b7b701 GetVersion endpoint 2025-04-24 18:54:03 +02:00
Gu://em_ b7f01446d7 Added crypto and new validation utils (keeping legacy until new one is finished) 2025-04-24 18:49:20 +02:00
Gu://em_ 0374d7bccb Added a sample implementation for validation schemas 2025-04-24 18:39:35 +02:00
20 changed files with 487 additions and 188 deletions

View file

@ -13,6 +13,7 @@
"license": "ISC", "license": "ISC",
"packageManager": "pnpm@10.5.2", "packageManager": "pnpm@10.5.2",
"dependencies": { "dependencies": {
"ajv": "^8.17.1",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"better-sqlite3": "^11.9.1", "better-sqlite3": "^11.9.1",
"dompurify": "^3.2.4", "dompurify": "^3.2.4",

View file

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
ajv:
specifier: ^8.17.1
version: 8.17.1
bcrypt: bcrypt:
specifier: ^5.1.1 specifier: ^5.1.1
version: 5.1.1 version: 5.1.1
@ -92,6 +95,9 @@ packages:
resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -296,6 +302,12 @@ packages:
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-uri@3.0.6:
resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
file-uri-to-path@1.0.0: file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
@ -458,6 +470,9 @@ packages:
canvas: canvas:
optional: true optional: true
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
jsonwebtoken@9.0.2: jsonwebtoken@9.0.2:
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
engines: {node: '>=12', npm: '>=6'} engines: {node: '>=12', npm: '>=6'}
@ -685,6 +700,10 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
rimraf@3.0.2: rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported deprecated: Rimraf versions prior to v4 are no longer supported
@ -974,6 +993,13 @@ snapshots:
agent-base@7.1.3: {} agent-base@7.1.3: {}
ajv@8.17.1:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.0.6
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
anymatch@3.1.3: anymatch@3.1.3:
@ -1197,6 +1223,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
fast-deep-equal@3.1.3: {}
fast-uri@3.0.6: {}
file-uri-to-path@1.0.0: {} file-uri-to-path@1.0.0: {}
fill-range@7.1.1: fill-range@7.1.1:
@ -1387,6 +1417,8 @@ snapshots:
- supports-color - supports-color
- utf-8-validate - utf-8-validate
json-schema-traverse@1.0.0: {}
jsonwebtoken@9.0.2: jsonwebtoken@9.0.2:
dependencies: dependencies:
jws: 3.2.2 jws: 3.2.2
@ -1614,6 +1646,8 @@ snapshots:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
require-from-string@2.0.2: {}
rimraf@3.0.2: rimraf@3.0.2:
dependencies: dependencies:
glob: 7.2.3 glob: 7.2.3

View file

@ -1,12 +1,14 @@
const index_service = require("../services/indexService");
const handleError = require("../middleware/errors"); const handleError = require("../middleware/errors");
async function helloWorld(req, res) {
async function getVersion(req, res) {
try { try {
const query_result = "Unknown development version"; const query_result = await index_service.getVersion();
res.send(query_result); res.json(query_result);
} catch (error) { } catch (error) {
handleError(error); handleError(error, res);
} }
} }
module.exports = { helloWorld }; module.exports = { getVersion };

View file

@ -1,6 +1,6 @@
const { getConfig } = require("../utils/configManager");
const MySQLDatabase = require("./mysql"); const MySQLDatabase = require("./mysql");
const SQLiteDatabase = require("./sqlite"); const SQLiteDatabase = require("./sqlite");
const { getConfig } = require("../utils/configManager");
let db; let db;
@ -31,40 +31,75 @@ async function initDatabase() {
throw new Error("Database is not connected"); throw new Error("Database is not connected");
} }
// Create mods table // --- Users ---
db.exec("CREATE TABLE IF NOT EXISTS mods ( \
Username tinytext PRIMARY KEY, \ // Uers table
DisplayName tinytext, \ db.exec("CREATE TABLE IF NOT EXISTS Users ( \
Author tinytext,\ username TINYTEXT PRIMARY KEY, \
Versions longtext,\ display_name TINYTEXT, \
OtherInfos longtext \ email TINYTEXT,\
password TINYTEXT,\
profile_picture LONGTEXT,\
settings LONGTEXT, \
);"); );");
// Insert example mod // --- Mods ---
// if (!(await db.exists("mods", "Name", "example"))) {
// console.debug("Creating default mod");
// db.exec(`INSERT INTO mods (Name, DisplayName, Author, Versions, OtherInfos) \
// VALUES ('example', 'Example mod', '${config.users.admin.username}', '', '');`);
// }
db.exec("DROP TABLE users"); // Mods table
// Create users table db.exec(`CREATE TABLE IF NOT EXISTS Mods (
db.exec("CREATE TABLE IF NOT EXISTS users ( \ name TINYTEXT PRIMARY KEY,
Username tinytext PRIMARY KEY, \ display_name TINYTEXT NOT NULL,
DisplayName tinytext, \ author TINYTEXT NOT NULL,
Email tinytext,\ description TINYTEXT NOT NULL,
Password tinytext,\
ProfilePicture longtext,\ FOREIGN KEY author REFERENCES Users(username)
Preferences longtext, \ );`);
Favorites longtext \
);"); // Mods complementary infos
db.exec(`CREATE TABLE IF NOT EXISTS ModInfos (
mod TINYTEXT PRIMARY KEY,
full_description TEXT NOT NULL,
license TINYTEXT,
custom_license TEXT,
links TEXT,
creation_date TINYTEXT NOT NULL,
downloads_count INT NOT NULL,
FOREIGN KEY mod REFERENCES Users(username)
);`);
// Mods tags
db.exec(`CREATE TABLE IF NOT EXISTS ModTags (
mod TINYTEXT NOT NULL,
tag TINYTEXT NOT NULL,
FOREIGN KEY mod REFERENCES Mods(name)
);`);
// Mods versions
db.exec(`CREATE TABLE IF NOT EXISTS ModVersions (
mod TINYTEXT NOT NULL,
version_number TINYTEXT NOT NULL,
channel TINYTEXT NOT NULL,
changelog TEXT NOT NULL,
release_date TINYTEXT NOT NULL,
game_version TINYTEXT NOT NULL,
platform TINYTEXT NOT NULL,
environment TINYTEXT NOT NULL,
url TINYTEXT NOT NULL,
FOREGIN KEY mod REFERENCES Mods(name)
);`);
// User favorites (mods)
db.exec(`CREATE TABLE IF NOT EXISTS UserFavoriteMods (
username TINYTEXT NOT NULL,
mod TINYTEXT NOT NULL,
FOREIGN KEY username REFERENCES Users(username),
FOREGIN KEY mod REFERENCES Mods(name)
);`);
// 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}', '', '', '' );`);
// }
} }

View file

@ -1,23 +1,83 @@
const authService = require("../services/authService"); const { getModByName } = require("../services/modService");
const { getModpackByName } = require("../services/modpackService");
const { getUserByName } = require("../services/userService");
const { verifyToken } = require("../utils/crypto");
const AppError = require("../utils/appError"); const AppError = require("../utils/appError");
function authenticateToken(req, res, next) { async function authenticateToken(req) {
const auth_header = req.headers["authorization"]; const token = req.header("Authorization");
const token = auth_header && auth_header.split(' ')[1];
if (token == null) { if (!token) {
throw new AppError(401, "Unauthorized: missing or bad authorization header"); throw new AppError(401, "Missing authorization header", "Unauthorized");
} }
try { try {
req.user = authService.verifyToken(token); req.token_infos = await verifyToken(token);
next(); console.debug("Authorizing token from", req.token_infos);
} catch (err) { } catch (err) {
throw new AppError(403, "Forbidden: Error verifying the authorization token"); throw new AppError(403, "Forbidden: Error verifying the authorization token");
} }
} }
module.exports = { authenticateToken } async function authorizeModModification(req) {
// Auth token
await authenticateToken(req);
// Get mod infos
if (!req.params || req.params.id) {
throw new AppError(400, "No mod name was scpecified", "Bad request");
}
const mod_name = req.params.id;
const mod = getModByName(mod_name);
if (!mod) {
throw new AppError(404, "No mod was found with this name", "Not found");
}
// Authorize
if ( mod.author != req.token_infos.username) {
throw new AppError(401, "Mod author differs from current user", "Unauthorized");
}
}
async function authorizeModpackModification(req) {
// Auth token
await authenticateToken(req);
// Get mod infos
if (!req.params || req.params.id) {
throw new AppError(400, "No mod name was scpecified", "Bad request");
}
const modpack_name = req.params.id;
const modpack = getModpackByName(modpack_name);
if (!modpack) {
throw new AppError(404, "No mod was found with this name", "Not found");
}
// Authorize
if ( modpack.author != req.token_infos.username) {
throw new AppError(401, "Mod author differs from current user", "Unauthorized");
}
}
async function authorizeUserModification(req) {
// Auth token
await authenticateToken(req);
// Get mod infos
if (!req.params || req.params.id) {
throw new AppError(400, "No mod name was scpecified", "Bad request");
}
const user_name = req.params.id;
const user = getUserByName(user_name);
if (!user) {
throw new AppError(404, "No mod 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");
}
}
module.exports = { authenticateToken, authorizeModModification, authorizeModpackModification, authorizeUserModification };

12
src/models/index.js Normal file
View file

@ -0,0 +1,12 @@
const { getVersion } = require("../utils/configManager");
async function getVersion() {
const version = await getVersion();
const res = {
version: version
};
return res;
}
module.exports = { getVersion }

View file

@ -3,27 +3,16 @@ const AppError = require('../utils/appError');
const db = getDatabase(); const db = getDatabase();
async function getAllMods() { async function getAllMods() {
return db.query("SELECT * FROM mods"); return db.query("SELECT name FROM Mods");
} }
async function getModByName(name) { async function getModByName(name) {
try { return await db.query("SELECT name, display_name, author FROM Mods WHERE name = ?;", [name]);
console.debug("Searching for", name);
const res = await db.query("SELECT * FROM mods WHERE Name = ?;", [name]);
if (res && res.length > 0) {
return res[0];
} else {
return null;
}
} catch (err) {
console.error("Error in getModByName:", err);
throw err;
}
} }
async function exists(name) { async function exists(name) {
return db.exists("mods", "Name", name); return db.exists("Mods", "name", name);
} }
async function createMod(mod_data) { async function createMod(mod_data) {
@ -31,8 +20,8 @@ async function createMod(mod_data) {
const { name, displayName, author, versions, otherInfos } = mod_data; const { name, displayName, author, versions, otherInfos } = mod_data;
const { description, links, tags, screenshots, license, changelogs, counts } = otherInfos; const { description, links, tags, screenshots, license, changelogs, counts } = otherInfos;
await db.prepare("INSERT INTO mods (Name, DisplayName, Author, Versions) \ await db.prepare("INSERT INTO mods (name, display_name, author) \
VALUES (?, ?, ?, ?)", [name, displayName, author, versions]); VALUES (?, ?, ?, ?)", [name, displayName, author]);
// db.prepare("INSERT INTO modsDescription (Name, Description, Links, Tags, Screenshots, License, Changelogs, Counts) \ // db.prepare("INSERT INTO modsDescription (Name, Description, Links, Tags, Screenshots, License, Changelogs, Counts) \
// VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [name, description, links, tags, screenshots, license, changelogs, counts]); // VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [name, description, links, tags, screenshots, license, changelogs, counts]);
return; return;
@ -40,7 +29,7 @@ async function createMod(mod_data) {
async function deleteMod(name) { async function deleteMod(name) {
console.log("WARNING: using a WIP function : deleteMod (models/mods.js)"); console.log("WARNING: using a WIP function : deleteMod (models/mods.js)");
db.prepare("DELETE FROM mods WHERE Name = ?", [name]); db.prepare("DELETE FROM Mods WHERE name = ?", [name]);
// db.prepare("DELETE FROM modsDescription WHERE Name = ?", [name]); // db.prepare("DELETE FROM modsDescription WHERE Name = ?", [name]);
return; return;
} }

View file

@ -7,52 +7,29 @@ async function getAllUsers() {
} }
async function getUserByName(name) { async function getUserByName(name) {
try { return await db.query("SELECT * FROM Users WHERE username = ?;", [name]);
console.debug("Searching for", 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) { async function getUserByEmail(email) {
try { return await db.query("SELECT * FROM Users WHERE email = ?;", [email]);
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 {
return null;
}
} catch (err) {
console.error("Error in getUserByName:", err);
throw err;
}
} }
async function exists(name) { async function exists(name) {
return db.exists("users", "Username", name); return db.exists("Users", "username", name);
} }
async function createUser(user_data) { async function createUser(user_data) {
const { name, email, password, displayName, profilePicture, favorites, preferences } = user_data; const { name, email, password, displayName, profilePicture, favorites, preferences } = user_data;
await db.prepare("INSERT INTO users (Username, Email, Password, DisplayName, ProfilePicture, Favorites, Preferences) \ //TODO breakdown to handle partial updates
await db.prepare("INSERT INTO Users (username, email, password, display_name, profile_picture, favorites, preferences) \
VALUES (?, ?, ?, ?)", [name, email, password, displayName, profilePicture, favorites, preferences]); VALUES (?, ?, ?, ?)", [name, email, password, displayName, profilePicture, favorites, preferences]);
return; return;
} }
async function deleteUser(name) { async function deleteUser(name) {
db.prepare("DELETE FROM users WHERE Username = ?", [name]); await db.prepare("DELETE FROM Users WHERE username = ?", [name]);
return; return;
} }
@ -68,4 +45,4 @@ async function updateUser(user_data) {
module.exports = { getAllUsers, getUserByName, createUser, deleteUser, exists } module.exports = { getAllUsers, getUserByName, getUserByEmail, createUser, deleteUser, exists }

View file

@ -3,12 +3,8 @@ const controller = require("../controllers/index");
const router = express.Router(); const router = express.Router();
router.get('/', (res, req) => {
console.debug("Triggered hello world");
controller.helloWorld(res, req);
});
router.get('/version', (res, req) => { router.get('/version', async (res, req) => {
controller.getVersion(res, req); controller.getVersion(res, req);
}); });

21
src/schemas/auth.js Normal file
View file

@ -0,0 +1,21 @@
const Ajv = require("ajv");
const ajv = new Ajv();
// --- Schemas ---
const AuthUserSchema = {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
password: { type: 'string', minLength: 3, maxLength: 30 },
},
required: ['email', 'password'],
additionalProperties: false
};
const validateAuthUserData = ajv.compile(AuthUserSchema);
// --- Exports ---
module.exports = { validateAuthUserData, validateAuthNodeData };

21
src/schemas/mod.js Normal file
View file

@ -0,0 +1,21 @@
const Ajv = require("ajv");
const ajv = new Ajv();
// --- Schemas ---
//TODO
const newModSchema = {
type: 'object',
properties: {
name: { type: 'string'},
},
required: ['name'],
additionalProperties: false
};
const validateNewModData = ajv.compile(newModSchema);
// --- Exports ---
module.exports = { validateNewModData };

21
src/schemas/modpack.js Normal file
View file

@ -0,0 +1,21 @@
const Ajv = require("ajv");
const ajv = new Ajv();
// --- Schemas ---
//TODO
const newModpackSchema = {
type: 'object',
properties: {
name: { type: 'string'},
},
required: ['name'],
additionalProperties: false
};
const validateNewModpackData = ajv.compile(newModpackSchema);
// --- Exports ---
module.exports = { validateNewModpackData };

23
src/schemas/user.js Normal file
View file

@ -0,0 +1,23 @@
const Ajv = require("ajv");
const ajv = new Ajv();
// --- Schemas ---
//TODO
const newUserSchema = {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
name: { type: 'string' },
password: { type: 'string', minLength: 3, maxLength: 30 },
},
required: ['name', 'email', 'password'],
additionalProperties: false
};
const validateNewUserData = ajv.compile(newUserSchema);
// --- Exports ---
module.exports = { validateNewUserData };

View file

@ -3,7 +3,7 @@ const jwt = require("jsonwebtoken");
const userModel = require("../models/user"); const userModel = require("../models/user");
const AppError = require("../utils/appError"); const AppError = require("../utils/appError");
const configManager = require("../utils/configManager"); const configManager = require("../utils/configManager");
const validate = require("../utils/validate"); const validate = require("../utils/validate_legacy");
const JWT_Secret = configManager.getJWTSecret(); const JWT_Secret = configManager.getJWTSecret();
@ -45,22 +45,10 @@ async function login(identifier, password) {
return jwt.sign({ username: user[0].username, role: user[0].role }, await JWT_Secret); 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) { // function authorizeRole(user, roles) {
// if (!user || !roles.includes(user.role)) { // if (!user || !roles.includes(user.role)) {
// throw new AppError(401, "Unauthorized: You don't have the necessary permissions to access this resource"); // throw new AppError(401, "Unauthorized: You don't have the necessary permissions to access this resource");
// } // }
// } // }
module.exports = { login, verifyToken }; module.exports = { login };

View file

@ -0,0 +1,7 @@
const model = require("../models/index");
async function getVersion() {
return model.getVersion();
}
module.exports = { getVersion }

View file

@ -1,6 +1,6 @@
const model = require("../models/mod"); const model = require("../models/mod");
const AppError = require("../utils/appError"); const AppError = require("../utils/appError");
const { validateModData } = require("../utils/validate"); const { validateModData } = require("../utils/validate_legacy");
const { mdToHtml } = require("../utils/convert"); const { mdToHtml } = require("../utils/convert");
const { sanitizeModData } = require("../utils/sanitize"); const { sanitizeModData } = require("../utils/sanitize");

View file

@ -1,6 +1,6 @@
const model = require("../models/user"); const model = require("../models/user");
const AppError = require("../utils/appError"); const AppError = require("../utils/appError");
const { validateUserData } = require("../utils/validate"); const { validateUserData } = require("../utils/validate_legacy");
const { sanitizeUserData } = require("../utils/sanitize"); const { sanitizeUserData } = require("../utils/sanitize");
async function getAllUsers() { async function getAllUsers() {

61
src/utils/crypto.js Normal file
View file

@ -0,0 +1,61 @@
// --- Imports ---
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const { getConfig, getJWTSecret } = require("./configManager");
// --- Config ---
// Declarations
let JWT_Secret;
let token_expiry;
// Constant values
const saltRounds = 12;
// Load
(async () => {
const config = await getConfig();
JWT_Secret = await getJWTSecret();
token_expiry = config.auth.tokenExpiry;
signature_algorithm = config.auth.signatureAlgorithm;
})();
// --- Functions ---
async function hashPassword(passwd) {
const hash = bcrypt.hashSync(passwd, saltRounds);
return hash;
}
async function passwordsMatch(password, hashed_password) {
return await bcrypt.compare(password, hashed_password);
}
async function signToken(payload, options = null) {
if (options == null) {
return jwt.sign(payload, JWT_Secret, { expiresIn: token_expiry, });
}
else {
return jwt.sign(payload, JWT_Secret, options);
}
}
function verifyToken(token) {
return new Promise( async (resolve, reject) => {
await jwt.verify( token, JWT_Secret, (err, user) => {
if (err) {
reject(err);
} else {
resolve(user);
}
});
});
}
// --- Exports ---
module.exports = { passwordsMatch, hashPassword, verifyToken, signToken };

View file

@ -1,82 +1,32 @@
const mod_model = require("../models/mod"); // --- Imports ---
const user_model = require("../models/user");
const AppError = require("./appError"); const AppError = require("./appError");
async function validateModData(mod_data) {
//TODO WIP
// Check fields existence
const not_null = mod_data &&
Object.keys(mod_data).length == 5 &&
mod_data.name &&
mod_data.displayName &&
mod_data.author &&
mod_data.versions != null;
// mod_data.otherInfos != null && // --- Functions ---
// Object.keys(mod_data.otherInfos).length == 0 &&
// mod_data.otherInfos.description != null &&
// mod_data.otherInfos.links != null &&
// mod_data.otherInfos.tags != null &&
// mod_data.otherInfos.screenshots != null &&
// mod_data.otherInfos.license != null &&
// mod_data.otherInfos.changelogs != null;
if (!not_null) { async function validateNewModData(mod_data) {
console.debug("Item is missing expected fields:", mod_data);
throw new AppError(400, "Bad request", "Missing expected fields");
}
// Check fields format (check if sanitized)
const is_valid_name = /^[a-zA-Z0-9_]+$/.test(mod_data.name);
const is_valid_displayName = true;
// const is_valid_displayName = /^[a-zA-Z0-9_]+$/.test(mod_data.name); // Temporary
// const
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");
}
// 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");
}
}
async function validateUserData(user_data) {
throw new AppError(501, "Not implemented"); throw new AppError(501, "Not implemented");
//TODO //TODO
// try {
// node_schemas.validateNewModData(node_data);
// } catch (err) {
// throw new AppError(400, "Missing or invalid fields", "Bad request", err);
// }
// 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; async function validateNewUserData(user_data) {
if (!is_valid) {
console.debug("Fields are not following the expected formats"); throw new AppError(501, "Not implemented");
throw new AppError(400, "Bad request: The provided fields don't match the expected format"); //TODO
} // try {
// node_schemas.validateNewUserData(node_data);
// } catch (err) {
// throw new AppError(400, "Missing or invalid fields", "Bad request", err);
// }
// 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) { async function validateCretendials(identifier, password) {
@ -84,6 +34,9 @@ async function validateCretendials(identifier, password) {
throw new AppError(501, "Not implemented"); throw new AppError(501, "Not implemented");
} }
// --- Utils ---
async function isEmail(text) { async function isEmail(text) {
const email_regex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/; const email_regex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
return email_regex.test(text); return email_regex.test(text);
@ -95,4 +48,4 @@ async function isID(text) {
} }
module.exports = { validateModData, validateUserData, isEmail, isID }; module.exports = { validateNewModData, validateNewUserData, isEmail, isID };

View file

@ -0,0 +1,98 @@
const mod_model = require("../models/mod");
const user_model = require("../models/user");
const AppError = require("./appError");
async function validateModData(mod_data) {
//TODO WIP
// Check fields existence
const not_null = mod_data &&
Object.keys(mod_data).length == 5 &&
mod_data.name &&
mod_data.displayName &&
mod_data.author &&
mod_data.versions != null;
// mod_data.otherInfos != null &&
// Object.keys(mod_data.otherInfos).length == 0 &&
// mod_data.otherInfos.description != null &&
// mod_data.otherInfos.links != null &&
// mod_data.otherInfos.tags != null &&
// mod_data.otherInfos.screenshots != null &&
// mod_data.otherInfos.license != null &&
// mod_data.otherInfos.changelogs != null;
if (!not_null) {
console.debug("Item is missing expected fields:", mod_data);
throw new AppError(400, "Bad request", "Missing expected fields");
}
// Check fields format (check if sanitized)
const is_valid_name = /^[a-zA-Z0-9_]+$/.test(mod_data.name);
const is_valid_displayName = true;
// const is_valid_displayName = /^[a-zA-Z0-9_]+$/.test(mod_data.name); // Temporary
// const
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");
}
// 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");
}
}
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 };