Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/controllers/todoListController.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ exports.read_a_task = function(req, res) {
};

exports.update_a_task = function(req, res) {
Task.findOneAndUpdate(req.params.taskId, req.body, {new: true}, function(err, task) {
Task.findByIdAndUpdate(req.params.taskId, req.body, {new: true}, function(err, task) {
if (err)
res.send(err);
res.json(task);
Expand Down
186 changes: 186 additions & 0 deletions api/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
'use strict';

var mongoose = require('mongoose'),
jwt = require('jsonwebtoken'),
bcrypt = require('bcrypt'),
User = mongoose.model('User'),
path = require('path'),
async = require('async'),
crypto = require('crypto'),
_ = require('lodash'),
hbs = require('nodemailer-express-handlebars'),
email = process.env.MAILER_EMAIL_ID || 'auth_email_address@gmail.com',
pass = process.env.MAILER_PASSWORD || 'auth_email_pass',
nodemailer = require('nodemailer');


var smtpTransport = nodemailer.createTransport({
service: process.env.MAILER_SERVICE_PROVIDER || 'Gmail',
auth: {
user: email,
pass: pass
}
});


var handlebarsOptions = {
viewEngine: 'handlebars',
viewPath: path.resolve('./api/templates/'),
extName: '.html'
};

smtpTransport.use('compile', hbs(handlebarsOptions));


exports.register = function(req, res) {
var newUser = new User(req.body);
newUser.hash_password = bcrypt.hashSync(req.body.password, 10);
newUser.save(function(err, user) {
if (err) {
return res.status(400).send({
message: err
});
} else {
user.hash_password = undefined;
return res.json(user);
}
});
};

exports.index = function(req, res) {
return res.sendFile(path.resolve('./public/home.html'));
};

exports.render_forgot_password_template = function(req, res) {
return res.sendFile(path.resolve('./public/forgot-password.html'));
};

exports.render_reset_password_template = function(req, res) {
return res.sendFile(path.resolve('./public/reset-password.html'));
};

exports.sign_in = function(req, res) {
User.findOne({
email: req.body.email
}, function(err, user) {
if (err) throw err;
if (!user || !user.comparePassword(req.body.password)) {
return res.status(401).json({ message: 'Authentication failed. Invalid user or password.' });
}
return res.json({ token: jwt.sign({ email: user.email, fullName: user.fullName, _id: user._id }, 'RESTFULAPIs') });
});
};

exports.loginRequired = function(req, res, next) {
if (req.user) {
next();
} else {
return res.status(401).json({ message: 'Unauthorized user!' });
}
};

exports.forgot_password = function(req, res) {
async.waterfall([
function(done) {
User.findOne({
email: req.body.email
}).exec(function(err, user) {
if (user) {
done(err, user);
} else {
done('User not found.');
}
});
},
function(user, done) {
// create a unique token
var tokenObject = {
email: user.email,
id: user._id
};
var secret = user._id + '_' + user.email + '_' + new Date().getTime();
var token = jwt.sign(tokenObject, secret);
done(err, user, token);
},
function(user, token, done) {
User.findByIdAndUpdate({ _id: user._id }, { reset_password_token: token, reset_password_expires: Date.now() + 86400000 }, { new: true }).exec(function(err, new_user) {
done(err, token, new_user);
});
},
function(token, user, done) {
var data = {
to: user.email,
from: email,
template: 'forgot-password-email',
subject: 'Password help has arrived!',
context: {
url: 'http://localhost:3000/auth/reset_password?token=' + token,
name: user.fullName.split(' ')[0]
}
};

smtpTransport.sendMail(data, function(err) {
if (!err) {
return res.json({ message: 'Kindly check your email for further instructions' });
} else {
return done(err);
}
});
}
], function(err) {
return res.status(422).json({ message: err });
});
};

/**
* Reset password
*/
exports.reset_password = function(req, res, next) {
User.findOne({
reset_password_token: req.body.token,
reset_password_expires: {
$gt: Date.now()
}
}).exec(function(err, user) {
if (!err && user) {
if (req.body.newPassword === req.body.verifyPassword) {
user.hash_password = bcrypt.hashSync(req.body.newPassword, 10);
user.reset_password_token = undefined;
user.reset_password_expires = undefined;
user.save(function(err) {
if (err) {
return res.status(422).send({
message: err
});
} else {
var data = {
to: user.email,
from: email,
template: 'reset-password-email',
subject: 'Password Reset Confirmation',
context: {
name: user.fullName.split(' ')[0]
}
};

smtpTransport.sendMail(data, function(err) {
if (!err) {
return res.json({ message: 'Password reset' });
} else {
return done(err);
}
});
}
});
} else {
return res.status(422).send({
message: 'Passwords do not match'
});
}
} else {
return res.status(400).send({
message: 'Password reset token is invalid or has expired.'
});
}
});
};
9 changes: 3 additions & 6 deletions api/models/todoListModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ var TaskSchema = new Schema({
type: String,
Required: 'Kindly enter the name of the task'
},
Created_date: {
created_date: {
type: Date,
default: Date.now
},
status: {
type: [{
type: String,
enum: ['pending', 'ongoing', 'completed']
}],
default: ['pending']
type: String,
default: 'pending'
}
});

Expand Down
44 changes: 44 additions & 0 deletions api/models/userModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

var mongoose = require('mongoose'),
bcrypt = require('bcrypt'),
Schema = mongoose.Schema;

/**
* User Schema
*/
var UserSchema = new Schema({
fullName: {
type: String,
trim: true,
required: true
},
email: {
type: String,
unique: true,
lowercase: true,
trim: true,
required: true
},
hash_password: {
type: String,
required: true
},
created: {
type: Date,
default: Date.now
},
reset_password_token: {
type: String
},
reset_password_expires: {
type: Date
}
});

UserSchema.methods.comparePassword = function(password) {
return bcrypt.compareSync(password, this.hash_password);
};


mongoose.model('User', UserSchema);
37 changes: 28 additions & 9 deletions api/routes/todoListRoutes.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
'use strict';

module.exports = function(app) {
var todoList = require('../controllers/todoListController');
var todoList = require('../controllers/todoListController'),
userHandlers = require('../controllers/userController.js');

// todoList Routes
app.route('/tasks')
.get(todoList.list_all_tasks)
.post(todoList.create_a_task);
// todoList Routes

app.route('/tasks/:taskId')
.get(todoList.read_a_task)
.put(todoList.update_a_task)
.delete(todoList.delete_a_task);
app.route('/')
.get(userHandlers.index);

app.route('/tasks')
.get(todoList.list_all_tasks)
.post(userHandlers.loginRequired, todoList.create_a_task);

app.route('/tasks/:taskId')
.get(todoList.read_a_task)
.put(todoList.update_a_task)
.delete(todoList.delete_a_task);

app.route('/auth/register')
.post(userHandlers.register);

app.route('/auth/sign_in')
.post(userHandlers.sign_in);

app.route('/auth/forgot_password')
.get(userHandlers.render_forgot_password_template)
.post(userHandlers.forgot_password);

app.route('/auth/reset_password')
.get(userHandlers.render_reset_password_template)
.post(userHandlers.reset_password);
};
18 changes: 18 additions & 0 deletions api/templates/forgot-password-email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>

<head>
<title>Forget Password Email</title>
</head>

<body>
<div>
<h3>Dear {{name}},</h3>
<p>You requested for a password reset, kindly use this <a href="{{url}}">link</a> to reset your password</p>
<br>
<p>Cheers!</p>
</div>

</body>

</html>
20 changes: 20 additions & 0 deletions api/templates/reset-password-email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>

<head>
<title>Password Reset</title>
</head>

<body>
<div>
<h3>Dear {{name}},</h3>
<p>Your password has been successful reset, you can now login with your new password.</p>
<br>
<div>
Cheers!
</div>
</div>

</body>

</html>
15 changes: 11 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server.js"
},
"engines": {
"node": "6.11.1",
"npm": ">=3.10.8"
},
"repository": {
"type": "git",
"url": "git+https://github.com/generalgmt/RESTfulAPITutorial.git"
},
"keywords": [
"RESTful",
"API",
"Authentication",
"JWT",
"Tutorial"
],
"author": "olatunde garuba",
Expand All @@ -22,12 +28,13 @@
"url": "https://github.com/generalgmt/RESTfulAPITutorial/issues"
},
"homepage": "https://github.com/generalgmt/RESTfulAPITutorial#readme",
"devDependencies": {
"nodemon": "^1.11.0"
},
"dependencies": {
"bcrypt": "^1.0.2",
"body-parser": "^1.15.2",
"express": "^4.14.0",
"mongoose": "^4.7.2"
"jsonwebtoken": "^7.3.0",
"mongoose": "^4.7.2",
"nodemailer": "^4.0.1",
"nodemailer-express-handlebars": "^2.0.0"
}
}
Loading