diff --git a/assets/js/controllers/authCtrl.js b/assets/js/controllers/authCtrl.js index bbcfb7853b..f83db3397c 100644 --- a/assets/js/controllers/authCtrl.js +++ b/assets/js/controllers/authCtrl.js @@ -82,5 +82,15 @@ habitrpg.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$loca $('#login-modal').modal('show'); } } + + $scope.passwordReset = function(email){ + $http.post(API_URL + '/api/v1/user/reset-password', {email:email}) + .success(function(){ + alert('New password sent.'); + }) + .error(function(data){ + alert(data.err); + }); + } } ]); diff --git a/package.json b/package.json index e656b2a2b2..25c0cae0b3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "nib": "~1.0.1", "jade": "~0.35.0", "passport": "~0.1.17", - "validator": "~1.5.1" + "validator": "~1.5.1", + "nodemailer": "~0.5.2" }, "private": true, "subdomain": "habitrpg", diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 9f044f0876..dedbb240e1 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -5,7 +5,8 @@ var sanitize = validator.sanitize; var passport = require('passport'); var helpers = require('habitrpg-shared/script/helpers'); var async = require('async'); -var derbyAuthUtil = require('derby-auth/utils'); +var utils = require('../utils'); +var nconf = require('nconf'); var User = require('../models/user').model; var api = module.exports; @@ -73,7 +74,7 @@ api.registerUser = function(req, res, next) { return cb("Username already taken"); } newUser = helpers.newUser(true); - salt = derbyAuthUtil.makeSalt(); + salt = utils.makeSalt(); newUser.auth = { local: { username: username, @@ -82,7 +83,7 @@ api.registerUser = function(req, res, next) { }, timestamps: {created: +new Date(), loggedIn: +new Date()} }; - newUser.auth.local.hashed_password = derbyAuthUtil.encryptPassword(password, salt); + newUser.auth.local.hashed_password = utils.encryptPassword(password, salt); user = new User(newUser); user.save(cb); } @@ -111,7 +112,7 @@ api.loginLocal = function(req, res, next) { // We needed the whole user object first so we can get his salt to encrypt password comparison User.findOne({ 'auth.local.username': username, - 'auth.local.hashed_password': derbyAuthUtil.encryptPassword(password, user.auth.local.salt) + 'auth.local.hashed_password': utils.encryptPassword(password, user.auth.local.salt) }, cb); } ], function(err, user) { @@ -160,6 +161,29 @@ api.loginFacebook = function(req, res, next) { }); }; +api.resetPassword = function(req, res, next){ + var email = req.body.email, + salt = utils.makeSalt(), + newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later) + hashed_password = utils.encryptPassword(newPassword, salt); + + User.findOne({'auth.local.email':email}, function(err, user){ + if (err) return res.json(500,{err:err}); + if (!user) return res.send(500, {err:"Couldn't find a user registered for email " + email}); + user.auth.local.salt = salt; + user.auth.local.hashed_password = hashed_password; + utils.sendEmail({ + from: "HabitRPG ", + to: email, + subject: "Password Reset for HabitRPG", + text: "Password for " + user.auth.local.username + " has been reset to " + newPassword + ". Log in at " + nconf.get('BASE_URL'), + html: "Password for " + user.auth.local.username + " has been reset to " + newPassword + ". Log in at" + nconf.get('BASE_URL') + }); + user.save(); + return res.send('New password sent to '+ email); + }); +}; + /* Registers a new user. Only accepting username/password registrations, no Facebook */ diff --git a/src/routes/auth.js b/src/routes/auth.js index c3068e078f..7b85c93dad 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -7,5 +7,6 @@ auth.setupPassport(router); //FIXME make this consistent with the others router.post('/api/v1/register', auth.registerUser); router.post('/api/v1/user/auth/local', auth.loginLocal); router.post('/api/v1/user/auth/facebook', auth.loginFacebook); +router.post('/api/v1/user/reset-password', auth.resetPassword); module.exports = router; \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000000..cfcd6d601e --- /dev/null +++ b/src/utils.js @@ -0,0 +1,34 @@ +var nodemailer = require('nodemailer'); +var nconf = require('nconf'); +var crypto = require('crypto'); + +module.exports.sendEmail = function(mailData) { + var smtpTransport = nodemailer.createTransport("SMTP",{ + service: nconf.get('SMTP_SERVICE'), + auth: { + user: nconf.get('SMTP_USER'), + pass: nconf.get('SMTP_PASS') + } + }); + smtpTransport.sendMail(mailData, function(error, response){ + if(error){ + console.log(error); + }else{ + console.log("Message sent: " + response.message); + } + smtpTransport.close(); // shut down the connection pool, no more messages + }); +} + +// Encryption using http://dailyjs.com/2010/12/06/node-tutorial-5/ +// Note: would use [password-hash](https://github.com/davidwood/node-password-hash), but we need to run +// model.query().equals(), so it's a PITA to work in their verify() function + +module.exports.encryptPassword = function(password, salt) { + return crypto.createHmac('sha1', salt).update(password).digest('hex'); +} + +module.exports.makeSalt = function() { + var len = 10; + return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').substring(0, len); +} \ No newline at end of file diff --git a/views/static/front.jade b/views/static/front.jade index 5bd1164f0f..8e7ffe92cd 100644 --- a/views/static/front.jade +++ b/views/static/front.jade @@ -41,14 +41,34 @@ block content input(type='text', ng-model='loginUsername', placeholder='{{useUUID ? "UUID" : "Username"}}') .control-group input(type='{{useUUID ? "text" : "password"}}', ng-model='loginPassword', placeholder='{{useUUID ? "API Token" : "Password"}}') - .control-group + //-.control-group label.checkbox input(type='checkbox', ng-click='useUUID = !useUUID') | Use UUID / API Token (For Facebook Users) .control-group input.btn.btn-primary(type='submit', value='Login') - .tab-pane#register-tab + // good god accordions have html ceremony + #forgot-password-accordion.accordion + .accordion-group + .accordion-heading + a.accordion-toggle(data-toggle='collapse', data-parent='#forgot-password-accordion', href='#forgot-password-group') + | Forgot Password + #forgot-password-group.accordion-body.collapse + .accordion-inner + form#derby-auth-password-reset(ng-submit='passwordReset(passwordResetEmail)') + h3 Email New Password + //.alert.alert-success {.success.passwordReset} + //.control-group.{#if..errors.passwordReset}error{/} + .control-group + input(type='text', name='email', placeholder='Email', ng-model='passwordResetEmail') + //span.help-inline {.errors.passwordReset} + input.btn(type='submit', value='Submit') + + + + + .tab-pane#register-tab form(ng-submit='register()', name='registrationForm') .control-group input(type='text', ng-model='registerVals.username', placeholder='Username', required)