diff --git a/UC/backend/src/app.js b/UC/backend/src/app.js index 47f9b42..0b7849a 100644 --- a/UC/backend/src/app.js +++ b/UC/backend/src/app.js @@ -10,6 +10,9 @@ app.use(cors({ })); app.use(json()); +const {apiKey} = require('./config.json'); +app.locals.apiKey = apiKey; + const { usersDB, departmentsDB, diff --git a/UC/backend/src/config.json b/UC/backend/src/config.json new file mode 100644 index 0000000..ba0ff54 --- /dev/null +++ b/UC/backend/src/config.json @@ -0,0 +1,3 @@ +{ + "apiKey": "uc_api" +} \ No newline at end of file diff --git a/UC/backend/src/db/jsonDatabaseManager.js b/UC/backend/src/db/jsonDatabaseManager.js index ca524b1..f96d424 100644 --- a/UC/backend/src/db/jsonDatabaseManager.js +++ b/UC/backend/src/db/jsonDatabaseManager.js @@ -31,6 +31,48 @@ class JSONDatabase { console.error('Error writing file:', error); } } + + isKeyValue(key, value) { + const data = this.readFile(); + if (!data) { + return false; + } + + for (const item of data) { + if (item[key] === value) { + return true; + } + } + return false; + } + + findIndexByKeyValueInArray(key, value) { + const data = this.readFile(); + if (!data || !Array.isArray(data)) { + return -1; + } + + for (let i = 0; i < data.length; i++) { + if (data[i][key] === value) { + return i; + } + } + return -1; + } + + findByKeyValue(key, value) { + const data = this.readFile(); + if (!data || !Array.isArray(data)) { + return null; // Database read failed or data is not an array + } + + for (const item of data) { + if (item[key] === value) { + return item; // Return the object containing the specified key-value pair + } + } + return null; // No matching key-value pair found + } } const usersDB = new JSONDatabase(path.join(__dirname, 'users.json')); diff --git a/UC/backend/src/models/ceoModifyModel.js b/UC/backend/src/models/ceoModifyModel.js new file mode 100644 index 0000000..d757447 --- /dev/null +++ b/UC/backend/src/models/ceoModifyModel.js @@ -0,0 +1,15 @@ +const Joi = require('joi'); + +const ceoModifyModel = Joi.object({ + name: Joi.string().required().messages({ + 'any.required': 'CEO name is required', + 'string.empty': 'CEO name must not be empty' + }), + email: Joi.string().email().required().messages({ + 'any.required': 'CEO email is required', + 'string.empty': 'CEO email must not be empty', + 'string.email': 'CEO email must be a valid email address' + }) +}); + +module.exports = ceoModifyModel; \ No newline at end of file diff --git a/UC/backend/src/models/ceoPasswordModel.js b/UC/backend/src/models/ceoPasswordModel.js new file mode 100644 index 0000000..9cbd3f1 --- /dev/null +++ b/UC/backend/src/models/ceoPasswordModel.js @@ -0,0 +1,10 @@ +const Joi = require('joi'); + +const ceoPasswordModel = Joi.object({ + password: Joi.string().required().messages({ + 'any.required': 'Password is required', + 'string.empty': 'Password must not be empty' + }) +}); + +module.exports = ceoPasswordModel; \ No newline at end of file diff --git a/UC/backend/src/models/ceoRegisterModel.js b/UC/backend/src/models/ceoRegisterModel.js deleted file mode 100644 index 8286e23..0000000 --- a/UC/backend/src/models/ceoRegisterModel.js +++ /dev/null @@ -1,8 +0,0 @@ -const Joi = require('joi'); - -const ceoRegisterModel = Joi.object({ - name: Joi.string().required(), - email: Joi.string().email().required() -}); - -module.exports = ceoRegisterModel; \ No newline at end of file diff --git a/UC/backend/src/models/departmentRegisterModel.js b/UC/backend/src/models/departmentRegisterModel.js index 9d4d182..2d45bad 100644 --- a/UC/backend/src/models/departmentRegisterModel.js +++ b/UC/backend/src/models/departmentRegisterModel.js @@ -1,7 +1,10 @@ const Joi = require('joi'); const departmentRegisterModel = Joi.object({ - name: Joi.string().required() + name: Joi.string().required().messages({ + 'any.required': 'Department name is required', + 'string.empty': 'Department name must not be empty' + }) }); module.exports = departmentRegisterModel \ No newline at end of file diff --git a/UC/backend/src/models/dirStructureModel.js b/UC/backend/src/models/dirStructureModel.js index 5cefb4a..f269bee 100644 --- a/UC/backend/src/models/dirStructureModel.js +++ b/UC/backend/src/models/dirStructureModel.js @@ -1,9 +1,20 @@ const Joi = require('joi'); const dirStructureModel = Joi.object({ - id: Joi.string().guid().required(), - dir_config: Joi.string().required(), - total_space: Joi.number().positive().required() + id: Joi.string().guid().required().messages({ + 'any.required': 'ID is required', + 'string.empty': 'ID must not be empty', + 'string.guid': 'ID must be a valid GUID' + }), + dir_config: Joi.string().required().messages({ + 'any.required': 'Directory configuration is required', + 'string.empty': 'Directory configuration must not be empty' + }), + total_space: Joi.number().positive().required().messages({ + 'any.required': 'Total space is required', + 'number.positive': 'Total space must be a positive number', + 'number.base': 'Total space must be a number' + }) }); module.exports = dirStructureModel \ No newline at end of file diff --git a/UC/backend/src/models/emailVerificationModel.js b/UC/backend/src/models/emailVerificationModel.js index d93342f..32ba392 100644 --- a/UC/backend/src/models/emailVerificationModel.js +++ b/UC/backend/src/models/emailVerificationModel.js @@ -1,7 +1,11 @@ const Joi = require('joi'); const emailVerificationModel = Joi.object({ - email: Joi.string().email().required() + email: Joi.string().email().required().messages({ + 'string.empty': 'Email can\'t be empty.', + 'string.email': 'Invalid email format.', + 'any.required': 'Email is required' + }) }); module.exports = emailVerificationModel; \ No newline at end of file diff --git a/UC/backend/src/models/schemaMapper.js b/UC/backend/src/models/schemaMapper.js index 06e332f..fb93d43 100644 --- a/UC/backend/src/models/schemaMapper.js +++ b/UC/backend/src/models/schemaMapper.js @@ -3,9 +3,10 @@ const usersLoginModelSchema = require('./usersLoginModel'); const usersModifyModelSchema = require('./usersModifyModel'); const dirStructureModelSchema = require('./dirStructureModel'); const departmentRegisterModelSchema = require('./departmentRegisterModel'); -const ceoRegisterModelSchema = require('./ceoRegisterModel'); +const ceoModifyModelSchema = require('./ceoModifyModel'); const emailVerificationSchema = require('./emailVerificationModel'); const userDepartmentPatchSchema = require('./userDepartmentPatchModel'); +const ceoPasswordModelSchema = require('./ceoPasswordModel'); module.exports = { schemas: { @@ -14,8 +15,9 @@ module.exports = { usersLoginModelSchema, usersModifyModelSchema, departmentRegisterModelSchema, - ceoRegisterModelSchema, + ceoModifyModelSchema, emailVerificationSchema, - userDepartmentPatchSchema + userDepartmentPatchSchema, + ceoPasswordModelSchema }, }; \ No newline at end of file diff --git a/UC/backend/src/models/userDepartmentPatchModel.js b/UC/backend/src/models/userDepartmentPatchModel.js index 75e4992..5dd2c00 100644 --- a/UC/backend/src/models/userDepartmentPatchModel.js +++ b/UC/backend/src/models/userDepartmentPatchModel.js @@ -1,7 +1,11 @@ const Joi = require("joi"); const UserDepartmentPatchModel = Joi.object({ - department: Joi.string().uuid().required() + id: Joi.string().uuid().required().messages({ + 'any.required': 'User ID is required', + 'string.empty': 'User ID must not be empty', + 'string.uuid': 'User ID must be a valid UUID' + }) }); module.exports = UserDepartmentPatchModel; diff --git a/UC/backend/src/models/usersLoginModel.js b/UC/backend/src/models/usersLoginModel.js index 95f8156..880a0c0 100644 --- a/UC/backend/src/models/usersLoginModel.js +++ b/UC/backend/src/models/usersLoginModel.js @@ -1,8 +1,15 @@ const Joi = require('joi'); const usersLoginModel = Joi.object({ - email: Joi.string().email().required(), - password: Joi.string().required() + email: Joi.string().email().required().messages({ + 'any.required': 'Email is required', + 'string.empty': 'Email must not be empty', + 'string.email': 'Email must be a valid email address' + }), + password: Joi.string().required().messages({ + 'any.required': 'Password is required', + 'string.empty': 'Password must not be empty' + }) }); module.exports = usersLoginModel; \ No newline at end of file diff --git a/UC/backend/src/models/usersModifyModel.js b/UC/backend/src/models/usersModifyModel.js index cbcb5d2..5820256 100644 --- a/UC/backend/src/models/usersModifyModel.js +++ b/UC/backend/src/models/usersModifyModel.js @@ -1,9 +1,21 @@ const Joi = require('joi'); const usersModifyModel = Joi.object({ - name: Joi.string().required(), - email: Joi.string().email().required(), - password: Joi.string().min(8).max(20).required() + name: Joi.string().required().messages({ + 'any.required': 'User name is required', + 'string.empty': 'User name must not be empty' + }), + email: Joi.string().email().required().messages({ + 'any.required': 'User email is required', + 'string.empty': 'User email must not be empty', + 'string.email': 'User email must be a valid email address' + }), + password: Joi.string().min(8).max(20).required().messages({ + 'any.required': 'Password is required', + 'string.empty': 'Password must not be empty', + 'string.min': 'Password must be at least {#limit} characters long', + 'string.max': 'Password must be at most {#limit} characters long' + }) }); module.exports = usersModifyModel; \ No newline at end of file diff --git a/UC/backend/src/models/usersRegisterModel.js b/UC/backend/src/models/usersRegisterModel.js index 29d13c8..cef077a 100644 --- a/UC/backend/src/models/usersRegisterModel.js +++ b/UC/backend/src/models/usersRegisterModel.js @@ -1,10 +1,26 @@ const Joi = require('joi'); const usersRegisterModel = Joi.object({ - name: Joi.string().required(), - email: Joi.string().email().required(), - password: Joi.string().min(8).max(20).required(), - department: Joi.string().uuid().required() + name: Joi.string().required().messages({ + 'any.required': 'User name is required', + 'string.empty': 'User name must not be empty' + }), + email: Joi.string().email().required().messages({ + 'any.required': 'Email is required', + 'string.empty': 'Email must not be empty', + 'string.email': 'Email must be a valid email address' + }), + password: Joi.string().min(8).max(20).required().messages({ + 'any.required': 'Password is required', + 'string.empty': 'Password must not be empty', + 'string.min': 'Password must be at least {#limit} characters long', + 'string.max': 'Password must be at most {#limit} characters long' + }), + department: Joi.string().uuid().required().messages({ + 'any.required': 'Department ID is required', + 'string.empty': 'Department ID must not be empty', + 'string.uuid': 'Department ID must be a valid UUID' + }) }); module.exports = usersRegisterModel; \ No newline at end of file diff --git a/UC/backend/src/routes/users.js b/UC/backend/src/routes/users.js index b871e98..b7f4b5c 100644 --- a/UC/backend/src/routes/users.js +++ b/UC/backend/src/routes/users.js @@ -1,34 +1,171 @@ const express = require('express'); +const { v4: uuidv4 } = require('uuid'); + +const {schemas} = require('../models/schemaMapper'); +const {httpStatus} = require("../helpers/httpResponses"); + const router = express.Router(); function validateBody(req, res, next) { + let validationSchema = undefined; + + switch(req.path){ + case '/register': + validationSchema = schemas.usersRegisterModelSchema; + break; + case '/login': + validationSchema = schemas.usersLoginModelSchema; + break; + case '/validate_email': + validationSchema = schemas.emailVerificationSchema; + break; + case '/': + if(req.method === 'PUT'){ + validationSchema = schemas.usersModifyModelSchema; + } + break; + case 'change_department': + validationSchema = schemas.userDepartmentPatchSchema; + break; + default: + validationSchema = undefined; + } + + console.log(validationSchema); + + if(validationSchema !== undefined){ + const {error} = validationSchema.validate(req.body); + if(error){ + const errorMessage = error.details.map(detail => detail.message).join(', '); + return res.status(httpStatus.BAD_REQUEST).json({ message: errorMessage }); + } + } next(); } router.post('/register', validateBody, (req, res) => { + const usersDB = req.app.get('usersDB'); + const usersJson = usersDB.readFile(); + const { name, email, password, department } = req.body; + const hashedPassword = crypto.createHash('sha256').update(password).digest('hex'); + + const newUser = { + id: uuidv4(), + name: name, + email: email, + password: hashedPassword, + department: department + } + + usersJson.push(newUser); + usersDB.writeFile(usersJson); + + return res.status(httpStatus.CREATED).json({message: "User successfully created.", data: newUser}); }); router.post('/login', validateBody, (req, res) => { + const usersDB = req.app.get('usersDB'); const { email, password } = req.body; + const hashedPassword = crypto.createHash('sha256').update(password).digest('hex'); + + if(usersDB.findIndexByKeyValueInArray('email', email) !== + usersDB.findIndexByKeyValueInArray('password', hashedPassword)){ + return res.status(httpStatus.NOT_FOUND).json({message: 'Invalid credentials.'}); + } + + return res.status(httpStatus.Ok).json({message: 'Logged in.'}); }); -router.post('/email-validation', validateBody, (req, res) => { +router.post('/validate_email', validateBody, (req, res) => { + const usersDB = req.app.get('usersDB'); const { email } = req.body; + + if(usersDB.isKeyValue("email", email)){ + return res.status(httpStatus.CONFLICT).json({"message": "Email already in system."}); + } + + return res.status(httpStatus.OK).json({"message": "Email is valid."}); }); router.get('/', (req, res) => { + const usersDB = req.app.get('usersDB'); + return res.status(httpStatus.OK).json({ + message: "Fetched users", + data: usersDB.readFile() + }) }); router.put('/', validateBody, (req, res) => { + const usersDB = req.app.get('usersDB'); + let usersJson = usersDB.readFile(); + const { name, email, password } = req.body; + const userIndex = usersDB.findIndexByKeyValueInArray('email', email); + if(userIndex === -1){ + return res.status(httpStatus.NOT_FOUND).json({message: 'User not found.'}); + } + + const hashedPassword = crypto.createHash('sha256').update(password).digest('hex'); + let userInfo = usersJson[userIndex]; + userInfo = { + id: userInfo.id, + name: name, + email: email, + password: hashedPassword, + department: userInfo.department + } + + usersJson[userIndex] = userInfo; + usersDB.writeFile(usersJson); + + return res.status(httpStatus.OK).json({message: 'Information modified'}); }); -router.patch('/change-department', validateBody, (req, res) => { +router.patch('/change_department', validateBody, (req, res) => { + const usersDB = req.app.get('usersDB'); const { id, department } = req.body; + + const userIndex = usersDB.findIndexByKeyValueInArray('id', id); + if(userIndex === -1){ + return res.status(httpStatus.NOT_FOUND).json({message: "User not found."}); + } + + let usersJson = usersDB.readFile(); + let userInfo = usersJson[userIndex]; + + userInfo = { + id: userInfo.id, + name: userInfo.name, + email: userInfo.email, + password: userInfo.password, + department: department + } + + usersJson[userIndex] = userInfo + usersDB.writeFile(usersJson); + + return res.status(httpStatus.Ok).json({message: "Department modified."}); }); router.get('/get_decrypt_keys', (req, res) => { + const ceoDB = req.app.get('ceoDB'); + const departmentsDB = req.app.get('departmentsDB'); + const ceoPassword = req.headers['ceo_password']; + const {password} = ceoDB.readFile() + + if(ceoPassword !== password){ + return res.status(httpStatus.UNAUTHORIZED).json({message: 'Invalid CEO password.'}); + } + + return res.status(httpStatus.OK).json({ + message: 'Departments fetched.', + data: departmentsDB.readFile() + }) }); +router.use((req, res) => { + return res.status(httpStatus.NOT_FOUND).json({message: "Endpoint not found"}); +}) + module.exports = router; diff --git a/UC/frontend/public/js/main_menu.js b/UC/frontend/public/js/main_menu.js index 02f25ca..70dff71 100644 --- a/UC/frontend/public/js/main_menu.js +++ b/UC/frontend/public/js/main_menu.js @@ -70,7 +70,8 @@ document.addEventListener('DOMContentLoaded', function() { fetch(endpoint, { method: 'GET', headers: { - 'Authorization': id + 'Authorization': id, + 'x-api-key': "uc_api" }, }) .then(async response => { @@ -116,7 +117,8 @@ document.addEventListener('DOMContentLoaded', function() { fetch(endpoint, { method: 'GET', headers: { - 'Authorization': id + 'Authorization': id, + 'x-api-key': "uc_api" }, }) .then(async response => { @@ -162,7 +164,8 @@ document.addEventListener('DOMContentLoaded', function() { fetch(endpoint, { method: 'GET', headers: { - 'Authorization': id + 'Authorization': id, + 'x-api-key': "uc_api" }, }) .then(async response => {