Skip to content
This repository has been archived by the owner on Aug 30, 2021. It is now read-only.

Commit

Permalink
Implemented password reset core feature
Browse files Browse the repository at this point in the history
  • Loading branch information
cpacker committed May 23, 2014
1 parent 2aebeca commit 58cfb2e
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 5 deletions.
160 changes: 160 additions & 0 deletions app/controllers/users.server.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ var mongoose = require('mongoose'),
passport = require('passport'),
User = mongoose.model('User'),
_ = require('lodash');
/* Requires for reset password */
var nodemailer = require('nodemailer');
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt-nodejs');
var async = require('async');
var crypto = require('crypto');

/**
* Get the error message from error object
Expand Down Expand Up @@ -92,6 +98,160 @@ exports.signin = function(req, res, next) {
})(req, res, next);
};

/**
* Forgot for reset password (forgot POST)
*/
exports.forgot = function(req, res, next) {
async.waterfall([
// Generate random token
function(done) {
crypto.randomBytes(20, function(err, buf) {
var token = buf.toString('hex');
done(err, token);
});
},
// Lookup user by email address
function(token, done) {
if (req.body.email) {
User.findOne({ email: req.body.email }, function(err, user) {
if (!user) {
return res.send(400, {
message: 'No account with that email address exists'
});
}

user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour

user.save(function(err) {
done(err, token, user);
});
});
} else {
return res.send(400, {
message: 'Email field must not be blank'
});
}

},
// If valid email, send reset email using service
function(token, user, done) {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid', // Choose email service, default SendGrid
auth: {
user: 'your_sendgrid_email@domain.com',
pass: 'your_sendgrid_password'
}
});
var mailOptions = {
to: user.email,
from: 'your_email@domain.com',
subject: 'Password Reset',
text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/auth/reset/' + token + '\n\n' +
'If you did not request this, please ignore this email and your password will remain unchanged.\n'
};
smtpTransport.sendMail(mailOptions, function(err) {
res.send(200, {
message: 'An email has been sent to ' + user.email + ' with further instructions.'
});
done(err, 'done');
});
}
], function(err) {
if (err) return next(err);
});
};

/**
* Reset password GET from email token
*/
exports.resetGet = function(req, res) {
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) {
if (!user) {
// res.render('404');
res.send(400, {
message: 'Password reset token is invalid or has expired.'
});
return res.redirect('/#!/forgot');
}

res.redirect('/#!/reset/' + req.params.token);

});
};

/**
* Reset password POST from email token
*/
exports.resetPost = function(req, res) {
// Init Variables
var passwordDetails = req.body;
var message = null;

async.waterfall([
function(done) {
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) {
if (!err && user) {
if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
user.password = passwordDetails.newPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;

user.save(function(err) {
if (err) {
return res.send(400, {
message: getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.send(400, err);
} else {
done(err, user);
}
});
}
});
} else {
return res.send(400, {
message: 'Passwords do not match'
});
}
} else {
return res.send(400, {
message: 'Password reset token is invalid or has expired.'
});
}
});
},
function(user, done) {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: 'your_sendgrid_email@domain.com',
pass: 'your_sendgrid_password'
}
});
var mailOptions = {
to: user.email,
from: 'your_email@domain.com',
subject: 'Your password has been changed',
text: 'Hello,\n\n' +
'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
};
smtpTransport.sendMail(mailOptions, function(err) {
res.send(200, {
message: 'Password changed successfully'
});
});
}
], function(err) {
res.redirect('/');
});
};

/**
* Update user details
*/
Expand Down
9 changes: 8 additions & 1 deletion app/models/user.server.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,14 @@ var UserSchema = new Schema({
created: {
type: Date,
default: Date.now
}
},
/* For reset password */
resetPasswordToken: {
type: String
},
resetPasswordExpires: {
type: Date
}
});

/**
Expand Down
3 changes: 3 additions & 0 deletions app/routes/users.server.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ module.exports = function(app) {
app.route('/auth/signup').post(users.signup);
app.route('/auth/signin').post(users.signin);
app.route('/auth/signout').get(users.signout);
app.route('/auth/forgot').post(users.forgot);
app.route('/auth/reset/:token').get(users.resetGet);
app.route('/auth/reset/:token').post(users.resetPost);

// Setting the facebook oauth routes
app.route('/auth/facebook').get(passport.authenticate('facebook', {
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@
"forever": "~0.11.0",
"bower": "~1.3.1",
"grunt-cli": "~0.1.13",
"glob": "~3.2.9"
"glob": "~3.2.9",
"bcrypt-nodejs": "0.0.3",
"async": "~0.8.0",
"nodemailer": "~0.6.3"
},
"devDependencies": {
"supertest": "~0.10.0",
Expand Down
2 changes: 1 addition & 1 deletion public/dist/application.min.js

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions public/modules/users/config/users.client.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ angular.module('users').config(['$stateProvider',
state('signin', {
url: '/signin',
templateUrl: 'modules/users/views/signin.client.view.html'
}).
state('forgot', {
url: '/forgot',
templateUrl: 'modules/users/views/forgot.client.view.html'
}).
state('reset', {
url: '/reset/:token',
templateUrl: 'modules/users/views/reset.client.view.html'
});
}
]);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

angular.module('users').controller('AuthenticationController', ['$scope', '$http', '$location', 'Authentication',
function($scope, $http, $location, Authentication) {
angular.module('users').controller('AuthenticationController', ['$scope', '$stateParams', '$http', '$location', 'Authentication',
function($scope, $stateParams, $http, $location, Authentication) {
$scope.authentication = Authentication;

//If user is signed in then redirect back home
Expand Down Expand Up @@ -30,5 +30,37 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$http
$scope.error = response.message;
});
};

$scope.forgot = function() {
$scope.success = $scope.error = null;

$http.post('/auth/forgot', $scope.credentials).success(function(response) {
// Show user success message and clear form
$scope.credentials = null;
$scope.success = response.message;

}).error(function(response) {
// Show user error message and clear form
$scope.credentials = null;
$scope.error = response.message;
});
};

// Change user password
$scope.reset = function() {
$scope.success = $scope.error = null;

$http.post('/auth/reset/' + $stateParams.token,
$scope.passwordDetails).success(function(response) {

// If successful show success message and clear form
$scope.success = response.message;
$scope.passwordDetails = null;

}).error(function(response) {
$scope.error = response.message;
});
};

}
]);
21 changes: 21 additions & 0 deletions public/modules/users/views/forgot.client.view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<section class="row" data-ng-controller="AuthenticationController">
<h3 class="col-md-12 text-center">Forgot your password?</h3>
<div class="col-xs-offset-2 col-xs-8 col-md-offset-5 col-md-2">
<form data-ng-submit="forgot()" class="signin form-horizontal" autocomplete="off">
<fieldset>
<div class="form-group">
<input type="text" id="email" name="email" class="form-control" data-ng-model="credentials.email" placeholder="Account Email">
</div>
<div class="text-center form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong>
</div>
<div data-ng-show="success" class="text-center text-success">
<strong>{{success}}</strong>
</div>
</fieldset>
</form>
</div>
</section>
26 changes: 26 additions & 0 deletions public/modules/users/views/reset.client.view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<section class="row" data-ng-controller="AuthenticationController">
<h3 class="col-md-12 text-center">Reset your password</h3>
<div class="col-xs-offset-2 col-xs-8 col-md-offset-5 col-md-2">
<form data-ng-submit="reset()" class="signin form-horizontal" autocomplete="off">
<fieldset>
<div class="form-group">
<label for="newPassword">New Password</label>
<input type="password" id="newPassword" name="newPassword" class="form-control" data-ng-model="passwordDetails.newPassword" placeholder="New Password">
</div>
<div class="form-group">
<label for="verifyPassword">Verify Password</label>
<input type="password" id="verifyPassword" name="verifyPassword" class="form-control" data-ng-model="passwordDetails.verifyPassword" placeholder="Verify Password">
</div>
<div class="text-center form-group">
<button type="submit" class="btn btn-large btn-primary">Update Password</button>
</div>
<div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong>
</div>
<div data-ng-show="success" class="text-center text-success">
<strong>{{success}}</strong>
</div>
</fieldset>
</form>
</div>
</section>
3 changes: 3 additions & 0 deletions public/modules/users/views/signin.client.view.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ <h3 class="col-md-12 text-center">Or with your account</h3>
<button type="submit" class="btn btn-primary">Sign in</button>&nbsp; or&nbsp;
<a href="/#!/signup">Sign up</a>
</div>
<div class"forgot-password">
<a href="/#!/forgot">Forgot your password?</a>
</div>
<div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong>
</div>
Expand Down

0 comments on commit 58cfb2e

Please sign in to comment.