Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registration & Login Backend #3

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
32 changes: 26 additions & 6 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
const express = require('express');
const logger = require('morgan');

const bodyParser = require('body-parser');
const UsersRouter = require('./routes/users.route');
const AuthRouter = require('./routes/auth.route');
const appConfig = require('./configs/app.config');
const app = express();

app.get('/', (req, res) => {
res.send('Hello CMS!')
app.use(logger('combined'));

app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE');
res.header('Access-Control-Expose-Headers', 'Content-Length');
res.header('Access-Control-Allow-Headers', 'Accept, Authorization, Content-Type, X-Requested-With, Range');
if (req.method === 'OPTIONS') {
return res.send(200);
} else {
return next();
}
});

app.use(bodyParser.json());
UsersRouter.routesConfig(app);
AuthRouter.routesConfig(app);

app.get(appConfig.apiEndpointBase, (req, res) => {
res.send(`CMS API ${appConfig.apiVersion}. RUN command <curl -I localhost:${appConfig.port}> for details.`)
});

app.listen(3000, () => {
console.log('Server started and listening on PORT: 3000');
});
module.exports = app;
7 changes: 7 additions & 0 deletions configs/app.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
"port": 3001,
"appEndpoint": "http://localhost:3001",
"apiEndpointBase": "/api/v1",
"apiVersion": "v1",
"environment": "dev"
};
9 changes: 9 additions & 0 deletions configs/auth.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
"secret": "life is beautiful, when it is beautiful!",
"expiration": 18000,//30 MINUTES in SECONDS
"permissionLevels": {
"VIEWER": 1,
"EDITOR": 2,
"ADMIN": 128
}
};
5 changes: 5 additions & 0 deletions configs/db.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
"host": "localhost",
"port": 27017,
"dbName": "cmsdb"
};
22 changes: 22 additions & 0 deletions controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const {secret} = require('../configs/auth.config');

exports.login = (req, res) => {
try {
let refreshId = req.body.userId + secret;
let salt = crypto.randomBytes(16).toString('base64');
let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64");
req.body.refreshKey = salt;
let token = jwt.sign(req.body, secret);
let b = new Buffer(hash);
let refresh_token = b.toString('base64');
res.status(201).send({accessToken: token, refreshToken: refresh_token});
} catch (err) {
res.status(500).send({errors: err});
}
};

exports.logout = (req, res) => {
res.status(204).send({accessToken: null, refreshToken: null});
};
56 changes: 56 additions & 0 deletions controllers/users.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const UserModel = require('../models/users.model');
const crypto = require('crypto');

exports.insert = (req, res) => {
let salt = crypto.randomBytes(16).toString('base64');
let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64");
req.body.password = salt + "$" + hash;
UserModel.createUser(req.body)
.then((result) => {
res.status(201).send({id: result._id});
});
};

exports.list = (req, res) => {
let limit = req.query.limit && req.query.limit <= 100 ? parseInt(req.query.limit) : 10;
let page = 0;
if (req.query) {
if (req.query.page) {
req.query.page = parseInt(req.query.page);
page = Number.isInteger(req.query.page) ? req.query.page : 0;
}
}

UserModel.list(limit, page)
.then((result) => {
res.status(200).send(result);
})
};

exports.getById = (req, res) => {
UserModel.findById(req.params.userId)
.then((result) => {
res.status(200).send(result);
}).catch(err => res.status(400).send({'message': err.message}));
};

exports.patchById = (req, res) => {
if (req.body.password) {
let salt = crypto.randomBytes(16).toString('base64');
let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64");
req.body.password = salt + "$" + hash;
}

UserModel.patchUser(req.params.userId, req.body)
.then((result) => {
res.status(204).send({});
});

};

exports.removeById = (req, res) => {
UserModel.removeById(req.params.userId)
.then((result) => {
res.status(204).send({});
});
};
38 changes: 38 additions & 0 deletions middlewares/auth.permission.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const authConfig = require('../configs/auth.config');

exports.minimumPermissionLevelRequired = (required_permission_level) => {
return (req, res, next) => {
let user_permission_level = parseInt(req.jwt.permissionLevel);
if (user_permission_level & required_permission_level) {
return next();
} else {
return res.status(403).send();
}
};
};

exports.onlySameUserOrAdminCanDoThisAction = (req, res, next) => {

let user_permission_level = parseInt(req.jwt.permissionLevel);
let userId = req.jwt.userId;
if (req.params && req.params.userId && userId === req.params.userId) {
return next();
} else {
if (user_permission_level & authConfig.permissionLevels.ADMIN) {
return next();
} else {
return res.status(403).send();
}
}

};

exports.sameUserCantDoThisAction = (req, res, next) => {
let userId = req.jwt.userId;

if (req.params.userId !== userId) {
return next();
} else {
return res.status(400).send();
}
};
60 changes: 60 additions & 0 deletions middlewares/auth.validation.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const {check} = require('express-validator');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const {secret} = require('../configs/auth.config');
const UserModel = require('../models/users.model');

exports.authFieldValidationRules = () => {
return [
// email should not be empty
check('email', 'email empty').notEmpty(),
// email must be valid
check('email', 'email is not valid').isEmail().normalizeEmail(),
// password should not be empty
check('password', 'password empty').notEmpty()
]
};

exports.matchEmailAndPassword = (req, res, next) => {
const {email, password} = req.body;
UserModel.findByEmail(email).then(user => {
if (!user) {
return res.status(404).send({errors: `User with email:<${email}> doesn't exist`});
} else {
let passwordFields = user.password.split('$');
let salt = passwordFields[0];
let hash = crypto.createHmac('sha512', salt).update(password).digest("base64");
if (hash === passwordFields[1]) {
req.body = {
userId: user._id,
email: user.email,
permissionLevel: user.permissionLevel,
provider: 'email',
name: `${user.firstName} ${user.lastName}`,
};
return next();
} else {
return res.status(400).send({errors: 'Invalid e-mail or password'});
}
}
});
};

exports.verifyJwtToken = (req, res, next) => {
if (req.headers['authorization']) {
try {
let authorization = req.headers['authorization'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).send();
} else {
req.jwt = jwt.verify(authorization[1], secret);
return next();
}

} catch (err) {
return res.status(403).send();
}
} else {
return res.status(401).send();
}
};
15 changes: 15 additions & 0 deletions middlewares/field.validation.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const {validationResult} = require('express-validator');

exports.validateRules = (req, res, next) => {
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}

const extractedErrors = [];
errors.array().map(err => extractedErrors.push({[err.param]: err.msg}));

return res.status(400).json({
errors: extractedErrors,
});
};
53 changes: 53 additions & 0 deletions middlewares/user.validation.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const mongoose = require('../services/db.service').mongoose;
const UserModel = require('../models/users.model');

const {check} = require('express-validator');

exports.registrationFieldValidationRules = () => {
return [
// firstName should not be empty
check('firstName', 'firstName empty').notEmpty(),
// lastName should not be empty
check('lastName', 'lastName empty').notEmpty(),
// email should not be empty
check('email', 'email empty').notEmpty(),
// email must be valid
check('email', 'email is not valid').isEmail().normalizeEmail(),
// password should not be empty
check('password', 'password empty').notEmpty(),
// password must be at least 8 chars long
check('password', 'password must be at least 8 chars long').isLength({min: 8})
]
};

exports.isEmailAlreadyExists = (req, res, next) => {
UserModel.findByEmail(req.body.email)
.then((result) => {
if (result) {
return res.status(409).json({message: 'Email already in use.'});
} else {
return next();
}
}).catch(err => res.status(500).json({errors: err}));
};

exports.verifyUserId = (req, res, next) => {
if (req.params.userId && mongoose.Types.ObjectId.isValid(req.params.userId)) {
return next();
}

return res.sendStatus(404);
};

exports.updatePasswordValidationRules = () => {
return [
check('email', 'email empty').notEmpty(),
// email must be valid
check('email', 'email is not valid').isEmail().normalizeEmail(),
// password should not be empty
check('password', 'password empty').notEmpty(),
// password must be at least 8 chars long
check('password', 'password must be at least 8 chars long').isLength({min: 8})
]
};

Loading