mirror of
				https://github.com/xfarrow/blink
				synced 2025-06-27 09:03:02 +02:00 
			
		
		
		
	code refactoring
This commit is contained in:
		| @@ -15,7 +15,9 @@ | |||||||
| const express = require('express'); | const express = require('express'); | ||||||
| const cors = require('cors'); | const cors = require('cors'); | ||||||
| const rateLimit = require('express-rate-limit'); | const rateLimit = require('express-rate-limit'); | ||||||
| const apiController = require('./controllers/api_controller.js'); | const apiController = require('./controllers/api_controller.js'); // todo refactor | ||||||
|  | const personRoutes = require('./routes/person_routes.js'); | ||||||
|  | const jwt_utils = require('./utils/jwt_utils.js'); | ||||||
| require('dotenv').config(); | require('dotenv').config(); | ||||||
|  |  | ||||||
| // Application configuration | // Application configuration | ||||||
| @@ -29,15 +31,15 @@ app.use(rateLimit({ | |||||||
| })); // Apply the rate limiter middleware to all routes | })); // Apply the rate limiter middleware to all routes | ||||||
|  |  | ||||||
| const publicRoutes = express.Router(); | const publicRoutes = express.Router(); | ||||||
| publicRoutes.post('/register', apiController.registerPerson); | publicRoutes.post('/register', personRoutes.registerPerson); | ||||||
| publicRoutes.post('/login', apiController.login); | publicRoutes.post('/login', personRoutes.login); | ||||||
|  |  | ||||||
| const protectedRoutes = express.Router(); | const protectedRoutes = express.Router(); | ||||||
| protectedRoutes.use(apiController.verifyToken); | protectedRoutes.use(jwt_utils.verifyToken); | ||||||
| protectedRoutes.get('/person/myself', apiController.getMyself); | protectedRoutes.get('/person/myself', personRoutes.getMyself); | ||||||
| protectedRoutes.get('/person/:id', apiController.getPerson); | protectedRoutes.get('/person/:id', personRoutes.getPerson); | ||||||
| protectedRoutes.put('/person/:id', apiController.updatePerson); | protectedRoutes.put('/person/:id', personRoutes.updatePerson); | ||||||
| protectedRoutes.delete('/person/delete', apiController.deletePerson); | protectedRoutes.delete('/person/delete', personRoutes.deletePerson); | ||||||
| protectedRoutes.post('/organization/admin', apiController.addOrganizationAdmin); | protectedRoutes.post('/organization/admin', apiController.addOrganizationAdmin); | ||||||
| protectedRoutes.delete('/organization/removeadmin', apiController.removeOrganizationAdmin); | protectedRoutes.delete('/organization/removeadmin', apiController.removeOrganizationAdmin); | ||||||
| protectedRoutes.post('/organization', apiController.createOrganization); | protectedRoutes.post('/organization', apiController.createOrganization); | ||||||
|   | |||||||
| @@ -19,259 +19,6 @@ const jwt = require('jsonwebtoken'); | |||||||
|  |  | ||||||
| // ======== BEGIN API ENDPOINTS ======== | // ======== BEGIN API ENDPOINTS ======== | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * POST Request |  | ||||||
|  * |  | ||||||
|  * Registers a Person |  | ||||||
|  *  |  | ||||||
|  * Required field(s): name, email (valid ISO standard), password |  | ||||||
|  *  |  | ||||||
|  * @returns The activationlink identifier |  | ||||||
|  */ |  | ||||||
| async function registerPerson(req, res){ |  | ||||||
|    |  | ||||||
|     if (process.env.ALLOW_USER_REGISTRATION === 'false'){ |  | ||||||
|       return res.status(403).json({error : "Users cannot register on this server"}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Ensure that the required fields are present before proceeding |  | ||||||
|     if (!req.body.display_name || !req.body.email || !req.body.password) { |  | ||||||
|       return res.status(400).json({ error : "Some or all required fields are missing"}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if(!validateEmail(req.body.email)){ |  | ||||||
|       return res.status(400).json({ error : "The email is not in a valid format"}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Generate activation link token |  | ||||||
|     const activationLink = crypto.randomBytes(16).toString('hex'); |  | ||||||
|  |  | ||||||
|     // Hash provided password |  | ||||||
|     const hashPasswordPromise = bcrypt.hash(req.body.password, 10); |  | ||||||
|  |  | ||||||
|     try{ |  | ||||||
|  |  | ||||||
|       // Check whether e-mail exists already (enforced by database constraints) |  | ||||||
|       const existingUser = await knex('Person') |  | ||||||
|         .where('email', req.body.email) |  | ||||||
|         .first(); |  | ||||||
|  |  | ||||||
|         if(existingUser){ |  | ||||||
|           return res.status(409).json({ error: "E-mail already in use" }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // We need to insert either both in the "Person" table |  | ||||||
|         // and in the "ActivationLink" one, or in neither |  | ||||||
|         await knex.transaction(async (tr) => { |  | ||||||
|            |  | ||||||
|           const personIdResult = await tr('Person') |  | ||||||
|             .insert({  |  | ||||||
|               email: req.body.email,  |  | ||||||
|               password: await hashPasswordPromise, |  | ||||||
|               display_name: req.body.display_name, |  | ||||||
|               date_of_birth: req.body.date_of_birth, |  | ||||||
|               available: req.body.available, |  | ||||||
|               enabled: true, |  | ||||||
|               place_of_living: req.body.place_of_living |  | ||||||
|             }) |  | ||||||
|             .returning("id"); |  | ||||||
|    |  | ||||||
|           await tr('ActivationLink') |  | ||||||
|             .insert({ |  | ||||||
|               person_id: personIdResult[0].id, |  | ||||||
|               identifier: activationLink |  | ||||||
|             }); |  | ||||||
|       }); |  | ||||||
|       return res.status(200).json({ activationLink: activationLink }); |  | ||||||
|     } |  | ||||||
|     catch (error){ |  | ||||||
|       console.error('Error registering person:', error); |  | ||||||
|       res.status(500).json({error : "Internal server error"}); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * POST Request |  | ||||||
|  *  |  | ||||||
|  * Creates a token if the specified email |  | ||||||
|  * and password are valid. |  | ||||||
|  *  |  | ||||||
|  * Required field(s): email, password |  | ||||||
|  * |  | ||||||
|  * @returns The token |  | ||||||
|  */ |  | ||||||
| async function login(req, res){ |  | ||||||
|  |  | ||||||
|   // Ensure that the required fields are present before proceeding |  | ||||||
|   if (!req.body.email || !req.body.password) { |  | ||||||
|     return res.status(400).json({error : "Invalid request"}); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const person = await checkUserCredentials(req.body.email, req.body.password); |  | ||||||
|  |  | ||||||
|   if (person){ |  | ||||||
|     const token = generateToken(person.id); |  | ||||||
|     res.status(200).json({token: token }); |  | ||||||
|   } |  | ||||||
|   else{  |  | ||||||
|     res.status(401).json({error : "Unauthorized"}); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Obtain a Person's details if the |  | ||||||
|  * Person to retrieve is either myself or an |  | ||||||
|  * enabled Person. |  | ||||||
|  *  |  | ||||||
|  * Required field(s): none |  | ||||||
|  * |  | ||||||
|  * @returns The Person |  | ||||||
|  */ |  | ||||||
| async function getPerson(req, res){ |  | ||||||
|   try { |  | ||||||
|     const user = await knex('Person') |  | ||||||
|       .select('*') |  | ||||||
|       .where({ id: req.params.id }) |  | ||||||
|       .first(); |  | ||||||
|      |  | ||||||
|     if(user){ |  | ||||||
|       // I am retrieving either myself or an enabled user |  | ||||||
|       if(user.id == req.jwt.person_id || user.enabled){ |  | ||||||
|         delete user['password']; // remove password field for security reasons |  | ||||||
|         return res.status(200).send(user); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return res.status(404).json({error: "Not found"}); |  | ||||||
|   } |  | ||||||
|   catch (error) { |  | ||||||
|     console.log("Error while getting person: " + error); |  | ||||||
|     return res.status(500).json({error : "Internal server error"}); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  *  |  | ||||||
|  * GET Request |  | ||||||
|  *  |  | ||||||
|  * Get myself, from the JWT token |  | ||||||
|  * |  | ||||||
|  * @returns Person's details |  | ||||||
|  */ |  | ||||||
| async function getMyself(req, res){ |  | ||||||
|   try{ |  | ||||||
|     const person = await knex('Person') |  | ||||||
|       .select('*') |  | ||||||
|       .where({ id: req.jwt.person_id }) |  | ||||||
|       .first(); |  | ||||||
|  |  | ||||||
|       console.log(req.jwt.person_id); |  | ||||||
|  |  | ||||||
|       if(person){ |  | ||||||
|         delete person['password']; |  | ||||||
|         return res.status(200).send(person); |  | ||||||
|       } |  | ||||||
|       return res.status(404).json({error: "Not found"}); |  | ||||||
|   } |  | ||||||
|   catch (error){ |  | ||||||
|     console.log("Error while getting myself: " + error); |  | ||||||
|     return res.status(500).json({error : "Internal server error"}); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * PUT request |  | ||||||
|  *  |  | ||||||
|  * Updates a Person's details. If some details are |  | ||||||
|  * not present, they shall be ignored. |  | ||||||
|  *  |  | ||||||
|  * Required field(s): none. Both old_password and |  | ||||||
|  * new_password if updating the password. |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| async function updatePerson(req, res){ |  | ||||||
|    |  | ||||||
|   if (req.jwt.person_id != req.params.id){ |  | ||||||
|     return res.status(403).json({ error : "Forbidden"}); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const updatePerson = {}; |  | ||||||
|  |  | ||||||
|   if(req.body.display_name){ |  | ||||||
|     updatePerson.display_name = req.body.display_name; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if(req.body.date_of_birth){ |  | ||||||
|     if(isPostgresDateFormatValid(req.body.date_of_birth)){ |  | ||||||
|       updatePerson.date_of_birth = req.body.date_of_birth; |  | ||||||
|     } |  | ||||||
|     else{ |  | ||||||
|       return res.status(400).json({ error : "Date of birth format not valid. Please specify a YYYY-MM-DD date"}); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if(req.body.available){ |  | ||||||
|     updatePerson.available = req.body.available; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if(req.body.place_of_living){ |  | ||||||
|     updatePerson.place_of_living = req.body.place_of_living; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // If we are tying to change password, the old password must be provided |  | ||||||
|   if(req.body.old_password && req.body.new_password){ |  | ||||||
|     const user = await knex('Person') |  | ||||||
|       .select('password') |  | ||||||
|       .where({ id: req.jwt.person_id }) |  | ||||||
|       .first(); |  | ||||||
|       const passwordMatches = await bcrypt.compare(req.body.old_password, user.password); |  | ||||||
|       if(passwordMatches){ |  | ||||||
|         updatePerson.password = await bcrypt.hash(req.body.new_password, 10); |  | ||||||
|       } |  | ||||||
|       else{ |  | ||||||
|         return res.status(401).json({ error : "Password verification failed"}); |  | ||||||
|       } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (Object.keys(updatePerson).length === 0) { |  | ||||||
|     return res.status(400).json({ error : "Bad request. No data to update"}); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   try { |  | ||||||
|     await knex('Person') |  | ||||||
|       .where('id', req.params.id) |  | ||||||
|       .update(updatePerson); |  | ||||||
|     return res.status(200).json({ success : "true"}); |  | ||||||
|   } |  | ||||||
|   catch (error) { |  | ||||||
|     console.log("Error while updating a Person: " + error); |  | ||||||
|     return res.status(500).json({ error : "Internal server error"}); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * GET Request |  | ||||||
|  *  |  | ||||||
|  * Deletes a Person. An user can only delete  |  | ||||||
|  * themselves. |  | ||||||
|  *  |  | ||||||
|  * Required field(s): none |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| async function deletePerson(req, res) { |  | ||||||
|   try { |  | ||||||
|     await knex('Person') |  | ||||||
|       .where({id : req.jwt.person_id}) |  | ||||||
|       .del(); |  | ||||||
|     return res.status(200).json({success: true}); |  | ||||||
|  |  | ||||||
|     // TODO: Delete Organization if this user was its only administrator |  | ||||||
|   }  |  | ||||||
|   catch (error) { |  | ||||||
|     console.log("Error deleting a Person: " + error); |  | ||||||
|     return res.status(500).json({error : "Internal server error"}); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * POST Request |  * POST Request | ||||||
|  *  |  *  | ||||||
| @@ -642,80 +389,10 @@ async function removeOrganizationAdmin(req, res){ | |||||||
|  |  | ||||||
| // ======== END API ENDPOINTS ======== | // ======== END API ENDPOINTS ======== | ||||||
|  |  | ||||||
| async function checkUserCredentials(email, password){ |  | ||||||
|   try { |  | ||||||
|     const user = await knex('Person') |  | ||||||
|       .where('email', email) |  | ||||||
|       .where('enabled', true) |  | ||||||
|       .select('*') |  | ||||||
|       .first(); |  | ||||||
|  |  | ||||||
|     if(user){ |  | ||||||
|       const passwordMatches = await bcrypt.compare(password, user.password); |  | ||||||
|       if (passwordMatches) { |  | ||||||
|         return user; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|   catch (error) { |  | ||||||
|     console.log(error); |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function generateToken(person_id) { |  | ||||||
|   // The payload the JWT will carry within itself |  | ||||||
|   const payload = { |  | ||||||
|     person_id: person_id |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const token = jwt.sign(payload, process.env.JWT_SECRET_KEY, {  |  | ||||||
|     expiresIn: '8h'  |  | ||||||
|   }); |  | ||||||
|   return token; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Middlware |  | ||||||
| function verifyToken(req, res, next) { |  | ||||||
|   const token = req.headers.authorization; |  | ||||||
|  |  | ||||||
|   if (!token) { |  | ||||||
|     return res.status(401).send({error : 'No token provided'}); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   jwt.verify(token, process.env.JWT_SECRET_KEY, (err, decoded) => { |  | ||||||
|     if (err) { |  | ||||||
|       return res.status(401).send({error : 'Failed to authenticate token'}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // If the token is valid, store the decoded data in the request object |  | ||||||
|     req.jwt = decoded; |  | ||||||
|     next(); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function validateEmail(email) { |  | ||||||
|   const regex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/; |  | ||||||
|   return regex.test(email); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function isPostgresDateFormatValid(dateString) { |  | ||||||
|   const regex = /^\d{4}-\d{2}-\d{2}$/; |  | ||||||
|   return regex.test(dateString); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Exporting a function | // Exporting a function | ||||||
| // means making a JavaScript function defined in one | // means making a JavaScript function defined in one | ||||||
| // module available for use in another module. | // module available for use in another module. | ||||||
| module.exports = { | module.exports = { | ||||||
|     registerPerson, |  | ||||||
|     login, |  | ||||||
|     getPerson, |  | ||||||
|     getMyself, |  | ||||||
|     updatePerson, |  | ||||||
|     deletePerson, |  | ||||||
|     verifyToken, |  | ||||||
|     createOrganization, |     createOrganization, | ||||||
|     getOrganization, |     getOrganization, | ||||||
|     updateOrganization, |     updateOrganization, | ||||||
|   | |||||||
| @@ -0,0 +1,294 @@ | |||||||
|  | // Blink-specific modules | ||||||
|  | const validator = require('../utils/validation'); | ||||||
|  | const knex = require('../utils/knex_config'); | ||||||
|  | const jwt_utils = require('../utils/jwt_utils'); | ||||||
|  | // Other modules | ||||||
|  | const bcrypt = require('bcrypt'); | ||||||
|  | const crypto = require('crypto'); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * POST Request | ||||||
|  |  * | ||||||
|  |  * Registers a Person | ||||||
|  |  *  | ||||||
|  |  * Required field(s): name, email (valid ISO standard), password | ||||||
|  |  *  | ||||||
|  |  * @returns The activationlink identifier | ||||||
|  |  */ | ||||||
|  | async function registerPerson(req, res){ | ||||||
|  |    | ||||||
|  |     if (process.env.ALLOW_USER_REGISTRATION === 'false'){ | ||||||
|  |       return res.status(403).json({error : "Users cannot register on this server"}); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Ensure that the required fields are present before proceeding | ||||||
|  |     if (!req.body.display_name || !req.body.email || !req.body.password) { | ||||||
|  |       return res.status(400).json({ error : "Some or all required fields are missing"}); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if(!validator.validateEmail(req.body.email)){ | ||||||
|  |       return res.status(400).json({ error : "The email is not in a valid format"}); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Generate activation link token | ||||||
|  |     const activationLink = crypto.randomBytes(16).toString('hex'); | ||||||
|  |  | ||||||
|  |     // Hash provided password | ||||||
|  |     const hashPasswordPromise = bcrypt.hash(req.body.password, 10); | ||||||
|  |  | ||||||
|  |     try{ | ||||||
|  |  | ||||||
|  |       // Check whether e-mail exists already (enforced by database constraints) | ||||||
|  |       const existingUser = await knex('Person') | ||||||
|  |         .where('email', req.body.email) | ||||||
|  |         .first(); | ||||||
|  |  | ||||||
|  |         if(existingUser){ | ||||||
|  |           return res.status(409).json({ error: "E-mail already in use" }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // We need to insert either both in the "Person" table | ||||||
|  |         // and in the "ActivationLink" one, or in neither | ||||||
|  |         await knex.transaction(async (tr) => { | ||||||
|  |            | ||||||
|  |           const personIdResult = await tr('Person') | ||||||
|  |             .insert({  | ||||||
|  |               email: req.body.email,  | ||||||
|  |               password: await hashPasswordPromise, | ||||||
|  |               display_name: req.body.display_name, | ||||||
|  |               date_of_birth: req.body.date_of_birth, | ||||||
|  |               available: req.body.available, | ||||||
|  |               enabled: true, | ||||||
|  |               place_of_living: req.body.place_of_living | ||||||
|  |             }) | ||||||
|  |             .returning("id"); | ||||||
|  |    | ||||||
|  |           await tr('ActivationLink') | ||||||
|  |             .insert({ | ||||||
|  |               person_id: personIdResult[0].id, | ||||||
|  |               identifier: activationLink | ||||||
|  |             }); | ||||||
|  |       }); | ||||||
|  |       return res.status(200).json({ activationLink: activationLink }); | ||||||
|  |     } | ||||||
|  |     catch (error){ | ||||||
|  |       console.error('Error registering person:', error); | ||||||
|  |       res.status(500).json({error : "Internal server error"}); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * POST Request | ||||||
|  |  *  | ||||||
|  |  * Creates a token if the specified email | ||||||
|  |  * and password are valid. | ||||||
|  |  *  | ||||||
|  |  * Required field(s): email, password | ||||||
|  |  * | ||||||
|  |  * @returns The token | ||||||
|  |  */ | ||||||
|  | async function login(req, res){ | ||||||
|  |  | ||||||
|  |   // Ensure that the required fields are present before proceeding | ||||||
|  |   if (!req.body.email || !req.body.password) { | ||||||
|  |     return res.status(400).json({error : "Invalid request"}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const person = await checkUserCredentials(req.body.email, req.body.password); | ||||||
|  |  | ||||||
|  |   if (person){ | ||||||
|  |     const token = jwt_utils.generateToken(person.id); | ||||||
|  |     res.status(200).json({token: token }); | ||||||
|  |   } | ||||||
|  |   else{  | ||||||
|  |     res.status(401).json({error : "Unauthorized"}); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Obtain a Person's details if the | ||||||
|  |  * Person to retrieve is either myself or an | ||||||
|  |  * enabled Person. | ||||||
|  |  *  | ||||||
|  |  * Required field(s): none | ||||||
|  |  * | ||||||
|  |  * @returns The Person | ||||||
|  |  */ | ||||||
|  | async function getPerson(req, res){ | ||||||
|  |   try { | ||||||
|  |     const user = await knex('Person') | ||||||
|  |       .select('*') | ||||||
|  |       .where({ id: req.params.id }) | ||||||
|  |       .first(); | ||||||
|  |      | ||||||
|  |     if(user){ | ||||||
|  |       // I am retrieving either myself or an enabled user | ||||||
|  |       if(user.id == req.jwt.person_id || user.enabled){ | ||||||
|  |         delete user['password']; // remove password field for security reasons | ||||||
|  |         return res.status(200).send(user); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return res.status(404).json({error: "Not found"}); | ||||||
|  |   } | ||||||
|  |   catch (error) { | ||||||
|  |     console.log("Error while getting person: " + error); | ||||||
|  |     return res.status(500).json({error : "Internal server error"}); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * GET Request | ||||||
|  |  *  | ||||||
|  |  * Get myself, from the JWT token | ||||||
|  |  * | ||||||
|  |  * @returns Person's details | ||||||
|  |  */ | ||||||
|  | async function getMyself(req, res){ | ||||||
|  |   try{ | ||||||
|  |     const person = await knex('Person') | ||||||
|  |       .select('*') | ||||||
|  |       .where({ id: req.jwt.person_id }) | ||||||
|  |       .first(); | ||||||
|  |  | ||||||
|  |       console.log(req.jwt.person_id); | ||||||
|  |  | ||||||
|  |       if(person){ | ||||||
|  |         delete person['password']; | ||||||
|  |         return res.status(200).send(person); | ||||||
|  |       } | ||||||
|  |       return res.status(404).json({error: "Not found"}); | ||||||
|  |   } | ||||||
|  |   catch (error){ | ||||||
|  |     console.log("Error while getting myself: " + error); | ||||||
|  |     return res.status(500).json({error : "Internal server error"}); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * PUT request | ||||||
|  |  *  | ||||||
|  |  * Updates a Person's details. If some details are | ||||||
|  |  * not present, they shall be ignored. | ||||||
|  |  *  | ||||||
|  |  * Required field(s): none. Both old_password and | ||||||
|  |  * new_password if updating the password. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | async function updatePerson(req, res){ | ||||||
|  |    | ||||||
|  |   if (req.jwt.person_id != req.params.id){ | ||||||
|  |     return res.status(403).json({ error : "Forbidden"}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const updatePerson = {}; | ||||||
|  |  | ||||||
|  |   if(req.body.display_name){ | ||||||
|  |     updatePerson.display_name = req.body.display_name; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if(req.body.date_of_birth){ | ||||||
|  |     if(validator.isPostgresDateFormatValid(req.body.date_of_birth)){ | ||||||
|  |       updatePerson.date_of_birth = req.body.date_of_birth; | ||||||
|  |     } | ||||||
|  |     else{ | ||||||
|  |       return res.status(400).json({ error : "Date of birth format not valid. Please specify a YYYY-MM-DD date"}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if(req.body.available){ | ||||||
|  |     updatePerson.available = req.body.available; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if(req.body.place_of_living){ | ||||||
|  |     updatePerson.place_of_living = req.body.place_of_living; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // If we are tying to change password, the old password must be provided | ||||||
|  |   if(req.body.old_password && req.body.new_password){ | ||||||
|  |     const user = await knex('Person') | ||||||
|  |       .select('password') | ||||||
|  |       .where({ id: req.jwt.person_id }) | ||||||
|  |       .first(); | ||||||
|  |       const passwordMatches = await bcrypt.compare(req.body.old_password, user.password); | ||||||
|  |       if(passwordMatches){ | ||||||
|  |         updatePerson.password = await bcrypt.hash(req.body.new_password, 10); | ||||||
|  |       } | ||||||
|  |       else{ | ||||||
|  |         return res.status(401).json({ error : "Password verification failed"}); | ||||||
|  |       } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (Object.keys(updatePerson).length === 0) { | ||||||
|  |     return res.status(400).json({ error : "Bad request. No data to update"}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   try { | ||||||
|  |     await knex('Person') | ||||||
|  |       .where('id', req.params.id) | ||||||
|  |       .update(updatePerson); | ||||||
|  |     return res.status(200).json({ success : "true"}); | ||||||
|  |   } | ||||||
|  |   catch (error) { | ||||||
|  |     console.log("Error while updating a Person: " + error); | ||||||
|  |     return res.status(500).json({ error : "Internal server error"}); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * GET Request | ||||||
|  |  *  | ||||||
|  |  * Deletes a Person. An user can only delete  | ||||||
|  |  * themselves. | ||||||
|  |  *  | ||||||
|  |  * Required field(s): none | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | async function deletePerson(req, res) { | ||||||
|  |   try { | ||||||
|  |     await knex('Person') | ||||||
|  |       .where({id : req.jwt.person_id}) | ||||||
|  |       .del(); | ||||||
|  |     return res.status(200).json({success: true}); | ||||||
|  |  | ||||||
|  |     // TODO: Delete Organization if this user was its only administrator | ||||||
|  |   }  | ||||||
|  |   catch (error) { | ||||||
|  |     console.log("Error deleting a Person: " + error); | ||||||
|  |     return res.status(500).json({error : "Internal server error"}); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function checkUserCredentials(email, password){ | ||||||
|  |     try { | ||||||
|  |       const user = await knex('Person') | ||||||
|  |         .where('email', email) | ||||||
|  |         .where('enabled', true) | ||||||
|  |         .select('*') | ||||||
|  |         .first(); | ||||||
|  |    | ||||||
|  |       if(user){ | ||||||
|  |         const passwordMatches = await bcrypt.compare(password, user.password); | ||||||
|  |         if (passwordMatches) { | ||||||
|  |           return user; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     catch (error) { | ||||||
|  |       console.log(error); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | // Exporting a function | ||||||
|  | // means making a JavaScript function defined in one | ||||||
|  | // module available for use in another module. | ||||||
|  | module.exports = { | ||||||
|  |     registerPerson, | ||||||
|  |     login, | ||||||
|  |     getPerson, | ||||||
|  |     getMyself, | ||||||
|  |     updatePerson, | ||||||
|  |     deletePerson | ||||||
|  | }; | ||||||
							
								
								
									
										37
									
								
								backend/apis/nodejs/src/utils/jwt_utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								backend/apis/nodejs/src/utils/jwt_utils.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | const jwt = require('jsonwebtoken'); | ||||||
|  |  | ||||||
|  | function generateToken(person_id) { | ||||||
|  |     // The payload the JWT will carry within itself | ||||||
|  |     const payload = { | ||||||
|  |       person_id: person_id | ||||||
|  |     }; | ||||||
|  |    | ||||||
|  |     const token = jwt.sign(payload, process.env.JWT_SECRET_KEY, {  | ||||||
|  |       expiresIn: '8h'  | ||||||
|  |     }); | ||||||
|  |     return token; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Middlware | ||||||
|  |   function verifyToken(req, res, next) { | ||||||
|  |     const token = req.headers.authorization; | ||||||
|  |    | ||||||
|  |     if (!token) { | ||||||
|  |       return res.status(401).send({error : 'No token provided'}); | ||||||
|  |     } | ||||||
|  |    | ||||||
|  |     jwt.verify(token, process.env.JWT_SECRET_KEY, (err, decoded) => { | ||||||
|  |       if (err) { | ||||||
|  |         return res.status(401).send({error : 'Failed to authenticate token'}); | ||||||
|  |       } | ||||||
|  |    | ||||||
|  |       // If the token is valid, store the decoded data in the request object | ||||||
|  |       req.jwt = decoded; | ||||||
|  |       next(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   module.exports = { | ||||||
|  |     generateToken, | ||||||
|  |     verifyToken | ||||||
|  | }; | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | /** | ||||||
|  |  * Checks whether an e-mail is in a valid format | ||||||
|  |  * @param {*} email email to validate  | ||||||
|  |  * @returns true or false | ||||||
|  |  */ | ||||||
|  | function validateEmail(email) { | ||||||
|  |     const regex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/; | ||||||
|  |     return regex.test(email); | ||||||
|  | } | ||||||
|  |    | ||||||
|  | /** | ||||||
|  |  * Checks whether a date is in a correct Postgres | ||||||
|  |  * format (YYYY-MM-DD) | ||||||
|  |  * @param {*} dateString the date to validate  | ||||||
|  |  * @returns true or false | ||||||
|  |  */ | ||||||
|  | function isPostgresDateFormatValid(dateString) { | ||||||
|  |     const regex = /^\d{4}-\d{2}-\d{2}$/; | ||||||
|  |     return regex.test(dateString); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     validateEmail, | ||||||
|  |     isPostgresDateFormatValid | ||||||
|  | }; | ||||||
		Reference in New Issue
	
	Block a user