Revert "ran 'npx standard --fix'"

This reverts commit 0dc67edc9baafaff19d7edebc1ccb3144d5397e8.
This commit is contained in:
xfarrow 2024-02-22 17:39:29 +01:00
parent 0dc67edc9b
commit 2a321916ff
12 changed files with 596 additions and 564 deletions

View File

@ -13,53 +13,54 @@
// Importing modules // Importing modules
// TODO: clean up // TODO: clean up
require('dotenv').config() require('dotenv').config();
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 personRoutes = require('./routes/person_routes.js') const personRoutes = require('./routes/person_routes.js');
const organizationRoutes = require('./routes/organization_routes.js') const organizationRoutes = require('./routes/organization_routes.js');
const organizationAdminRoutes = require('./routes/organization_admin_routes.js') const organizationAdminRoutes = require('./routes/organization_admin_routes.js');
const organizationPostRoutes = require('./routes/organization_post_routes.js') const organizationPostRoutes = require('./routes/organization_post_routes.js')
const jwt_utils = require('./utils/jwt_utils.js') const jwt_utils = require('./utils/jwt_utils.js');
// Application configuration // Application configuration
const app = express() const app = express();
app.use(express.json()) // Middleware which parses JSON for POST requests app.use(express.json()); // Middleware which parses JSON for POST requests
app.use(cors()) // Enable CORS for all routes app.use(cors()); // Enable CORS for all routes
app.use(rateLimit({ app.use(rateLimit({
windowMs: process.env.LIMITER_WINDOW, windowMs: process.env.LIMITER_WINDOW,
max: process.env.LIMITER_MAXIMUM_PER_WINDOW, max: process.env.LIMITER_MAXIMUM_PER_WINDOW,
message: { error: 'Too many requests from this IP, please try again later' } message: {error : "Too many requests from this IP, please try again later"}
})) // 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', personRoutes.registerPerson) publicRoutes.post('/register', personRoutes.registerPerson);
publicRoutes.post('/login', personRoutes.login) publicRoutes.post('/login', personRoutes.login);
const protectedRoutes = express.Router() const protectedRoutes = express.Router();
protectedRoutes.use(jwt_utils.verifyToken) protectedRoutes.use(jwt_utils.verifyToken);
protectedRoutes.get('/person/myself', personRoutes.getMyself) protectedRoutes.get('/person/myself', personRoutes.getMyself);
protectedRoutes.get('/person/:id', personRoutes.getPerson) protectedRoutes.get('/person/:id', personRoutes.getPerson);
protectedRoutes.put('/person/:id', personRoutes.updatePerson) protectedRoutes.put('/person/:id', personRoutes.updatePerson);
protectedRoutes.delete('/person/delete', personRoutes.deletePerson) protectedRoutes.delete('/person/delete', personRoutes.deletePerson);
protectedRoutes.post('/organization/admin', organizationAdminRoutes.addOrganizationAdmin) protectedRoutes.post('/organization/admin', organizationAdminRoutes.addOrganizationAdmin);
protectedRoutes.delete('/organization/removeadmin', organizationAdminRoutes.removeOrganizationAdmin) protectedRoutes.delete('/organization/removeadmin', organizationAdminRoutes.removeOrganizationAdmin);
protectedRoutes.post('/organization', organizationRoutes.createOrganization) protectedRoutes.post('/organization', organizationRoutes.createOrganization);
protectedRoutes.get('/organization/:id', organizationRoutes.getOrganization) protectedRoutes.get('/organization/:id', organizationRoutes.getOrganization);
protectedRoutes.put('/organization/:id', organizationRoutes.updateOrganization) protectedRoutes.put('/organization/:id', organizationRoutes.updateOrganization);
protectedRoutes.delete('/organization/:id', organizationRoutes.deleteOrganization) protectedRoutes.delete('/organization/:id', organizationRoutes.deleteOrganization);
protectedRoutes.post('/organization/post', organizationPostRoutes.createOrganizationPost) protectedRoutes.post('/organization/post', organizationPostRoutes.createOrganizationPost);
protectedRoutes.delete('/organization/post/:id', organizationPostRoutes.deleteOrganizationPost) protectedRoutes.delete('/organization/post/:id', organizationPostRoutes.deleteOrganizationPost);
// Mounting routes // Mounting routes
app.use('/api', publicRoutes) // Routes not requiring token app.use('/api', publicRoutes); // Routes not requiring token
app.use('/api', protectedRoutes) // Routes requiring token app.use('/api', protectedRoutes); // Routes requiring token
// Start the server. Default port is 3000 // Start the server. Default port is 3000
const port = process.env.API_SERVER_PORT || 3000 const port = process.env.API_SERVER_PORT || 3000;
app.listen(port, () => { app.listen(port, () => {
console.log(`Blink API server is running on port ${port}`) console.log(`Blink API server is running on port ${port}`);
}) });
module.exports = app module.exports = app;

View File

@ -11,7 +11,7 @@
IN THE SOFTWARE. IN THE SOFTWARE.
*/ */
const knex = require('../utils/knex_config') const knex = require('../utils/knex_config');
/** /**
* Create Organization object * Create Organization object
@ -23,12 +23,12 @@ const knex = require('../utils/knex_config')
*/ */
function organization(name, location, description, is_hiring){ function organization(name, location, description, is_hiring){
const organization = { const organization = {
name, name: name,
location, location: location,
description, description: description,
is_hiring is_hiring: is_hiring
} };
return organization return organization;
} }
/** /**
@ -40,8 +40,8 @@ async function getOrganizationById (id) {
const organization = await knex('Organization') const organization = await knex('Organization')
.where('id', id) .where('id', id)
.select('*') .select('*')
.first() .first();
return organization return organization;
} }
/** /**
@ -53,15 +53,15 @@ async function insertOrganization (organization, organizationAdministratorId) {
// We have to insert either both in Organization and in OrganizationAdministrator // We have to insert either both in Organization and in OrganizationAdministrator
// or in neither // or in neither
const organizationResult = await trx('Organization') const organizationResult = await trx('Organization')
.insert(organization, '*') .insert(organization, '*');
// Inserting in the "OrganizationAdministrator" table // Inserting in the "OrganizationAdministrator" table
await trx('OrganizationAdministrator') await trx('OrganizationAdministrator')
.insert({ .insert({
id_person: organizationAdministratorId, id_person: organizationAdministratorId,
id_organization: organizationResult[0].id id_organization: organizationResult[0].id,
}) });
}) });
} }
/** /**
@ -110,8 +110,8 @@ async function updateOrganizationIfAdministrator (organization, organizationId,
.where('id_person', personId) .where('id_person', personId)
.where('id_organization', organizationId) .where('id_organization', organizationId)
}) })
.update(organization) .update(organization);
return numberOfUpdatedRows == 1 return numberOfUpdatedRows == 1;
} }
/** /**
@ -130,8 +130,8 @@ async function deleteOrganizationIfAdmin (organizationId, personId) {
.where('id_person', personId) .where('id_person', personId)
.where('id_organization', organizationId) .where('id_organization', organizationId)
}) })
.del() .del();
return numberOfDeletedRows == 1 return numberOfDeletedRows == 1;
} }
// Exporting a function // Exporting a function
@ -144,4 +144,4 @@ module.exports = {
updateOrganizationIfAdministrator, updateOrganizationIfAdministrator,
updateOrganizationIfAdministrator, updateOrganizationIfAdministrator,
deleteOrganizationIfAdmin deleteOrganizationIfAdmin
} };

View File

@ -11,8 +11,8 @@
IN THE SOFTWARE. IN THE SOFTWARE.
*/ */
const knex = require('../utils/knex_config') const knex = require('../utils/knex_config');
const bcrypt = require('bcrypt') const bcrypt = require('bcrypt');
/** /**
* Creates Person object by the specified fields * Creates Person object by the specified fields
@ -28,14 +28,14 @@ const bcrypt = require('bcrypt')
function person(email, password, display_name, date_of_birth, available, enabled, place_of_living) { function person(email, password, display_name, date_of_birth, available, enabled, place_of_living) {
const person = { const person = {
email: email.toLowerCase(), email: email.toLowerCase(),
password, password: password,
display_name, display_name: display_name,
date_of_birth, date_of_birth: date_of_birth,
available, available: available,
enabled, enabled: enabled,
place_of_living place_of_living: place_of_living
} };
return person return person;
} }
/** /**
@ -46,7 +46,7 @@ function person (email, password, display_name, date_of_birth, available, enable
async function getPersonByEmail(email){ async function getPersonByEmail(email){
return await knex('Person') return await knex('Person')
.where('email', email.toLowerCase()) .where('email', email.toLowerCase())
.first() .first();
} }
/** /**
@ -57,8 +57,8 @@ async function getPersonByEmail (email) {
async function getPersonById(id){ async function getPersonById(id){
return await knex('Person') return await knex('Person')
.select('*') .select('*')
.where({ id }) .where({ id: id })
.first() .first();
} }
/** /**
@ -81,13 +81,13 @@ async function registerPerson (person, activationLink) {
enabled: person.enabled, enabled: person.enabled,
place_of_living: person.place_of_living place_of_living: person.place_of_living
}) })
.returning('id') .returning("id");
await tr('ActivationLink') await tr('ActivationLink')
.insert({ .insert({
person_id: personIdResult[0].id, person_id: personIdResult[0].id,
identifier: activationLink identifier: activationLink
}) });
}) });
} }
/** /**
@ -102,15 +102,15 @@ async function getPersonByEmailAndPassword (email, password) {
.where('email', email.toLowerCase()) .where('email', email.toLowerCase())
.where('enabled', true) .where('enabled', true)
.select('*') .select('*')
.first() .first();
if(person){ if(person){
const passwordMatches = await bcrypt.compare(password, person.password) const passwordMatches = await bcrypt.compare(password, person.password);
if (passwordMatches) { if (passwordMatches) {
return person return person;
} }
} }
return null return null;
} }
/** /**
@ -121,7 +121,7 @@ async function getPersonByEmailAndPassword (email, password) {
async function updatePerson(person, person_id){ async function updatePerson(person, person_id){
await knex('Person') await knex('Person')
.where('id', person_id) .where('id', person_id)
.update(person) .update(person);
} }
/** /**
@ -131,9 +131,10 @@ async function updatePerson (person, person_id) {
async function deletePerson(person_id){ async function deletePerson(person_id){
await knex('Person') await knex('Person')
.where({id : person_id}) .where({id : person_id})
.del() .del();
} }
// 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.
@ -145,4 +146,4 @@ module.exports = {
registerPerson, registerPerson,
updatePerson, updatePerson,
deletePerson deletePerson
} };

View File

@ -11,8 +11,8 @@
IN THE SOFTWARE. IN THE SOFTWARE.
*/ */
const knex = require('../utils/knex_config') const knex = require('../utils/knex_config');
const organization_admin_model = require('../models/organization_admin_model') const organization_admin_model = require('../models/organization_admin_model');
/** /**
* POST Method * POST Method
@ -23,22 +23,24 @@ const organization_admin_model = require('../models/organization_admin_model')
* Required field(s): organization_id, person_id * Required field(s): organization_id, person_id
*/ */
async function addOrganizationAdmin(req, res){ async function addOrganizationAdmin(req, res){
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id || !req.body.person_id) { if (!req.body.organization_id || !req.body.person_id) {
return res.status(400).json({ error: 'Invalid request' }) return res.status(400).json({ error : "Invalid request"});
} }
try { try {
const isPersonAdmin = await organization_admin_model.isPersonAdmin(req.jwt.person_id, req.body.organization_id) const isPersonAdmin = await organization_admin_model.isPersonAdmin(req.jwt.person_id, req.body.organization_id);
// TOC/TOU // TOC/TOU
if(!isPersonAdmin){ if(!isPersonAdmin){
return res.status(401).json({ error: 'Forbidden' }) return res.status(401).json({error : "Forbidden"});
} }
await organization_admin_model.addOrganizationAdministrator(req.body.person_id, req.body.organization_id) await organization_admin_model.addOrganizationAdministrator(req.body.person_id, req.body.organization_id);
return res.status(200).json({ success: true }) return res.status(200).json({success : true});
} catch (error) { }
console.error('Error while adding organization admin: ' + error) catch (error) {
res.status(500).json({ error: 'Internal server error' }) console.error('Error while adding organization admin: ' + error);
res.status(500).json({error : "Internal server error"});
} }
} }
@ -52,21 +54,23 @@ async function addOrganizationAdmin (req, res) {
* Required field(s): organization_id * Required field(s): organization_id
*/ */
async function removeOrganizationAdmin(req, res){ async function removeOrganizationAdmin(req, res){
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id) { if (!req.body.organization_id) {
return res.status(400).json({ error: 'Invalid request' }) return res.status(400).json({ error : "Invalid request"});
} }
try{ try{
await organization_admin_model.removeOrganizationAdmin(req.jwt.person_id, req.body.organization_id) await organization_admin_model.removeOrganizationAdmin(req.jwt.person_id, req.body.organization_id);
return res.status(200).json({ success: true }) return res.status(200).json({success : true});
} catch (error) { }
console.error(error) catch (error){
return res.status(500).json({ error: 'Internal server error' }) console.error(error);
return res.status(500).json({ error: "Internal server error"});
} }
} }
module.exports = { module.exports = {
addOrganizationAdmin, addOrganizationAdmin,
removeOrganizationAdmin removeOrganizationAdmin
} };

View File

@ -11,7 +11,7 @@
IN THE SOFTWARE. IN THE SOFTWARE.
*/ */
const knex = require('../utils/knex_config') const knex = require('../utils/knex_config');
/** /**
* POST Request * POST Request
@ -22,9 +22,10 @@ const knex = require('../utils/knex_config')
* @returns the inserted Post * @returns the inserted Post
*/ */
async function createOrganizationPost(req, res){ async function createOrganizationPost(req, res){
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id || !req.body.content) { if (!req.body.organization_id || !req.body.content) {
return res.status(400).json({ error: 'Invalid request' }) return res.status(400).json({ error : "Invalid request"});
} }
try { try {
@ -33,12 +34,12 @@ async function createOrganizationPost (req, res) {
.where('id_person', req.jwt.person_id) .where('id_person', req.jwt.person_id)
.where('id_organization', req.body.organization_id) .where('id_organization', req.body.organization_id)
.select('*') .select('*')
.first() .first();
// Non-exploitable TOC/TOU weakness // Non-exploitable TOC/TOU weakness
// For more information https://softwareengineering.stackexchange.com/questions/451038/when-should-i-be-worried-of-time-of-check-time-of-use-vulnerabilities-during-dat // For more information https://softwareengineering.stackexchange.com/questions/451038/when-should-i-be-worried-of-time-of-check-time-of-use-vulnerabilities-during-dat
if(!isOrganizationAdmin){ if(!isOrganizationAdmin){
return res.status(403).json({ error: 'Forbidden' }) return res.status(403).json({error : "Forbidden"});
} }
const organizationPost = await knex('OrganizationPost') const organizationPost = await knex('OrganizationPost')
@ -47,11 +48,12 @@ async function createOrganizationPost (req, res) {
content: req.body.content, content: req.body.content,
original_author: req.jwt.person_id original_author: req.jwt.person_id
}) })
.returning('*') .returning('*');
return res.status(200).json(organizationPost[0]) return res.status(200).json(organizationPost[0]);
} catch (error) { }
console.log('Error while creating Organization Post: ' + error) catch (error) {
return res.status(500).json({ error: 'Internal server error' }) console.log("Error while creating Organization Post: " + error);
return res.status(500).json({error : "Internal server error"});
} }
} }
@ -64,7 +66,8 @@ async function createOrganizationPost (req, res) {
* Required field(s): none. * Required field(s): none.
*/ */
async function deleteOrganizationPost(req, res){ async function deleteOrganizationPost(req, res){
const organizationPostIdToDelete = req.params.id
const organizationPostIdToDelete = req.params.id;
try{ try{
const isOrganizationAdmin = await knex('OrganizationPost') const isOrganizationAdmin = await knex('OrganizationPost')
@ -72,20 +75,22 @@ async function deleteOrganizationPost (req, res) {
.where('OrganizationPost.id', organizationPostIdToDelete) .where('OrganizationPost.id', organizationPostIdToDelete)
.where('OrganizationAdministrator.id_person', req.jwt.person_id) .where('OrganizationAdministrator.id_person', req.jwt.person_id)
.select('*') .select('*')
.first() .first();
// Unexploitable TOC/TOU // Unexploitable TOC/TOU
if(isOrganizationAdmin){ if(isOrganizationAdmin){
await knex('OrganizationPost') await knex('OrganizationPost')
.where('id', organizationPostIdToDelete) .where('id', organizationPostIdToDelete)
.del() .del();
return res.status(200).json({ success: true }) return res.status(200).json({success : true});
} else {
return res.status(401).json({ error: 'Forbidden' })
} }
} catch (error) { else{
console.log(error) return res.status(401).json({error : "Forbidden"});
res.status(500).json({ error: 'Internal server error' }) }
}
catch (error) {
console.log(error);
res.status(500).json({error : "Internal server error"});
} }
} }
@ -95,4 +100,4 @@ async function deleteOrganizationPost (req, res) {
module.exports = { module.exports = {
createOrganizationPost, createOrganizationPost,
deleteOrganizationPost deleteOrganizationPost
} };

View File

@ -11,7 +11,7 @@
IN THE SOFTWARE. IN THE SOFTWARE.
*/ */
const organization_model = require('../models/organization_model') const organization_model = require('../models/organization_model');
/** /**
* POST Request * POST Request
@ -23,18 +23,20 @@ const organization_model = require('../models/organization_model')
* @returns the inserted organization * @returns the inserted organization
*/ */
async function createOrganization(req, res){ async function createOrganization(req, res){
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.name) { if (!req.body.name) {
return res.status(400).json({ error: 'Invalid request' }) return res.status(400).json({ error : "Invalid request"});
} }
try{ try{
const organization = organization_model.organization(req.body.name, req.body.location, req.body.description, req.body.is_hiring) const organization = organization_model.organization(req.body.name, req.body.location, req.body.description, req.body.is_hiring);
await organization_model.insertOrganization(organization, req.jwt.person_id) await organization_model.insertOrganization(organization, req.jwt.person_id);
return res.status(200).json({ Organization: organization }) return res.status(200).json({ Organization: organization });
} catch (error) { }
console.error('Error creating Organization:', error) catch (error){
res.status(500).json({ error: 'Internal server error' }) console.error('Error creating Organization:', error);
res.status(500).json({error : "Internal server error"});
} }
} }
@ -45,38 +47,41 @@ async function createOrganization (req, res) {
* Required field(s): none. * Required field(s): none.
*/ */
async function updateOrganization(req, res){ async function updateOrganization(req, res){
const updateOrganization = {}
const updateOrganization = {};
if(req.body.name){ if(req.body.name){
updateOrganization.name = req.body.name updateOrganization.name = req.body.name;
} }
if(req.body.location){ if(req.body.location){
updateOrganization.location = req.body.location updateOrganization.location = req.body.location;
} }
if(req.body.description){ if(req.body.description){
updateOrganization.description = req.body.description updateOrganization.description = req.body.description;
} }
if(req.body.is_hiring){ if(req.body.is_hiring){
updateOrganization.is_hiring = req.body.is_hiring updateOrganization.is_hiring = req.body.is_hiring;
} }
if (Object.keys(updateOrganization).length === 0) { if (Object.keys(updateOrganization).length === 0) {
return res.status(400).json({ error: 'Bad request. No data to update' }) return res.status(400).json({ error : "Bad request. No data to update"});
} }
try { try {
const isUpdateSuccessful = organization_model.updateOrganizationIfAdministrator(updateOrganization, req.params.id, req.jwt.person_id) const isUpdateSuccessful = organization_model.updateOrganizationIfAdministrator(updateOrganization, req.params.id, req.jwt.person_id);
if(isUpdateSuccessful){ if(isUpdateSuccessful){
return res.status(200).json({ success: 'true' }) return res.status(200).json({ success : "true"});
} else {
return res.status(404).json({ error: 'Organization either not found or insufficient permissions' })
} }
} catch (error) { else{
console.log(error) return res.status(404).json({error : "Organization either not found or insufficient permissions"});
return res.status(500).json({ error: 'Internal server error' }) }
}
catch (error) {
console.log(error);
return res.status(500).json({error : "Internal server error"});
} }
} }
@ -88,15 +93,17 @@ async function updateOrganization (req, res) {
*/ */
async function deleteOrganization(req, res){ async function deleteOrganization(req, res){
try { try {
const isDeleteSuccessful = organization_model.deleteOrganizationIfAdmin(req.params.id, req.jwt.person_id) const isDeleteSuccessful = organization_model.deleteOrganizationIfAdmin(req.params.id, req.jwt.person_id);
if(isDeleteSuccessful){ if(isDeleteSuccessful){
return res.status(403).json({ error: 'Forbidden' }) return res.status(403).json({error: "Forbidden"});
} else {
return res.status(200).json({ success: true })
} }
} catch (error) { else{
console.error(error) return res.status(200).json({success: true});
return res.status(500).json({ error: 'Internal server error' }) }
}
catch (error) {
console.error(error);
return res.status(500).json({error : "Internal server error"});
} }
} }
@ -111,15 +118,17 @@ async function deleteOrganization (req, res) {
*/ */
async function getOrganization(req, res){ async function getOrganization(req, res){
try { try {
const organization = await organization_model.getOrganizationById(req.params.id) const organization = await organization_model.getOrganizationById(req.params.id);
if(organization) { if(organization) {
return res.status(200).json(organization) return res.status(200).json(organization);
} else {
return res.status(404).json({ error: 'Not found' })
} }
} catch (error) { else{
console.error('Error retrieving an organization: ' + error) return res.status(404).json({error : "Not found"});
return res.status(500).json({ error: 'Internal server error' }) }
}
catch (error) {
console.error("Error retrieving an organization: " + error);
return res.status(500).json({error : "Internal server error"});
} }
} }
@ -128,4 +137,5 @@ module.exports = {
getOrganization, getOrganization,
updateOrganization, updateOrganization,
deleteOrganization deleteOrganization
} };

View File

@ -11,12 +11,12 @@
IN THE SOFTWARE. IN THE SOFTWARE.
*/ */
const validator = require('../utils/validation') const validator = require('../utils/validation');
const knex = require('../utils/knex_config') const knex = require('../utils/knex_config');
const jwt_utils = require('../utils/jwt_utils') const jwt_utils = require('../utils/jwt_utils');
const bcrypt = require('bcrypt') const bcrypt = require('bcrypt');
const crypto = require('crypto') const crypto = require('crypto');
const person_model = require('../models/person_model') const person_model = require('../models/person_model');
/** /**
* POST Request * POST Request
@ -28,28 +28,29 @@ const person_model = require('../models/person_model')
* @returns The activationlink identifier * @returns The activationlink identifier
*/ */
async function registerPerson(req, res){ async function registerPerson(req, res){
// Does this server allow users to register? // Does this server allow users to register?
if (process.env.ALLOW_USER_REGISTRATION === 'false'){ if (process.env.ALLOW_USER_REGISTRATION === 'false'){
return res.status(403).json({ error: 'Users cannot register on this server' }) return res.status(403).json({error : "Users cannot register on this server"});
} }
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.display_name || !req.body.email || !req.body.password) { if (!req.body.display_name || !req.body.email || !req.body.password) {
return res.status(400).json({ error: 'Some or all required fields are missing' }) return res.status(400).json({ error : "Some or all required fields are missing"});
} }
if(!validator.validateEmail(req.body.email)){ if(!validator.validateEmail(req.body.email)){
return res.status(400).json({ error: 'The email is not in a valid format' }) return res.status(400).json({ error : "The email is not in a valid format"});
} }
// Generate activation link token // Generate activation link token
const activationLink = crypto.randomBytes(16).toString('hex') const activationLink = crypto.randomBytes(16).toString('hex');
// Hash provided password // Hash provided password
const hashPasswordPromise = bcrypt.hash(req.body.password, 10) const hashPasswordPromise = bcrypt.hash(req.body.password, 10);
try{ try{
// Check whether e-mail exists already (enforced by database constraints) // Check whether e-mail exists already (enforced by database constraints)
const existingUser = await person_model.getPersonByEmail(req.body.email) const existingUser = await person_model.getPersonByEmail(req.body.email);
if(existingUser){ if(existingUser){
return res.status(409).json({ error: 'E-mail already in use' }) return res.status(409).json({ error: "E-mail already in use" });
} }
const personToInsert = person_model.person( const personToInsert = person_model.person(
req.body.email, req.body.email,
@ -58,12 +59,13 @@ async function registerPerson (req, res) {
req.body.date_of_birth, req.body.date_of_birth,
req.body.available, req.body.available,
true, true,
req.body.place_of_living) req.body.place_of_living);
await person_model.registerPerson(personToInsert, activationLink) await person_model.registerPerson(personToInsert, activationLink);
return res.status(200).json({ activationLink }) return res.status(200).json({ activationLink: activationLink });
} catch (error) { }
console.error('Error registering person:', error) catch (error){
res.status(500).json({ error: 'Internal server error' }) console.error('Error registering person:', error);
res.status(500).json({error : "Internal server error"});
} }
} }
@ -78,22 +80,24 @@ async function registerPerson (req, res) {
* @returns The token * @returns The token
*/ */
async function login(req, res){ async function login(req, res){
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.email || !req.body.password) { if (!req.body.email || !req.body.password) {
return res.status(400).json({ error: 'Invalid request' }) return res.status(400).json({error : "Invalid request"});
} }
try{ try{
const person = await person_model.getPersonByEmailAndPassword(req.body.email, req.body.password) const person = await person_model.getPersonByEmailAndPassword(req.body.email, req.body.password);
if (person){ if (person){
const token = jwt_utils.generateToken(person.id) const token = jwt_utils.generateToken(person.id);
res.status(200).json({ token }) res.status(200).json({token: token });
} else { }
res.status(401).json({ error: 'Unauthorized' }) else{
res.status(401).json({error : "Unauthorized"});
} }
} catch(error){ } catch(error){
console.error('Error logging in: ', error) console.error('Error logging in: ', error);
res.status(500).json({ error: 'Internal server error' }) res.status(500).json({error : "Internal server error"});
} }
} }
@ -108,18 +112,19 @@ async function login (req, res) {
*/ */
async function getPerson(req, res){ async function getPerson(req, res){
try { try {
const person = await person_model.getPersonById(req.params.id) const person = await person_model.getPersonById(req.params.id);
if(person){ if(person){
// I am retrieving either myself or an enabled user // I am retrieving either myself or an enabled user
if(person.id == req.jwt.person_id || person.enabled){ if(person.id == req.jwt.person_id || person.enabled){
delete person.password // remove password field for security reasons delete person['password']; // remove password field for security reasons
return res.status(200).send(person) return res.status(200).send(person);
} }
} }
return res.status(404).json({ error: 'Not found' }) return res.status(404).json({error: "Not found"});
} catch (error) { }
console.log('Error while getting person: ' + error) catch (error) {
return res.status(500).json({ error: 'Internal server error' }) console.log("Error while getting person: " + error);
return res.status(500).json({error : "Internal server error"});
} }
} }
@ -133,15 +138,16 @@ async function getPerson (req, res) {
*/ */
async function getMyself(req, res){ async function getMyself(req, res){
try{ try{
const person = await person_model.getPersonById(req.jwt.person_id) const person = await person_model.getPersonById(req.jwt.person_id);
if(person){ if(person){
delete person.password delete person['password'];
return res.status(200).send(person) return res.status(200).send(person);
} }
return res.status(404).json({ error: 'Not found' }) return res.status(404).json({error: "Not found"});
} catch (error) { }
console.log('Error while getting myself: ' + error) catch (error){
return res.status(500).json({ error: 'Internal server error' }) console.log("Error while getting myself: " + error);
return res.status(500).json({error : "Internal server error"});
} }
} }
@ -156,30 +162,32 @@ async function getMyself (req, res) {
* *
*/ */
async function updatePerson(req, res){ async function updatePerson(req, res){
if (req.jwt.person_id != req.params.id){ if (req.jwt.person_id != req.params.id){
return res.status(403).json({ error: 'Forbidden' }) return res.status(403).json({ error : "Forbidden"});
} }
const updatePerson = {} const updatePerson = {};
if(req.body.display_name){ if(req.body.display_name){
updatePerson.display_name = req.body.display_name updatePerson.display_name = req.body.display_name;
} }
if(req.body.date_of_birth){ if(req.body.date_of_birth){
if(validator.isPostgresDateFormatValid(req.body.date_of_birth)){ if(validator.isPostgresDateFormatValid(req.body.date_of_birth)){
updatePerson.date_of_birth = 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' }) else{
return res.status(400).json({ error : "Date of birth format not valid. Please specify a YYYY-MM-DD date"});
} }
} }
if(req.body.available){ if(req.body.available){
updatePerson.available = req.body.available updatePerson.available = req.body.available;
} }
if(req.body.place_of_living){ if(req.body.place_of_living){
updatePerson.place_of_living = 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 we are tying to change password, the old password must be provided
@ -187,25 +195,27 @@ async function updatePerson (req, res) {
const user = await knex('Person') const user = await knex('Person')
.select('password') .select('password')
.where({ id: req.jwt.person_id }) .where({ id: req.jwt.person_id })
.first() .first();
const passwordMatches = await bcrypt.compare(req.body.old_password, user.password) const passwordMatches = await bcrypt.compare(req.body.old_password, user.password);
if(passwordMatches){ if(passwordMatches){
updatePerson.password = await bcrypt.hash(req.body.new_password, 10) updatePerson.password = await bcrypt.hash(req.body.new_password, 10);
} else { }
return res.status(401).json({ error: 'Password verification failed' }) else{
return res.status(401).json({ error : "Password verification failed"});
} }
} }
if (Object.keys(updatePerson).length === 0) { if (Object.keys(updatePerson).length === 0) {
return res.status(400).json({ error: 'Bad request. No data to update' }) return res.status(400).json({ error : "Bad request. No data to update"});
} }
try { try {
await person_model.updatePerson(updatePerson, req.params.id) await person_model.updatePerson(updatePerson, req.params.id);
return res.status(200).json({ success: 'true' }) return res.status(200).json({ success : "true"});
} catch (error) { }
console.log('Error while updating a Person: ' + error) catch (error) {
return res.status(500).json({ error: 'Internal server error' }) console.log("Error while updating a Person: " + error);
return res.status(500).json({ error : "Internal server error"});
} }
} }
@ -221,11 +231,12 @@ async function updatePerson (req, res) {
async function deletePerson(req, res) { async function deletePerson(req, res) {
// TODO: Delete Organization if this user was its only administrator // TODO: Delete Organization if this user was its only administrator
try { try {
await person_model.deletePerson(req.jwt.person_id) await person_model.deletePerson(req.jwt.person_id);
return res.status(200).json({ success: true }) return res.status(200).json({success: true});
} catch (error) { }
console.log('Error deleting a Person: ' + error) catch (error) {
return res.status(500).json({ error: 'Internal server error' }) console.log("Error deleting a Person: " + error);
return res.status(500).json({error : "Internal server error"});
} }
} }
@ -239,4 +250,4 @@ module.exports = {
getMyself, getMyself,
updatePerson, updatePerson,
deletePerson deletePerson
} };

View File

@ -11,41 +11,41 @@
IN THE SOFTWARE. IN THE SOFTWARE.
*/ */
const jwt = require('jsonwebtoken') const jwt = require('jsonwebtoken');
function generateToken(person_id) { function generateToken(person_id) {
// The payload the JWT will carry within itself // The payload the JWT will carry within itself
const payload = { const payload = {
person_id person_id: person_id
} };
const token = jwt.sign(payload, process.env.JWT_SECRET_KEY, { const token = jwt.sign(payload, process.env.JWT_SECRET_KEY, {
expiresIn: '8h' expiresIn: '8h'
}) });
return token return token;
} }
// Middlware // Middlware
function verifyToken(req, res, next) { function verifyToken(req, res, next) {
const token = req.headers.authorization const token = req.headers.authorization;
if (!token) { if (!token) {
return res.status(401).send({ error: 'No token provided' }) return res.status(401).send({error : 'No token provided'});
} }
jwt.verify(token, process.env.JWT_SECRET_KEY, (err, decoded) => { jwt.verify(token, process.env.JWT_SECRET_KEY, (err, decoded) => {
if (err) { if (err) {
return res.status(401).send({ error: 'Failed to authenticate token' }) return res.status(401).send({error : 'Failed to authenticate token'});
} }
// If the token is valid, store the decoded data in the request object // If the token is valid, store the decoded data in the request object
// req.jwt will contain the payload created in generateToken // req.jwt will contain the payload created in generateToken
req.jwt = decoded req.jwt = decoded;
next() next();
}) });
} }
module.exports = { module.exports = {
generateToken, generateToken,
verifyToken verifyToken
} };

View File

@ -20,6 +20,6 @@ const knexInstance = require('knex')({
port: process.env.POSTGRES_PORT, port: process.env.POSTGRES_PORT,
database: 'Blink' database: 'Blink'
} }
}) });
module.exports = knexInstance module.exports = knexInstance;

View File

@ -17,8 +17,8 @@
* @returns true or false * @returns true or false
*/ */
function validateEmail(email) { function validateEmail(email) {
const regex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/ const regex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
return regex.test(email) return regex.test(email);
} }
/** /**
@ -28,11 +28,11 @@ function validateEmail (email) {
* @returns true or false * @returns true or false
*/ */
function isPostgresDateFormatValid(dateString) { function isPostgresDateFormatValid(dateString) {
const regex = /^\d{4}-\d{2}-\d{2}$/ const regex = /^\d{4}-\d{2}-\d{2}$/;
return regex.test(dateString) return regex.test(dateString);
} }
module.exports = { module.exports = {
validateEmail, validateEmail,
isPostgresDateFormatValid isPostgresDateFormatValid
} };

View File

@ -1,30 +1,30 @@
// Run me with "npm test" // Run me with "npm test"
const request = require('supertest') const request = require('supertest');
const app = require('../src/app') const app = require('../src/app');
require('dotenv').config({ path: '../src/.env' }) require('dotenv').config({ path: '../src/.env' });
describe('Person Tests', () => { describe('Person Tests', () => {
test('Correct registration', async () => { test('Correct registration', async () => {
const response = await request(app) const response = await request(app)
.post('/api/register') .post('/api/register')
.send({ .send({
email: 'johntestdoe@mail.org', email : "johntestdoe@mail.org",
password: 'password', password : "password",
display_name: 'John Doe' display_name : "John Doe"
})
expect(response.status).toBe(200)
expect(response.body).toEqual({ activationLink: expect.any(String) })
}) })
expect(response.status).toBe(200);
expect(response.body).toEqual({ activationLink: expect.any(String) });
});
test('Incorrect registration', async () => { test('Incorrect registration', async () => {
const response = await request(app) const response = await request(app)
.post('/api/register') .post('/api/register')
.send({ .send({
email: 'this is not an email', email : "this is not an email",
password: 'password', password : "password",
display_name: 'John Doe' display_name : "John Doe"
})
expect(response.status).toBe(400)
})
}) })
expect(response.status).toBe(400);
});
});

View File

@ -1 +1 @@
const apiUrl = 'http://localhost:3000/blinkapi' const apiUrl = "http://localhost:3000/blinkapi";